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 re
import typing as T import typing as T
import hashlib import hashlib
import copy
from .. import build from .. import build
from .. import dependencies from .. import dependencies
@ -100,20 +99,18 @@ class InstallData:
self.strip_bin = strip_bin self.strip_bin = strip_bin
self.install_umask = install_umask self.install_umask = install_umask
self.targets: T.List[TargetInstallData] = [] self.targets: T.List[TargetInstallData] = []
self.headers: 'InstallType' = [] self.headers: T.List[InstallDataBase] = []
self.man: 'InstallType' = [] self.man: T.List[InstallDataBase] = []
self.data: 'InstallType' = [] self.data: T.List[InstallDataBase] = []
self.po_package_name: str = ''
self.po = []
self.install_scripts: T.List[ExecutableSerialisation] = [] self.install_scripts: T.List[ExecutableSerialisation] = []
self.install_subdirs: 'InstallSubdirsType' = [] self.install_subdirs: T.List[SubdirInstallData] = []
self.mesonintrospect = mesonintrospect self.mesonintrospect = mesonintrospect
self.version = version self.version = version
class TargetInstallData: class TargetInstallData:
def __init__(self, fname: str, outdir: str, aliases: T.Dict[str, str], strip: bool, 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_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.fname = fname
self.outdir = outdir self.outdir = outdir
self.aliases = aliases self.aliases = aliases
@ -122,8 +119,21 @@ class TargetInstallData:
self.rpath_dirs_to_remove = rpath_dirs_to_remove self.rpath_dirs_to_remove = rpath_dirs_to_remove
self.install_rpath = install_rpath self.install_rpath = install_rpath
self.install_mode = install_mode self.install_mode = install_mode
self.subproject = subproject
self.optional = optional 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: class ExecutableSerialisation:
def __init__(self, cmd_args, env: T.Optional[build.EnvironmentVariables] = None, exe_wrapper=None, def __init__(self, cmd_args, env: T.Optional[build.EnvironmentVariables] = None, exe_wrapper=None,
workdir=None, extra_paths=None, capture=None) -> None: workdir=None, extra_paths=None, capture=None) -> None:
@ -138,6 +148,7 @@ class ExecutableSerialisation:
self.pickled = False self.pickled = False
self.skip_if_destdir = False self.skip_if_destdir = False
self.verbose = False self.verbose = False
self.subproject = ''
class TestSerialisation: class TestSerialisation:
def __init__(self, name: str, project: str, suite: str, fname: T.List[str], 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: with open(ifilename, 'w') as f:
f.write(json.dumps(mfobj)) f.write(json.dumps(mfobj))
# Copy file from, to, and with mode unchanged # 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): def get_regen_filelist(self):
'''List of all files whose alteration means that the build '''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], i = TargetInstallData(self.get_target_filename(t), outdirs[0],
t.get_aliases(), should_strip, mappings, t.get_aliases(), should_strip, mappings,
t.rpath_dirs_to_remove, t.rpath_dirs_to_remove,
t.install_rpath, install_mode) t.install_rpath, install_mode, t.subproject)
d.targets.append(i) d.targets.append(i)
if isinstance(t, (build.SharedLibrary, build.SharedModule, build.Executable)): 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 # Install the import library; may not exist for shared modules
i = TargetInstallData(self.get_target_filename_for_linking(t), i = TargetInstallData(self.get_target_filename_for_linking(t),
implib_install_dir, {}, False, {}, set(), '', install_mode, implib_install_dir, {}, False, {}, set(), '', install_mode,
optional=isinstance(t, build.SharedModule)) t.subproject, optional=isinstance(t, build.SharedModule))
d.targets.append(i) d.targets.append(i)
if not should_strip and t.get_debug_filename(): if not should_strip and t.get_debug_filename():
debug_file = os.path.join(self.get_target_dir(t), t.get_debug_filename()) debug_file = os.path.join(self.get_target_dir(t), t.get_debug_filename())
i = TargetInstallData(debug_file, outdirs[0], i = TargetInstallData(debug_file, outdirs[0],
{}, False, {}, set(), '', {}, False, {}, set(), '',
install_mode, optional=True) install_mode, t.subproject,
optional=True)
d.targets.append(i) d.targets.append(i)
# Install secondary outputs. Only used for Vala right now. # Install secondary outputs. Only used for Vala right now.
if num_outdirs > 1: if num_outdirs > 1:
@ -1331,7 +1343,8 @@ class Backend:
if outdir is False: if outdir is False:
continue continue
f = os.path.join(self.get_target_dir(t), output) 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) d.targets.append(i)
elif isinstance(t, build.CustomTarget): elif isinstance(t, build.CustomTarget):
# If only one install_dir is specified, assume that all # If only one install_dir is specified, assume that all
@ -1345,7 +1358,7 @@ class Backend:
for output in t.get_outputs(): for output in t.get_outputs():
f = os.path.join(self.get_target_dir(t), output) f = os.path.join(self.get_target_dir(t), output)
i = TargetInstallData(f, outdirs[0], {}, False, {}, set(), None, install_mode, 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) d.targets.append(i)
else: else:
for output, outdir in zip(t.get_outputs(), outdirs): for output, outdir in zip(t.get_outputs(), outdirs):
@ -1354,23 +1367,11 @@ class Backend:
continue continue
f = os.path.join(self.get_target_dir(t), output) 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,
optional=not t.build_by_default) t.subproject, optional=not t.build_by_default)
d.targets.append(i) d.targets.append(i)
def generate_custom_install_script(self, d: InstallData) -> None: def generate_custom_install_script(self, d: InstallData) -> None:
result: T.List[ExecutableSerialisation] = [] d.install_scripts = self.build.install_scripts
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
def generate_header_install(self, d: InstallData) -> None: def generate_header_install(self, d: InstallData) -> None:
incroot = self.environment.get_includedir() incroot = self.environment.get_includedir()
@ -1387,7 +1388,7 @@ class Backend:
msg = 'Invalid header type {!r} can\'t be installed' msg = 'Invalid header type {!r} can\'t be installed'
raise MesonException(msg.format(f)) raise MesonException(msg.format(f))
abspath = f.absolute_path(srcdir, builddir) 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) d.headers.append(i)
def generate_man_install(self, d: InstallData) -> None: def generate_man_install(self, d: InstallData) -> None:
@ -1401,7 +1402,7 @@ class Backend:
subdir = os.path.join(manroot, 'man' + num) subdir = os.path.join(manroot, 'man' + num)
srcabs = f.absolute_path(self.environment.get_source_dir(), self.environment.get_build_dir()) srcabs = f.absolute_path(self.environment.get_source_dir(), self.environment.get_build_dir())
dstabs = os.path.join(subdir, os.path.basename(f.fname)) 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) d.man.append(i)
def generate_data_install(self, d: InstallData): def generate_data_install(self, d: InstallData):
@ -1416,7 +1417,7 @@ class Backend:
for src_file, dst_name in zip(de.sources, de.rename): for src_file, dst_name in zip(de.sources, de.rename):
assert(isinstance(src_file, mesonlib.File)) assert(isinstance(src_file, mesonlib.File))
dst_abs = os.path.join(subdir, dst_name) 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) d.data.append(i)
def generate_subdir_install(self, d: InstallData) -> None: def generate_subdir_install(self, d: InstallData) -> None:
@ -1432,8 +1433,8 @@ class Backend:
sd.install_dir) sd.install_dir)
if not sd.strip_directory: if not sd.strip_directory:
dst_dir = os.path.join(dst_dir, os.path.basename(src_dir)) dst_dir = os.path.join(dst_dir, os.path.basename(src_dir))
d.install_subdirs.append( i = SubdirInstallData(src_dir, dst_dir, sd.install_mode, sd.exclude, sd.subproject)
(src_dir, dst_dir, sd.install_mode, sd.exclude)) 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]]]]]]]: 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: class Headers:
def __init__(self, sources: T.List[File], install_subdir: T.Optional[str], 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.sources = sources
self.install_subdir = install_subdir self.install_subdir = install_subdir
self.custom_install_dir = install_dir self.custom_install_dir = install_dir
self.custom_install_mode = install_mode self.custom_install_mode = install_mode
self.subproject = subproject
# TODO: we really don't need any of these methods, but they're preserved to # TODO: we really don't need any of these methods, but they're preserved to
# keep APIs relying on them working. # keep APIs relying on them working.
@ -155,10 +157,11 @@ class Headers:
class Man: class Man:
def __init__(self, sources: T.List[File], install_dir: T.Optional[str], 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.sources = sources
self.custom_install_dir = install_dir self.custom_install_dir = install_dir
self.custom_install_mode = install_mode self.custom_install_mode = install_mode
self.subproject = subproject
def get_custom_install_dir(self) -> T.Optional[str]: def get_custom_install_dir(self) -> T.Optional[str]:
return self.custom_install_dir return self.custom_install_dir
@ -175,7 +178,8 @@ class InstallDir:
def __init__(self, src_subdir: str, inst_subdir: str, install_dir: str, def __init__(self, src_subdir: str, inst_subdir: str, install_dir: str,
install_mode: T.Optional['FileMode'], install_mode: T.Optional['FileMode'],
exclude: T.Tuple[T.Set[str], T.Set[str]], 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.source_subdir = src_subdir
self.installable_subdir = inst_subdir self.installable_subdir = inst_subdir
self.install_dir = install_dir self.install_dir = install_dir
@ -183,6 +187,7 @@ class InstallDir:
self.exclude = exclude self.exclude = exclude
self.strip_directory = strip_directory self.strip_directory = strip_directory
self.from_source_dir = from_source_dir self.from_source_dir = from_source_dir
self.subproject = subproject
class Build: class Build:
@ -2616,7 +2621,8 @@ class ConfigurationData:
# during install. # during install.
class Data: class Data:
def __init__(self, sources: T.List[File], install_dir: str, 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.sources = sources
self.install_dir = install_dir self.install_dir = install_dir
self.install_mode = install_mode self.install_mode = install_mode
@ -2624,6 +2630,7 @@ class Data:
self.rename = [os.path.basename(f.fname) for f in self.sources] self.rename = [os.path.basename(f.fname) for f in self.sources]
else: else:
self.rename = rename self.rename = rename
self.subproject = subproject
class TestSetup: class TestSetup:
def __init__(self, exe_wrapper: T.Optional[T.List[str]], gdb: bool, def __init__(self, exe_wrapper: T.Optional[T.List[str]], gdb: bool,

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

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

@ -110,14 +110,14 @@ def list_installed(installdata: backends.InstallData) -> T.Dict[str, str]:
for alias in t.aliases.keys(): for alias in t.aliases.keys():
res[os.path.join(installdata.build_dir, alias)] = \ res[os.path.join(installdata.build_dir, alias)] = \
os.path.join(installdata.prefix, t.outdir, os.path.basename(alias)) os.path.join(installdata.prefix, t.outdir, os.path.basename(alias))
for path, installpath, _ in installdata.data: for i in installdata.data:
res[path] = os.path.join(installdata.prefix, installpath) res[i.path] = os.path.join(installdata.prefix, i.install_path)
for path, installdir, _ in installdata.headers: for i in installdata.headers:
res[path] = os.path.join(installdata.prefix, installdir, os.path.basename(path)) res[i.path] = os.path.join(installdata.prefix, i.install_path, os.path.basename(i.path))
for path, installpath, _ in installdata.man: for i in installdata.man:
res[path] = os.path.join(installdata.prefix, installpath) res[i.path] = os.path.join(installdata.prefix, i.install_path)
for path, installpath, _, _ in installdata.install_subdirs: for i in installdata.install_subdirs:
res[path] = os.path.join(installdata.prefix, installpath) res[i.path] = os.path.join(installdata.prefix, i.install_path)
return res 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]]]]]]]: 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') 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]) return ModuleReturnValue(res, [res])
def create_package_file(self, infile, outfile, PACKAGE_RELATIVE_PATH, extra, confdata): 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: if conffile not in interpreter.build_def_files:
interpreter.build_def_files.append(conffile) 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) interpreter.build.data.append(res)
return res return res

@ -1678,7 +1678,7 @@ G_END_DECLS'''
with open(fname, 'w') as ofile: with open(fname, 'w') as ofile:
for package in packages: for package in packages:
ofile.write(package + '\n') 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): def _get_vapi_link_with(self, target):
link_with = [] link_with = []

@ -539,7 +539,7 @@ class PkgConfigModule(ExtensionModule):
self.generate_pkgconfig_file(state, deps, subdirs, name, description, url, self.generate_pkgconfig_file(state, deps, subdirs, name, description, url,
version, pcfile, conflicts, variables, version, pcfile, conflicts, variables,
False, dataonly) 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 = self.interpreter.extract_variables(kwargs, argname='uninstalled_variables', dict_new=True)
variables = parse_variable_list(variables) variables = parse_variable_list(variables)

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

@ -65,6 +65,7 @@ from mesonbuild.dependencies import PkgConfigDependency, ExternalProgram
import mesonbuild.dependencies.base import mesonbuild.dependencies.base
from mesonbuild.build import Target, ConfigurationData from mesonbuild.build import Target, ConfigurationData
import mesonbuild.modules.pkgconfig import mesonbuild.modules.pkgconfig
from mesonbuild.scripts import destdir_join
from mesonbuild.mtest import TAPParser, TestResult from mesonbuild.mtest import TAPParser, TestResult
@ -5496,6 +5497,52 @@ class AllPlatformTests(BasePlatformTests):
srcdir = os.path.join(self.common_test_dir, '2 cpp') srcdir = os.path.join(self.common_test_dir, '2 cpp')
self.init(srcdir, extra_args=['-Dbuild.b_lto=true']) 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): 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