diff --git a/docs/markdown/snippets/install_skip_subprojects.md b/docs/markdown/snippets/install_skip_subprojects.md new file mode 100644 index 000000000..16105a9a7 --- /dev/null +++ b/docs/markdown/snippets/install_skip_subprojects.md @@ -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. diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index 743574b11..8fe918965 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -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]]]]]]]: ''' diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 9a4f8b1e1..de8d94b6c 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -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, diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 2bc1afbf5..08c7424a3 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -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): diff --git a/mesonbuild/minstall.py b/mesonbuild/minstall.py index 212568a50..785ff5869 100644 --- a/mesonbuild/minstall.py +++ b/mesonbuild/minstall.py @@ -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: diff --git a/mesonbuild/mintro.py b/mesonbuild/mintro.py index 52f4ac0cb..551629427 100644 --- a/mesonbuild/mintro.py +++ b/mesonbuild/mintro.py @@ -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]]]]]]]: diff --git a/mesonbuild/modules/cmake.py b/mesonbuild/modules/cmake.py index ef53d9f54..dc45e71f8 100644 --- a/mesonbuild/modules/cmake.py +++ b/mesonbuild/modules/cmake.py @@ -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 diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py index f9660838e..a0b0de840 100644 --- a/mesonbuild/modules/gnome.py +++ b/mesonbuild/modules/gnome.py @@ -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 = [] diff --git a/mesonbuild/modules/pkgconfig.py b/mesonbuild/modules/pkgconfig.py index db589a35c..c0f561245 100644 --- a/mesonbuild/modules/pkgconfig.py +++ b/mesonbuild/modules/pkgconfig.py @@ -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) diff --git a/mesonbuild/modules/unstable_external_project.py b/mesonbuild/modules/unstable_external_project.py index 20c1671ee..451675ffb 100644 --- a/mesonbuild/modules/unstable_external_project.py +++ b/mesonbuild/modules/unstable_external_project.py @@ -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'}) diff --git a/run_unittests.py b/run_unittests.py index 62bf65c59..a82e476dc 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -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): ''' diff --git a/test cases/unit/91 install skip subprojects/foo.c b/test cases/unit/91 install skip subprojects/foo.c new file mode 100644 index 000000000..25927f5a1 --- /dev/null +++ b/test cases/unit/91 install skip subprojects/foo.c @@ -0,0 +1,4 @@ +int main(int argc, char *argv[]) +{ + return 0; +} diff --git a/test cases/unit/91 install skip subprojects/foo.dat b/test cases/unit/91 install skip subprojects/foo.dat new file mode 100644 index 000000000..421376db9 --- /dev/null +++ b/test cases/unit/91 install skip subprojects/foo.dat @@ -0,0 +1 @@ +dummy diff --git a/test cases/unit/91 install skip subprojects/foo.h b/test cases/unit/91 install skip subprojects/foo.h new file mode 100644 index 000000000..a7e26ac6c --- /dev/null +++ b/test cases/unit/91 install skip subprojects/foo.h @@ -0,0 +1 @@ +#define FOO diff --git a/test cases/unit/91 install skip subprojects/foo/foofile b/test cases/unit/91 install skip subprojects/foo/foofile new file mode 100644 index 000000000..e69de29bb diff --git a/test cases/unit/91 install skip subprojects/meson.build b/test cases/unit/91 install skip subprojects/meson.build new file mode 100644 index 000000000..cfbae9483 --- /dev/null +++ b/test cases/unit/91 install skip subprojects/meson.build @@ -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') diff --git a/test cases/unit/91 install skip subprojects/subprojects/bar/bar.c b/test cases/unit/91 install skip subprojects/subprojects/bar/bar.c new file mode 100644 index 000000000..25927f5a1 --- /dev/null +++ b/test cases/unit/91 install skip subprojects/subprojects/bar/bar.c @@ -0,0 +1,4 @@ +int main(int argc, char *argv[]) +{ + return 0; +} diff --git a/test cases/unit/91 install skip subprojects/subprojects/bar/bar.dat b/test cases/unit/91 install skip subprojects/subprojects/bar/bar.dat new file mode 100644 index 000000000..421376db9 --- /dev/null +++ b/test cases/unit/91 install skip subprojects/subprojects/bar/bar.dat @@ -0,0 +1 @@ +dummy diff --git a/test cases/unit/91 install skip subprojects/subprojects/bar/bar.h b/test cases/unit/91 install skip subprojects/subprojects/bar/bar.h new file mode 100644 index 000000000..a7e26ac6c --- /dev/null +++ b/test cases/unit/91 install skip subprojects/subprojects/bar/bar.h @@ -0,0 +1 @@ +#define FOO diff --git a/test cases/unit/91 install skip subprojects/subprojects/bar/bar/barfile b/test cases/unit/91 install skip subprojects/subprojects/bar/bar/barfile new file mode 100644 index 000000000..421376db9 --- /dev/null +++ b/test cases/unit/91 install skip subprojects/subprojects/bar/bar/barfile @@ -0,0 +1 @@ +dummy diff --git a/test cases/unit/91 install skip subprojects/subprojects/bar/meson.build b/test cases/unit/91 install skip subprojects/subprojects/bar/meson.build new file mode 100644 index 000000000..b5b073408 --- /dev/null +++ b/test cases/unit/91 install skip subprojects/subprojects/bar/meson.build @@ -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)