minstall: Add --skip-subprojects option

By default all subprojects are installed. If --skip-subprojects is given
with no value only the main project is installed. If --skip-subprojects
is given with a value, it should be a coma separated list of subprojects
to skip and all others will be installed.

Fixes: #2550.
pull/8398/head
Xavier Claessens 4 years ago committed by Jussi Pakkanen
parent 36d9d7a96f
commit 2fabd4c7dc
  1. 10
      docs/markdown/snippets/install_skip_subprojects.md
  2. 67
      mesonbuild/backend/backends.py
  3. 15
      mesonbuild/build.py
  4. 20
      mesonbuild/interpreter.py
  5. 55
      mesonbuild/minstall.py
  6. 16
      mesonbuild/mintro.py
  7. 4
      mesonbuild/modules/cmake.py
  8. 2
      mesonbuild/modules/gnome.py
  9. 2
      mesonbuild/modules/pkgconfig.py
  10. 7
      mesonbuild/modules/unstable_external_project.py
  11. 47
      run_unittests.py
  12. 4
      test cases/unit/91 install skip subprojects/foo.c
  13. 1
      test cases/unit/91 install skip subprojects/foo.dat
  14. 1
      test cases/unit/91 install skip subprojects/foo.h
  15. 0
      test cases/unit/91 install skip subprojects/foo/foofile
  16. 8
      test cases/unit/91 install skip subprojects/meson.build
  17. 4
      test cases/unit/91 install skip subprojects/subprojects/bar/bar.c
  18. 1
      test cases/unit/91 install skip subprojects/subprojects/bar/bar.dat
  19. 1
      test cases/unit/91 install skip subprojects/subprojects/bar/bar.h
  20. 1
      test cases/unit/91 install skip subprojects/subprojects/bar/bar/barfile
  21. 6
      test cases/unit/91 install skip subprojects/subprojects/bar/meson.build

@ -0,0 +1,10 @@
## Skip subprojects installation
It is now possible to skip installation of some or all subprojects. This is
useful when subprojects are internal dependencies static linked into the main
project.
By default all subprojects are still installed.
- `meson install -C builddir --skip-subprojects` installs only the main project.
- `meson install -C builddir --skip-subprojects foo,bar` installs the main project
and all subprojects except for subprojects `foo` and `bar` if they are used.

@ -23,7 +23,6 @@ import pickle
import re
import typing as T
import hashlib
import copy
from .. import build
from .. import dependencies
@ -100,20 +99,18 @@ class InstallData:
self.strip_bin = strip_bin
self.install_umask = install_umask
self.targets: T.List[TargetInstallData] = []
self.headers: 'InstallType' = []
self.man: 'InstallType' = []
self.data: 'InstallType' = []
self.po_package_name: str = ''
self.po = []
self.headers: T.List[InstallDataBase] = []
self.man: T.List[InstallDataBase] = []
self.data: T.List[InstallDataBase] = []
self.install_scripts: T.List[ExecutableSerialisation] = []
self.install_subdirs: 'InstallSubdirsType' = []
self.install_subdirs: T.List[SubdirInstallData] = []
self.mesonintrospect = mesonintrospect
self.version = version
class TargetInstallData:
def __init__(self, fname: str, outdir: str, aliases: T.Dict[str, str], strip: bool,
install_name_mappings: T.Dict, rpath_dirs_to_remove: T.Set[bytes],
install_rpath: str, install_mode: 'FileMode', optional: bool = False):
install_rpath: str, install_mode: 'FileMode', subproject: str, optional: bool = False):
self.fname = fname
self.outdir = outdir
self.aliases = aliases
@ -122,8 +119,21 @@ class TargetInstallData:
self.rpath_dirs_to_remove = rpath_dirs_to_remove
self.install_rpath = install_rpath
self.install_mode = install_mode
self.subproject = subproject
self.optional = optional
class InstallDataBase:
def __init__(self, path: str, install_path: str, install_mode: 'FileMode', subproject: str):
self.path = path
self.install_path = install_path
self.install_mode = install_mode
self.subproject = subproject
class SubdirInstallData(InstallDataBase):
def __init__(self, path: str, install_path: str, install_mode: 'FileMode', exclude, subproject: str):
super().__init__(path, install_path, install_mode, subproject)
self.exclude = exclude
class ExecutableSerialisation:
def __init__(self, cmd_args, env: T.Optional[build.EnvironmentVariables] = None, exe_wrapper=None,
workdir=None, extra_paths=None, capture=None) -> None:
@ -138,6 +148,7 @@ class ExecutableSerialisation:
self.pickled = False
self.skip_if_destdir = False
self.verbose = False
self.subproject = ''
class TestSerialisation:
def __init__(self, name: str, project: str, suite: str, fname: T.List[str],
@ -972,7 +983,7 @@ class Backend:
with open(ifilename, 'w') as f:
f.write(json.dumps(mfobj))
# Copy file from, to, and with mode unchanged
d.data.append((ifilename, ofilename, None))
d.data.append(InstallDataBase(ifilename, ofilename, None, ''))
def get_regen_filelist(self):
'''List of all files whose alteration means that the build
@ -1297,7 +1308,7 @@ class Backend:
i = TargetInstallData(self.get_target_filename(t), outdirs[0],
t.get_aliases(), should_strip, mappings,
t.rpath_dirs_to_remove,
t.install_rpath, install_mode)
t.install_rpath, install_mode, t.subproject)
d.targets.append(i)
if isinstance(t, (build.SharedLibrary, build.SharedModule, build.Executable)):
@ -1315,14 +1326,15 @@ class Backend:
# Install the import library; may not exist for shared modules
i = TargetInstallData(self.get_target_filename_for_linking(t),
implib_install_dir, {}, False, {}, set(), '', install_mode,
optional=isinstance(t, build.SharedModule))
t.subproject, optional=isinstance(t, build.SharedModule))
d.targets.append(i)
if not should_strip and t.get_debug_filename():
debug_file = os.path.join(self.get_target_dir(t), t.get_debug_filename())
i = TargetInstallData(debug_file, outdirs[0],
{}, False, {}, set(), '',
install_mode, optional=True)
install_mode, t.subproject,
optional=True)
d.targets.append(i)
# Install secondary outputs. Only used for Vala right now.
if num_outdirs > 1:
@ -1331,7 +1343,8 @@ class Backend:
if outdir is False:
continue
f = os.path.join(self.get_target_dir(t), output)
i = TargetInstallData(f, outdir, {}, False, {}, set(), None, install_mode)
i = TargetInstallData(f, outdir, {}, False, {}, set(), None,
install_mode, t.subproject)
d.targets.append(i)
elif isinstance(t, build.CustomTarget):
# If only one install_dir is specified, assume that all
@ -1345,7 +1358,7 @@ class Backend:
for output in t.get_outputs():
f = os.path.join(self.get_target_dir(t), output)
i = TargetInstallData(f, outdirs[0], {}, False, {}, set(), None, install_mode,
optional=not t.build_by_default)
t.subproject, optional=not t.build_by_default)
d.targets.append(i)
else:
for output, outdir in zip(t.get_outputs(), outdirs):
@ -1354,23 +1367,11 @@ class Backend:
continue
f = os.path.join(self.get_target_dir(t), output)
i = TargetInstallData(f, outdir, {}, False, {}, set(), None, install_mode,
optional=not t.build_by_default)
t.subproject, optional=not t.build_by_default)
d.targets.append(i)
def generate_custom_install_script(self, d: InstallData) -> None:
result: T.List[ExecutableSerialisation] = []
srcdir = self.environment.get_source_dir()
builddir = self.environment.get_build_dir()
for i in self.build.install_scripts:
fixed_args = []
for a in i.cmd_args:
a = a.replace('@SOURCE_ROOT@', srcdir)
a = a.replace('@BUILD_ROOT@', builddir)
fixed_args.append(a)
es = copy.copy(i)
es.cmd_args = fixed_args
result.append(es)
d.install_scripts = result
d.install_scripts = self.build.install_scripts
def generate_header_install(self, d: InstallData) -> None:
incroot = self.environment.get_includedir()
@ -1387,7 +1388,7 @@ class Backend:
msg = 'Invalid header type {!r} can\'t be installed'
raise MesonException(msg.format(f))
abspath = f.absolute_path(srcdir, builddir)
i = (abspath, outdir, h.get_custom_install_mode())
i = InstallDataBase(abspath, outdir, h.get_custom_install_mode(), h.subproject)
d.headers.append(i)
def generate_man_install(self, d: InstallData) -> None:
@ -1401,7 +1402,7 @@ class Backend:
subdir = os.path.join(manroot, 'man' + num)
srcabs = f.absolute_path(self.environment.get_source_dir(), self.environment.get_build_dir())
dstabs = os.path.join(subdir, os.path.basename(f.fname))
i = (srcabs, dstabs, m.get_custom_install_mode())
i = InstallDataBase(srcabs, dstabs, m.get_custom_install_mode(), m.subproject)
d.man.append(i)
def generate_data_install(self, d: InstallData):
@ -1416,7 +1417,7 @@ class Backend:
for src_file, dst_name in zip(de.sources, de.rename):
assert(isinstance(src_file, mesonlib.File))
dst_abs = os.path.join(subdir, dst_name)
i = (src_file.absolute_path(srcdir, builddir), dst_abs, de.install_mode)
i = InstallDataBase(src_file.absolute_path(srcdir, builddir), dst_abs, de.install_mode, de.subproject)
d.data.append(i)
def generate_subdir_install(self, d: InstallData) -> None:
@ -1432,8 +1433,8 @@ class Backend:
sd.install_dir)
if not sd.strip_directory:
dst_dir = os.path.join(dst_dir, os.path.basename(src_dir))
d.install_subdirs.append(
(src_dir, dst_dir, sd.install_mode, sd.exclude))
i = SubdirInstallData(src_dir, dst_dir, sd.install_mode, sd.exclude, sd.subproject)
d.install_subdirs.append(i)
def get_introspection_data(self, target_id: str, target: build.Target) -> T.List[T.Dict[str, T.Union[bool, str, T.List[T.Union[str, T.Dict[str, T.Union[str, T.List[str], bool]]]]]]]:
'''

@ -127,11 +127,13 @@ class DependencyOverride:
class Headers:
def __init__(self, sources: T.List[File], install_subdir: T.Optional[str],
install_dir: T.Optional[str], install_mode: T.Optional['FileMode']):
install_dir: T.Optional[str], install_mode: T.Optional['FileMode'],
subproject: str):
self.sources = sources
self.install_subdir = install_subdir
self.custom_install_dir = install_dir
self.custom_install_mode = install_mode
self.subproject = subproject
# TODO: we really don't need any of these methods, but they're preserved to
# keep APIs relying on them working.
@ -155,10 +157,11 @@ class Headers:
class Man:
def __init__(self, sources: T.List[File], install_dir: T.Optional[str],
install_mode: T.Optional['FileMode']):
install_mode: T.Optional['FileMode'], subproject: str):
self.sources = sources
self.custom_install_dir = install_dir
self.custom_install_mode = install_mode
self.subproject = subproject
def get_custom_install_dir(self) -> T.Optional[str]:
return self.custom_install_dir
@ -175,7 +178,8 @@ class InstallDir:
def __init__(self, src_subdir: str, inst_subdir: str, install_dir: str,
install_mode: T.Optional['FileMode'],
exclude: T.Tuple[T.Set[str], T.Set[str]],
strip_directory: bool, from_source_dir: bool = True):
strip_directory: bool, subproject: str,
from_source_dir: bool = True):
self.source_subdir = src_subdir
self.installable_subdir = inst_subdir
self.install_dir = install_dir
@ -183,6 +187,7 @@ class InstallDir:
self.exclude = exclude
self.strip_directory = strip_directory
self.from_source_dir = from_source_dir
self.subproject = subproject
class Build:
@ -2616,7 +2621,8 @@ class ConfigurationData:
# during install.
class Data:
def __init__(self, sources: T.List[File], install_dir: str,
install_mode: T.Optional['FileMode'] = None, rename: T.List[str] = None):
install_mode: T.Optional['FileMode'], subproject: str,
rename: T.List[str] = None):
self.sources = sources
self.install_dir = install_dir
self.install_mode = install_mode
@ -2624,6 +2630,7 @@ class Data:
self.rename = [os.path.basename(f.fname) for f in self.sources]
else:
self.rename = rename
self.subproject = subproject
class TestSetup:
def __init__(self, exe_wrapper: T.Optional[T.List[str]], gdb: bool,

@ -1966,7 +1966,9 @@ class MesonMain(InterpreterObject):
else:
m = 'Script or command {!r} not found or not executable'
raise InterpreterException(m.format(prog))
return self.interpreter.backend.get_executable_serialisation([found] + args)
es = self.interpreter.backend.get_executable_serialisation([found] + args)
es.subproject = self.interpreter.subproject
return es
def _process_script_args(
self, name: str, args: T.List[T.Union[
@ -2584,6 +2586,7 @@ class Interpreter(InterpreterBase):
elif isinstance(v, build.GeneratedList):
pass
elif isinstance(v, ExecutableSerialisation):
v.subproject = self.subproject
self.build.install_scripts.append(v)
elif isinstance(v, build.Data):
self.build.data.append(v)
@ -2593,8 +2596,9 @@ class Interpreter(InterpreterBase):
# FIXME: This is special cased and not ideal:
# The first source is our new VapiTarget, the rest are deps
self.process_new_values(v.sources[0])
elif isinstance(v, InstallDirHolder):
self.build.install_dirs.append(v.held_object)
elif isinstance(v, build.InstallDir):
self.build.install_dirs.append(v)
return InstallDirHolder(v)
elif isinstance(v, Test):
self.build.tests.append(v)
elif hasattr(v, 'held_object'):
@ -4229,7 +4233,7 @@ This will become a hard error in the future.''' % kwargs['input'], location=self
if install_dir is not None and not isinstance(install_dir, str):
raise InterpreterException('install_dir keyword argument must be a string if provided')
h = build.Headers(source_files, install_subdir, install_dir, install_mode)
h = build.Headers(source_files, install_subdir, install_dir, install_mode, self.subproject)
self.build.headers.append(h)
return HeadersHolder(h)
@ -4250,7 +4254,7 @@ This will become a hard error in the future.''' % kwargs['input'], location=self
if custom_install_dir is not None and not isinstance(custom_install_dir, str):
raise InterpreterException('install_dir must be a string.')
m = build.Man(sources, custom_install_dir, custom_install_mode)
m = build.Man(sources, custom_install_dir, custom_install_mode, self.subproject)
self.build.man.append(m)
return ManHolder(m)
@ -4350,7 +4354,7 @@ This will become a hard error in the future.''' % kwargs['input'], location=self
'"rename" and "sources" argument lists must be the same length if "rename" is given. '
f'Rename has {len(rename)} elements and sources has {len(sources)}.')
data = DataHolder(build.Data(sources, install_dir, install_mode, rename))
data = DataHolder(build.Data(sources, install_dir, install_mode, self.subproject, rename))
self.build.data.append(data.held_object)
return data
@ -4397,7 +4401,7 @@ This will become a hard error in the future.''' % kwargs['input'], location=self
exclude_directories = set()
exclude = (exclude_files, exclude_directories)
install_mode = self._get_kwarg_install_mode(kwargs)
idir = build.InstallDir(self.subdir, subdir, install_dir, install_mode, exclude, strip_directory)
idir = build.InstallDir(self.subdir, subdir, install_dir, install_mode, exclude, strip_directory, self.subproject)
self.build.install_dirs.append(idir)
return InstallDirHolder(idir)
@ -4587,7 +4591,7 @@ This will become a hard error in the future.''' % kwargs['input'], location=self
'is true')
cfile = mesonlib.File.from_built_file(ofile_path, ofile_fname)
install_mode = self._get_kwarg_install_mode(kwargs)
self.build.data.append(build.Data([cfile], idir, install_mode))
self.build.data.append(build.Data([cfile], idir, install_mode, self.subproject))
return mesonlib.File.from_built_file(self.subdir, output)
def extract_incdirs(self, kwargs):

@ -55,6 +55,7 @@ if T.TYPE_CHECKING:
wd: str
destdir: str
dry_run: bool
skip_subprojects: str
symlink_warning = '''Warning: trying to copy a symlink that points to a file. This will copy the file,
@ -77,7 +78,9 @@ def add_arguments(parser: argparse.Namespace) -> None:
parser.add_argument('--destdir', default=None,
help='Sets or overrides DESTDIR environment. (Since 0.57.0)')
parser.add_argument('--dry-run', '-n', action='store_true',
help='Doesn\'t actually install, but print logs.')
help='Doesn\'t actually install, but print logs. (Since 0.57.0)')
parser.add_argument('--skip-subprojects', nargs='?', const='*', default='',
help='Do not install files from given subprojects. (Since 0.58.0)')
class DirMaker:
def __init__(self, lf: T.TextIO, makedirs: T.Callable[..., None]):
@ -284,6 +287,10 @@ class Installer:
self.lf = lf
self.preserved_file_count = 0
self.dry_run = options.dry_run
# [''] means skip none,
# ['*'] means skip all,
# ['sub1', ...] means skip only those.
self.skip_subprojects = [i.strip() for i in options.skip_subprojects.split(',')]
def mkdir(self, *args: T.Any, **kwargs: T.Any) -> None:
if not self.dry_run:
@ -352,6 +359,11 @@ class Installer:
return run_exe(*args, **kwargs)
return 0
def install_subproject(self, subproject: str) -> bool:
if subproject and (subproject in self.skip_subprojects or '*' in self.skip_subprojects):
return False
return True
def log(self, msg: str) -> None:
if not self.options.quiet:
print(msg)
@ -521,43 +533,48 @@ class Installer:
raise
def install_subdirs(self, d: InstallData, dm: DirMaker, destdir: str, fullprefix: str) -> None:
for (src_dir, dst_dir, mode, exclude) in d.install_subdirs:
for i in d.install_subdirs:
if not self.install_subproject(i.subproject):
continue
self.did_install_something = True
full_dst_dir = get_destdir_path(destdir, fullprefix, dst_dir)
self.log('Installing subdir {} to {}'.format(src_dir, full_dst_dir))
full_dst_dir = get_destdir_path(destdir, fullprefix, i.install_path)
self.log('Installing subdir {} to {}'.format(i.path, full_dst_dir))
dm.makedirs(full_dst_dir, exist_ok=True)
self.do_copydir(d, src_dir, full_dst_dir, exclude, mode, dm)
self.do_copydir(d, i.path, full_dst_dir, i.exclude, i.install_mode, dm)
def install_data(self, d: InstallData, dm: DirMaker, destdir: str, fullprefix: str) -> None:
for i in d.data:
fullfilename = i[0]
outfilename = get_destdir_path(destdir, fullprefix, i[1])
mode = i[2]
if not self.install_subproject(i.subproject):
continue
fullfilename = i.path
outfilename = get_destdir_path(destdir, fullprefix, i.install_path)
outdir = os.path.dirname(outfilename)
if self.do_copyfile(fullfilename, outfilename, makedirs=(dm, outdir)):
self.did_install_something = True
self.set_mode(outfilename, mode, d.install_umask)
self.set_mode(outfilename, i.install_mode, d.install_umask)
def install_man(self, d: InstallData, dm: DirMaker, destdir: str, fullprefix: str) -> None:
for m in d.man:
full_source_filename = m[0]
outfilename = get_destdir_path(destdir, fullprefix, m[1])
if not self.install_subproject(m.subproject):
continue
full_source_filename = m.path
outfilename = get_destdir_path(destdir, fullprefix, m.install_path)
outdir = os.path.dirname(outfilename)
install_mode = m[2]
if self.do_copyfile(full_source_filename, outfilename, makedirs=(dm, outdir)):
self.did_install_something = True
self.set_mode(outfilename, install_mode, d.install_umask)
self.set_mode(outfilename, m.install_mode, d.install_umask)
def install_headers(self, d: InstallData, dm: DirMaker, destdir: str, fullprefix: str) -> None:
for t in d.headers:
fullfilename = t[0]
if not self.install_subproject(t.subproject):
continue
fullfilename = t.path
fname = os.path.basename(fullfilename)
outdir = get_destdir_path(destdir, fullprefix, t[1])
outdir = get_destdir_path(destdir, fullprefix, t.install_path)
outfilename = os.path.join(outdir, fname)
install_mode = t[2]
if self.do_copyfile(fullfilename, outfilename, makedirs=(dm, outdir)):
self.did_install_something = True
self.set_mode(outfilename, install_mode, d.install_umask)
self.set_mode(outfilename, t.install_mode, d.install_umask)
def run_install_script(self, d: InstallData, destdir: str, fullprefix: str) -> None:
env = {'MESON_SOURCE_ROOT': d.source_dir,
@ -570,6 +587,8 @@ class Installer:
env['MESON_INSTALL_QUIET'] = '1'
for i in d.install_scripts:
if not self.install_subproject(i.subproject):
continue
name = ' '.join(i.cmd_args)
if i.skip_if_destdir and destdir:
self.log('Skipping custom install script because DESTDIR is set {!r}'.format(name))
@ -588,6 +607,8 @@ class Installer:
def install_targets(self, d: InstallData, dm: DirMaker, destdir: str, fullprefix: str) -> None:
for t in d.targets:
if not self.install_subproject(t.subproject):
continue
if not os.path.exists(t.fname):
# For example, import libraries of shared modules are optional
if t.optional:

@ -110,14 +110,14 @@ def list_installed(installdata: backends.InstallData) -> T.Dict[str, str]:
for alias in t.aliases.keys():
res[os.path.join(installdata.build_dir, alias)] = \
os.path.join(installdata.prefix, t.outdir, os.path.basename(alias))
for path, installpath, _ in installdata.data:
res[path] = os.path.join(installdata.prefix, installpath)
for path, installdir, _ in installdata.headers:
res[path] = os.path.join(installdata.prefix, installdir, os.path.basename(path))
for path, installpath, _ in installdata.man:
res[path] = os.path.join(installdata.prefix, installpath)
for path, installpath, _, _ in installdata.install_subdirs:
res[path] = os.path.join(installdata.prefix, installpath)
for i in installdata.data:
res[i.path] = os.path.join(installdata.prefix, i.install_path)
for i in installdata.headers:
res[i.path] = os.path.join(installdata.prefix, i.install_path, os.path.basename(i.path))
for i in installdata.man:
res[i.path] = os.path.join(installdata.prefix, i.install_path)
for i in installdata.install_subdirs:
res[i.path] = os.path.join(installdata.prefix, i.install_path)
return res
def list_targets_from_source(intr: IntrospectionInterpreter) -> T.List[T.Dict[str, T.Union[bool, str, T.List[T.Union[str, T.Dict[str, T.Union[str, T.List[str], bool]]]]]]]:

@ -285,7 +285,7 @@ class CmakeModule(ExtensionModule):
}
mesonlib.do_conf_file(template_file, version_file, conf, 'meson')
res = build.Data([mesonlib.File(True, state.environment.get_scratch_dir(), version_file)], pkgroot)
res = build.Data([mesonlib.File(True, state.environment.get_scratch_dir(), version_file)], pkgroot, None, state.subproject)
return ModuleReturnValue(res, [res])
def create_package_file(self, infile, outfile, PACKAGE_RELATIVE_PATH, extra, confdata):
@ -370,7 +370,7 @@ class CmakeModule(ExtensionModule):
if conffile not in interpreter.build_def_files:
interpreter.build_def_files.append(conffile)
res = build.Data([mesonlib.File(True, ofile_path, ofile_fname)], install_dir)
res = build.Data([mesonlib.File(True, ofile_path, ofile_fname)], install_dir, None, state.subproject)
interpreter.build.data.append(res)
return res

@ -1678,7 +1678,7 @@ G_END_DECLS'''
with open(fname, 'w') as ofile:
for package in packages:
ofile.write(package + '\n')
return build.Data([mesonlib.File(True, outdir, fname)], install_dir)
return build.Data([mesonlib.File(True, outdir, fname)], install_dir, None, state.subproject)
def _get_vapi_link_with(self, target):
link_with = []

@ -539,7 +539,7 @@ class PkgConfigModule(ExtensionModule):
self.generate_pkgconfig_file(state, deps, subdirs, name, description, url,
version, pcfile, conflicts, variables,
False, dataonly)
res = build.Data([mesonlib.File(True, state.environment.get_scratch_dir(), pcfile)], pkgroot)
res = build.Data([mesonlib.File(True, state.environment.get_scratch_dir(), pcfile)], pkgroot, None, state.subproject)
variables = self.interpreter.extract_variables(kwargs, argname='uninstalled_variables', dict_new=True)
variables = parse_variable_list(variables)

@ -22,7 +22,7 @@ from ..mesonlib import (MesonException, Popen_safe, MachineChoice,
get_variable_regex, do_replacement)
from ..interpreterbase import InterpreterObject, InterpreterException, FeatureNew
from ..interpreterbase import stringArgs, permittedKwargs
from ..interpreter import Interpreter, DependencyHolder, InstallDirHolder
from ..interpreter import Interpreter, DependencyHolder
from ..compilers.compilers import CFLAGS_MAPPING, CEXE_MAPPING
from ..dependencies.base import InternalDependency, PkgConfigDependency
from ..environment import Environment
@ -212,9 +212,10 @@ class ExternalProject(InterpreterObject):
install_mode=None,
exclude=None,
strip_directory=True,
from_source_dir=False)
from_source_dir=False,
subproject=self.subproject)
return [self.target, InstallDirHolder(idir)]
return [self.target, idir]
@stringArgs
@permittedKwargs({'subdir'})

@ -65,6 +65,7 @@ from mesonbuild.dependencies import PkgConfigDependency, ExternalProgram
import mesonbuild.dependencies.base
from mesonbuild.build import Target, ConfigurationData
import mesonbuild.modules.pkgconfig
from mesonbuild.scripts import destdir_join
from mesonbuild.mtest import TAPParser, TestResult
@ -5496,6 +5497,52 @@ class AllPlatformTests(BasePlatformTests):
srcdir = os.path.join(self.common_test_dir, '2 cpp')
self.init(srcdir, extra_args=['-Dbuild.b_lto=true'])
def test_install_skip_subprojects(self):
testdir = os.path.join(self.unit_test_dir, '91 install skip subprojects')
self.init(testdir)
self.build()
main_expected = [
'',
'share',
'include',
'foo',
'bin',
'share/foo',
'share/foo/foo.dat',
'include/foo.h',
'foo/foofile',
'bin/foo' + exe_suffix,
]
bar_expected = [
'bar',
'share/foo/bar.dat',
'include/bar.h',
'bin/bar' + exe_suffix,
'bar/barfile'
]
env = get_fake_env(testdir, self.builddir, self.prefix)
cc = env.detect_c_compiler(MachineChoice.HOST)
if cc.get_argument_syntax() == 'msvc':
main_expected.append('bin/foo.pdb')
bar_expected.append('bin/bar.pdb')
prefix = destdir_join(self.installdir, self.prefix)
main_expected = [Path(prefix, p) for p in main_expected]
bar_expected = [Path(prefix, p) for p in bar_expected]
all_expected = main_expected + bar_expected
def check_installed_files(extra_args, expected):
args = ['install', '--destdir', self.installdir] + extra_args
self._run(self.meson_command + args, workdir=self.builddir)
all_files = [p for p in Path(self.installdir).rglob('*')]
self.assertEqual(sorted(expected), sorted(all_files))
windows_proof_rmtree(self.installdir)
check_installed_files([], all_expected)
check_installed_files(['--skip-subprojects'], main_expected)
check_installed_files(['--skip-subprojects', 'bar'], main_expected)
check_installed_files(['--skip-subprojects', 'another'], all_expected)
class FailureTests(BasePlatformTests):
'''

@ -0,0 +1,4 @@
int main(int argc, char *argv[])
{
return 0;
}

@ -0,0 +1,8 @@
project('foo', 'c')
install_data('foo.dat')
install_headers('foo.h')
install_subdir('foo', install_dir: '')
executable('foo', 'foo.c', install: true)
subproject('bar')

@ -0,0 +1,4 @@
int main(int argc, char *argv[])
{
return 0;
}

@ -0,0 +1,6 @@
project('bar', 'c')
install_data('bar.dat')
install_headers('bar.h')
install_subdir('bar', install_dir: '')
executable('bar', 'bar.c', install: true)
Loading…
Cancel
Save