aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXavier Claessens <xavier.claessens@collabora.com>2021-08-12 22:13:51 -0400
committerXavier Claessens <xclaesse@gmail.com>2021-08-17 15:19:18 -0400
commit8c5aa031b5ffb4eb3c61083072dcab49c8927d45 (patch)
treef62800f365cf6706fab33283436cbeef7df7206f
parent6d055b1e27dc6fc0fac967fbe2f439758a4c5d07 (diff)
downloadmeson-8c5aa031b5ffb4eb3c61083072dcab49c8927d45.zip
meson-8c5aa031b5ffb4eb3c61083072dcab49c8927d45.tar.gz
meson-8c5aa031b5ffb4eb3c61083072dcab49c8927d45.tar.bz2
Add install tags
Fixes: #7007.
-rw-r--r--docs/markdown/Installing.md76
-rw-r--r--docs/markdown/Python-module.md3
-rw-r--r--docs/markdown/Reference-manual.md33
-rw-r--r--docs/markdown/snippets/install_tag.md6
-rw-r--r--mesonbuild/backend/backends.py80
-rw-r--r--mesonbuild/backend/ninjabackend.py2
-rw-r--r--mesonbuild/build.py20
-rw-r--r--mesonbuild/interpreter/interpreter.py24
-rw-r--r--mesonbuild/interpreter/mesonmain.py15
-rw-r--r--mesonbuild/minstall.py24
-rw-r--r--mesonbuild/modules/gnome.py4
-rw-r--r--mesonbuild/modules/hotdoc.py1
-rw-r--r--mesonbuild/modules/i18n.py1
-rw-r--r--mesonbuild/modules/pkgconfig.py2
-rw-r--r--mesonbuild/modules/python.py8
-rw-r--r--mesonbuild/modules/qt.py1
-rw-r--r--test cases/unit/98 install tag/bar-custom.txt0
-rw-r--r--test cases/unit/98 install tag/bar-devel.h0
-rw-r--r--test cases/unit/98 install tag/bar-notag.txt0
-rw-r--r--test cases/unit/98 install tag/foo.in0
-rw-r--r--test cases/unit/98 install tag/foo1-devel.h0
-rw-r--r--test cases/unit/98 install tag/lib.c9
-rw-r--r--test cases/unit/98 install tag/main.c3
-rw-r--r--test cases/unit/98 install tag/meson.build73
-rw-r--r--test cases/unit/98 install tag/script.py7
-rw-r--r--unittests/allplatformstests.py125
26 files changed, 430 insertions, 87 deletions
diff --git a/docs/markdown/Installing.md b/docs/markdown/Installing.md
index 42c71f1..4a87ca3 100644
--- a/docs/markdown/Installing.md
+++ b/docs/markdown/Installing.md
@@ -74,7 +74,7 @@ giving an absolute install path.
install_data(sources : 'foo.dat', install_dir : '/etc') # -> /etc/foo.dat
```
-## Custom install behavior
+## Custom install script
Sometimes you need to do more than just install basic targets. Meson
makes this easy by allowing you to specify a custom script to execute
@@ -133,23 +133,57 @@ command in the build tree:
$ meson install --no-rebuild --only-changed
```
-## Finer control over install locations
-
-Sometimes it is necessary to only install a subsection of output files
-or install them in different directories. This can be done by
-specifying `install_dir` as an array rather than a single string. The
-array must have as many items as there are outputs and each entry
-specifies how the corresponding output file should be installed. For
-example:
-
-```meson
-custom_target(...
- output: ['file1', 'file2', 'file3'],
- install_dir: ['path1', false, 'path3'],
- ...
-)
-```
-
-In this case `file1` would be installed to `/prefix/path1/file1`,
-`file2` would not be installed at all and `file3` would be installed
-to `/prefix/path3/file3'.
+## Installation tags
+
+*Since 0.60.0*
+
+It is possible to install only a subset of the installable files using
+`meson install --tags tag1,tag2` command line. When `--tags` is specified, only
+files that have been tagged with one of the tags are going to be installed.
+
+This is intended to be used by packagers (e.g. distributions) who typically
+want to split `libfoo`, `libfoo-dev` and `libfoo-doc` packages. Instead of
+duplicating the list of installed files per category in each packaging system,
+it can be maintained in a single place, directly in upstream `meson.build` files.
+
+Meson sets predefined tags on some files. More tags are likely to be added over
+time, please help extending the list of well known categories.
+- `devel`:
+ * `static_library()`,
+ * `install_headers()`,
+ * `pkgconfig.generate()`,
+ * `gnome.generate_gir()` - `.gir` file,
+ * Files installed into `libdir` and with `.a` or `.pc` extension,
+ * File installed into `includedir`.
+- `runtime`:
+ * `executable()`,
+ * `shared_library()`,
+ * `shared_module()`,
+ * `jar()`,
+ * Files installed into `bindir`.
+ * Files installed into `libdir` and with `.so` or `.dll` extension.
+- `python-runtime`:
+ * `python.install_sources()`.
+- `man`:
+ * `install_man()`.
+- `doc`:
+ * `gnome.gtkdoc()`,
+ * `hotdoc.generate_doc()`.
+- `i18n`:
+ * `i18n.gettext()`,
+ * `qt.compile_translations()`,
+ * Files installed into `localedir`.
+- `typelib`:
+ * `gnome.generate_gir()` - `.typelib` file.
+
+Custom installation tag can be set using the `install_tag` keyword argument
+on various functions such as `custom_target()`, `configure_file()`,
+`install_subdir()` and `install_data()`. See their respective documentation
+in the reference manual for details. It is recommended to use one of the
+predefined tags above when possible.
+
+Installable files that have not been tagged either automatically by Meson, or
+manually using `install_tag` keyword argument won't be installed when `--tags`
+is used. They are reported at the end of `<builddir>/meson-logs/meson-log.txt`,
+it is recommended to add missing `install_tag` to have a tag on each installable
+files.
diff --git a/docs/markdown/Python-module.md b/docs/markdown/Python-module.md
index 3b7b4f5..20eae1a 100644
--- a/docs/markdown/Python-module.md
+++ b/docs/markdown/Python-module.md
@@ -135,6 +135,9 @@ All positional and keyword arguments are the same as for
- `subdir`: See documentation for the argument of the same name to
[][`extension_module()`]
+- `install_tag` *(since 0.60.0)*: A string used by `meson install --tags` command
+ to install only a subset of the files. By default it has the tag `python-runtime`.
+
#### `get_install_dir()`
``` meson
diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md
index 6454830..fd78a1e 100644
--- a/docs/markdown/Reference-manual.md
+++ b/docs/markdown/Reference-manual.md
@@ -304,6 +304,9 @@ false otherwise.
string, the file is not installed.
- `install_mode` *(since 0.47.0)*: specify the file mode in symbolic format
and optionally the owner/uid and group/gid for the installed files.
+- `install_tag` *(since 0.60.0)*: A string used by `meson install --tags` command
+ to install only a subset of the files. By default the file has no install
+ tag which means it is not being installed when `--tags` argument is specified.
- `output`: the output file name. *(since 0.41.0)* may contain
`@PLAINNAME@` or `@BASENAME@` substitutions. In configuration mode,
the permissions of the input file (if it is specified) are copied to
@@ -378,7 +381,7 @@ following.
- `install_dir`: If only one install_dir is provided, all outputs are installed there.
*Since 0.40.0* Allows you to specify the installation directory for each
corresponding output. For example:
- ```
+ ```meson
custom_target('different-install-dirs',
output : ['first.file', 'second.file'],
install : true,
@@ -388,15 +391,21 @@ following.
To only install some outputs, pass `false` for the outputs that you
don't want installed. For example:
- ```
- custom_target('only-install-second',
- output : ['first.file', 'second.file'],
- install : true,
- install_dir : [false, 'otherdir])
+ ```meson
+ custom_target('only-install-second',
+ output : ['first.file', 'second.file'],
+ install : true,
+ install_dir : [false, 'otherdir])
```
This would install `second.file` to `otherdir` and not install `first.file`.
- `install_mode` *(since 0.47.0)*: the file mode and optionally the
owner/uid and group/gid
+- `install_tag` *(since 0.60.0)*: A list of strings, one per output, used by
+ `meson install --tags` command to install only a subset of the files.
+ By default all outputs have no install tag which means they are not being
+ installed when `--tags` argument is specified. If only one tag is specified,
+ it is assumed that all outputs have the same tag. `false` can be used for
+ outputs that have no tag or are not installed.
- `output`: list of output files
- `env` *(since 0.57.0)*: environment variables to set, such as
`{'NAME1': 'value1', 'NAME2': 'value2'}` or `['NAME1=value1', 'NAME2=value2']`,
@@ -729,6 +738,9 @@ be passed to [shared and static libraries](#library).
and optionally the owner/uid and group/gid for the installed files.
- `install_rpath`: a string to set the target's rpath to after install
(but *not* before that). On Windows, this argument has no effect.
+- `install_tag` *(since 0.60.0)*: A string used by `meson install --tags` command
+ to install only a subset of the files. By default all build targets have the
+ tag `runtime` except for static libraries that have the `devel` tag.
- `objects`: list of prebuilt object files (usually for third party
products you don't have source to) that should be linked in this
target, **never** use this for object files that you build yourself.
@@ -1103,6 +1115,9 @@ arguments. The following keyword arguments are supported:
file from `rename` list. Nested paths are allowed and they are
joined with `install_dir`. Length of `rename` list must be equal to
the number of sources.
+- `install_tag` *(since 0.60.0)*: A string used by `meson install --tags` command
+ to install only a subset of the files. By default these files have no install
+ tag which means they are not being installed when `--tags` argument is specified.
See [Installing](Installing.md) for more examples.
@@ -1201,6 +1216,9 @@ The following keyword arguments are supported:
the owner/uid and group/gid for the installed files.
- `strip_directory` *(since 0.45.0)*: install directory contents. `strip_directory=false` by default.
If `strip_directory=true` only the last component of the source path is used.
+- `install_tag` *(since 0.60.0)*: A string used by `meson install --tags` command
+ to install only a subset of the files. By default these files have no install
+ tag which means they are not being installed when `--tags` argument is specified.
For a given directory `foo`:
```text
@@ -1975,6 +1993,9 @@ the following methods.
can be specified. If `true` the script will not be run if DESTDIR is set during
installation. This is useful in the case the script updates system wide
cache that is only needed when copying files into final destination.
+ *(since 0.60.0)* `install_tag` string keyword argument can be specified.
+ By default the script has no install tag which means it is not being run when
+ `meson install --tags` argument is specified.
*(since 0.54.0)* If `meson install` is called with the `--quiet` option, the
environment variable `MESON_INSTALL_QUIET` will be set.
diff --git a/docs/markdown/snippets/install_tag.md b/docs/markdown/snippets/install_tag.md
new file mode 100644
index 0000000..36df639
--- /dev/null
+++ b/docs/markdown/snippets/install_tag.md
@@ -0,0 +1,6 @@
+## Installation tags
+
+It is now possible to install only a subset of the installable files using
+`meson install --tags tag1,tag2` command line.
+
+See [documentation](Installing.md#installation-tags) for more details.
diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py
index aa8e844..6c877ea 100644
--- a/mesonbuild/backend/backends.py
+++ b/mesonbuild/backend/backends.py
@@ -115,7 +115,8 @@ class InstallData:
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', subproject: str, optional: bool = False):
+ install_rpath: str, install_mode: 'FileMode', subproject: str,
+ optional: bool = False, tag: T.Optional[str] = None):
self.fname = fname
self.outdir = outdir
self.aliases = aliases
@@ -126,22 +127,27 @@ class TargetInstallData:
self.install_mode = install_mode
self.subproject = subproject
self.optional = optional
+ self.tag = tag
class InstallDataBase:
- def __init__(self, path: str, install_path: str, install_mode: 'FileMode', subproject: str):
+ def __init__(self, path: str, install_path: str, install_mode: 'FileMode',
+ subproject: str, tag: T.Optional[str] = None):
self.path = path
self.install_path = install_path
self.install_mode = install_mode
self.subproject = subproject
+ self.tag = tag
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)
+ def __init__(self, path: str, install_path: str, install_mode: 'FileMode',
+ exclude, subproject: str, tag: T.Optional[str] = None):
+ super().__init__(path, install_path, install_mode, subproject, tag)
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, feed=None) -> None:
+ workdir=None, extra_paths=None, capture=None, feed=None,
+ tag: T.Optional[str] = None) -> None:
self.cmd_args = cmd_args
self.env = env
if exe_wrapper is not None:
@@ -155,6 +161,7 @@ class ExecutableSerialisation:
self.skip_if_destdir = False
self.verbose = False
self.subproject = ''
+ self.tag = tag
class TestSerialisation:
def __init__(self, name: str, project: str, suite: T.List[str], fname: T.List[str],
@@ -443,7 +450,8 @@ class Backend:
def get_executable_serialisation(self, cmd, workdir=None,
extra_bdeps=None, capture=None, feed=None,
- env: T.Optional[build.EnvironmentVariables] = None):
+ env: T.Optional[build.EnvironmentVariables] = None,
+ tag: T.Optional[str] = None):
exe = cmd[0]
cmd_args = cmd[1:]
if isinstance(exe, programs.ExternalProgram):
@@ -490,7 +498,7 @@ class Backend:
workdir = workdir or self.environment.get_build_dir()
return ExecutableSerialisation(exe_cmd + cmd_args, env,
exe_wrapper, workdir,
- extra_paths, capture, feed)
+ extra_paths, capture, feed, tag)
def as_meson_exe_cmdline(self, tname, exe, cmd_args, workdir=None,
extra_bdeps=None, capture=None, feed=None,
@@ -1032,7 +1040,7 @@ class Backend:
with open(ifilename, 'w', encoding='utf-8') as f:
f.write(json.dumps(mfobj))
# Copy file from, to, and with mode unchanged
- d.data.append(InstallDataBase(ifilename, ofilename, None, ''))
+ d.data.append(InstallDataBase(ifilename, ofilename, None, '', tag='devel'))
def get_regen_filelist(self):
'''List of all files whose alteration means that the build
@@ -1354,6 +1362,27 @@ class Backend:
with open(install_data_file, 'wb') as ofile:
pickle.dump(self.create_install_data(), ofile)
+ def guess_install_tag(self, fname: str, outdir: T.Optional[str] = None) -> T.Optional[str]:
+ prefix = self.environment.get_prefix()
+ bindir = Path(prefix, self.environment.get_bindir())
+ libdir = Path(prefix, self.environment.get_libdir())
+ incdir = Path(prefix, self.environment.get_includedir())
+ localedir = Path(prefix, self.environment.coredata.get_option(mesonlib.OptionKey('localedir')))
+ dest_path = Path(prefix, outdir, Path(fname).name) if outdir else Path(prefix, fname)
+ if bindir in dest_path.parents:
+ return 'runtime'
+ elif libdir in dest_path.parents:
+ if dest_path.suffix in {'.a', '.pc'}:
+ return 'devel'
+ elif dest_path.suffix in {'.so', '.dll'}:
+ return 'runtime'
+ elif incdir in dest_path.parents:
+ return 'devel'
+ elif localedir in dest_path.parents:
+ return 'i18n'
+ mlog.debug('Failed to guess install tag for', dest_path)
+ return None
+
def generate_target_install(self, d: InstallData) -> None:
for t in self.build.get_targets().values():
if not t.should_install():
@@ -1366,6 +1395,7 @@ class Backend:
"Pass 'false' for outputs that should not be installed and 'true' for\n" \
'using the default installation directory for an output.'
raise MesonException(m.format(t.name, num_out, t.get_outputs(), num_outdirs))
+ assert len(t.install_tag) == num_out
install_mode = t.get_custom_install_mode()
# Install the target output(s)
if isinstance(t, build.BuildTarget):
@@ -1387,11 +1417,13 @@ class Backend:
# Install primary build output (library/executable/jar, etc)
# Done separately because of strip/aliases/rpath
if outdirs[0] is not False:
+ tag = t.install_tag[0] or ('devel' if isinstance(t, build.StaticLibrary) else 'runtime')
mappings = t.get_link_deps_mapping(d.prefix, self.environment)
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.subproject)
+ t.install_rpath, install_mode, t.subproject,
+ tag=tag)
d.targets.append(i)
if isinstance(t, (build.SharedLibrary, build.SharedModule, build.Executable)):
@@ -1409,7 +1441,8 @@ 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,
- t.subproject, optional=isinstance(t, build.SharedModule))
+ t.subproject, optional=isinstance(t, build.SharedModule),
+ tag='devel')
d.targets.append(i)
if not should_strip and t.get_debug_filename():
@@ -1417,17 +1450,19 @@ class Backend:
i = TargetInstallData(debug_file, outdirs[0],
{}, False, {}, set(), '',
install_mode, t.subproject,
- optional=True)
+ optional=True, tag='devel')
d.targets.append(i)
# Install secondary outputs. Only used for Vala right now.
if num_outdirs > 1:
- for output, outdir in zip(t.get_outputs()[1:], outdirs[1:]):
+ for output, outdir, tag in zip(t.get_outputs()[1:], outdirs[1:], t.install_tag[1:]):
# User requested that we not install this output
if outdir is False:
continue
f = os.path.join(self.get_target_dir(t), output)
+ tag = tag or self.guess_install_tag(f, outdir)
i = TargetInstallData(f, outdir, {}, False, {}, set(), None,
- install_mode, t.subproject)
+ install_mode, t.subproject,
+ tag=tag)
d.targets.append(i)
elif isinstance(t, build.CustomTarget):
# If only one install_dir is specified, assume that all
@@ -1438,19 +1473,23 @@ class Backend:
# To selectively install only some outputs, pass `false` as
# the install_dir for the corresponding output by index
if num_outdirs == 1 and num_out > 1:
- for output in t.get_outputs():
+ for output, tag in zip(t.get_outputs(), t.install_tag):
f = os.path.join(self.get_target_dir(t), output)
+ tag = tag or self.guess_install_tag(f, outdirs[0])
i = TargetInstallData(f, outdirs[0], {}, False, {}, set(), None, install_mode,
- t.subproject, optional=not t.build_by_default)
+ t.subproject, optional=not t.build_by_default,
+ tag=tag)
d.targets.append(i)
else:
- for output, outdir in zip(t.get_outputs(), outdirs):
+ for output, outdir, tag in zip(t.get_outputs(), outdirs, t.install_tag):
# User requested that we not install this output
if outdir is False:
continue
f = os.path.join(self.get_target_dir(t), output)
+ tag = tag or self.guess_install_tag(f, outdir)
i = TargetInstallData(f, outdir, {}, False, {}, set(), None, install_mode,
- t.subproject, optional=not t.build_by_default)
+ t.subproject, optional=not t.build_by_default,
+ tag=tag)
d.targets.append(i)
def generate_custom_install_script(self, d: InstallData) -> None:
@@ -1475,7 +1514,7 @@ class Backend:
if not isinstance(f, File):
raise MesonException(f'Invalid header type {f!r} can\'t be installed')
abspath = f.absolute_path(srcdir, builddir)
- i = InstallDataBase(abspath, outdir, h.get_custom_install_mode(), h.subproject)
+ i = InstallDataBase(abspath, outdir, h.get_custom_install_mode(), h.subproject, tag='devel')
d.headers.append(i)
def generate_man_install(self, d: InstallData) -> None:
@@ -1495,7 +1534,7 @@ class Backend:
fname = fname.replace(f'.{m.locale}', '')
srcabs = f.absolute_path(self.environment.get_source_dir(), self.environment.get_build_dir())
dstabs = os.path.join(subdir, os.path.basename(fname))
- i = InstallDataBase(srcabs, dstabs, m.get_custom_install_mode(), m.subproject)
+ i = InstallDataBase(srcabs, dstabs, m.get_custom_install_mode(), m.subproject, tag='man')
d.man.append(i)
def generate_data_install(self, d: InstallData):
@@ -1510,7 +1549,8 @@ 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 = InstallDataBase(src_file.absolute_path(srcdir, builddir), dst_abs, de.install_mode, de.subproject)
+ tag = de.install_tag or self.guess_install_tag(dst_abs)
+ i = InstallDataBase(src_file.absolute_path(srcdir, builddir), dst_abs, de.install_mode, de.subproject, tag=tag)
d.data.append(i)
def generate_subdir_install(self, d: InstallData) -> None:
diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py
index 871bf9f..68c63b2 100644
--- a/mesonbuild/backend/ninjabackend.py
+++ b/mesonbuild/backend/ninjabackend.py
@@ -1513,6 +1513,7 @@ class NinjaBackend(backends.Backend):
args += ['--vapi', os.path.join('..', target.vala_vapi)]
valac_outputs.append(vapiname)
target.outputs += [target.vala_header, target.vala_vapi]
+ target.install_tag += ['devel', 'devel']
# Install header and vapi to default locations if user requests this
if len(target.install_dir) > 1 and target.install_dir[1] is True:
target.install_dir[1] = self.environment.get_includedir()
@@ -1524,6 +1525,7 @@ class NinjaBackend(backends.Backend):
args += ['--gir', os.path.join('..', target.vala_gir)]
valac_outputs.append(girname)
target.outputs.append(target.vala_gir)
+ target.install_tag.append('devel')
# Install GIR to default location if requested by user
if len(target.install_dir) > 3 and target.install_dir[3] is True:
target.install_dir[3] = os.path.join(self.environment.get_datadir(), 'gir-1.0')
diff --git a/mesonbuild/build.py b/mesonbuild/build.py
index 28ad60e..69a2d76 100644
--- a/mesonbuild/build.py
+++ b/mesonbuild/build.py
@@ -90,6 +90,7 @@ buildtarget_kwargs = {
'install_rpath',
'install_dir',
'install_mode',
+ 'install_tag',
'name_prefix',
'name_suffix',
'native',
@@ -189,7 +190,8 @@ class InstallDir(HoldableObject):
install_mode: 'FileMode',
exclude: T.Tuple[T.Set[str], T.Set[str]],
strip_directory: bool, subproject: str,
- from_source_dir: bool = True):
+ from_source_dir: bool = True,
+ install_tag: T.Optional[str] = None):
self.source_subdir = src_subdir
self.installable_subdir = inst_subdir
self.install_dir = install_dir
@@ -198,6 +200,7 @@ class InstallDir(HoldableObject):
self.strip_directory = strip_directory
self.from_source_dir = from_source_dir
self.subproject = subproject
+ self.install_tag = install_tag
class Build:
@@ -1014,6 +1017,7 @@ class BuildTarget(Target):
self.install_dir = typeslistify(kwargs.get('install_dir', [None]),
(str, bool))
self.install_mode = kwargs.get('install_mode', None)
+ self.install_tag = stringlistify(kwargs.get('install_tag', [None]))
main_class = kwargs.get('main_class', '')
if not isinstance(main_class, str):
raise InvalidArguments('Main class must be a string')
@@ -2206,6 +2210,7 @@ class CustomTarget(Target, CommandBase):
'install',
'install_dir',
'install_mode',
+ 'install_tag',
'build_always',
'build_always_stale',
'depends',
@@ -2339,10 +2344,19 @@ class CustomTarget(Target, CommandBase):
# the list index of that item will not be installed
self.install_dir = typeslistify(kwargs['install_dir'], (str, bool))
self.install_mode = kwargs.get('install_mode', None)
+ # If only one tag is provided, assume all outputs have the same tag.
+ # Otherwise, we must have as much tags as outputs.
+ self.install_tag = typeslistify(kwargs.get('install_tag', [None]), (str, bool))
+ if len(self.install_tag) == 1:
+ self.install_tag = self.install_tag * len(self.outputs)
+ elif len(self.install_tag) != len(self.outputs):
+ m = f'Target {self.name!r} has {len(self.outputs)} outputs but {len(self.install_tag)} "install_tag"s were found.'
+ raise InvalidArguments(m)
else:
self.install = False
self.install_dir = [None]
self.install_mode = None
+ self.install_tag = []
if 'build_always' in kwargs and 'build_always_stale' in kwargs:
raise InvalidArguments('build_always and build_always_stale are mutually exclusive. Combine build_by_default and build_always_stale.')
elif 'build_always' in kwargs:
@@ -2625,10 +2639,12 @@ class ConfigurationData(HoldableObject):
class Data(HoldableObject):
def __init__(self, sources: T.List[File], install_dir: str,
install_mode: 'FileMode', subproject: str,
- rename: T.List[str] = None):
+ rename: T.List[str] = None,
+ install_tag: T.Optional[str] = None):
self.sources = sources
self.install_dir = install_dir
self.install_mode = install_mode
+ self.install_tag = install_tag
if rename is None:
self.rename = [os.path.basename(f.fname) for f in self.sources]
else:
diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py
index ab7efa0..535229c 100644
--- a/mesonbuild/interpreter/interpreter.py
+++ b/mesonbuild/interpreter/interpreter.py
@@ -1598,6 +1598,7 @@ external dependencies (including libraries) must go to "dependencies".''')
def func_subdir_done(self, node, args, kwargs):
raise SubdirDoneRequest()
+ @FeatureNewKwargs('custom_target', '0.60.0', ['install_tag'])
@FeatureNewKwargs('custom_target', '0.57.0', ['env'])
@FeatureNewKwargs('custom_target', '0.48.0', ['console'])
@FeatureNewKwargs('custom_target', '0.47.0', ['install_mode', 'build_always_stale'])
@@ -1606,7 +1607,7 @@ external dependencies (including libraries) must go to "dependencies".''')
@permittedKwargs({'input', 'output', 'command', 'install', 'install_dir', 'install_mode',
'build_always', 'capture', 'depends', 'depend_files', 'depfile',
'build_by_default', 'build_always_stale', 'console', 'env',
- 'feed'})
+ 'feed', 'install_tag'})
@typed_pos_args('custom_target', str)
def func_custom_target(self, node: mparser.FunctionNode, args: T.Tuple[str], kwargs: 'TYPE_kwargs') -> build.CustomTarget:
if 'depfile' in kwargs and ('@BASENAME@' in kwargs['depfile'] or '@PLAINNAME@' in kwargs['depfile']):
@@ -1903,6 +1904,7 @@ This will become a hard error in the future.''' % kwargs['input'], location=self
KwargInfo('sources', ContainerTypeInfo(list, (str, mesonlib.File)), listify=True, default=[]),
KwargInfo('rename', ContainerTypeInfo(list, str), default=[], listify=True, since='0.46.0'),
INSTALL_MODE_KW.evolve(since='0.38.0'),
+ KwargInfo('install_tag', str, since='0.60.0'),
)
def func_install_data(self, node: mparser.BaseNode,
args: T.Tuple[T.List['mesonlib.FileOrString']],
@@ -1915,12 +1917,14 @@ 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)}.')
- return self.install_data_impl(sources, kwargs['install_dir'], kwargs['install_mode'], rename)
+ return self.install_data_impl(sources, kwargs['install_dir'], kwargs['install_mode'], rename,
+ kwargs['install_tag'])
def install_data_impl(self, sources: T.List[mesonlib.File], install_dir: str,
- install_mode: FileMode, rename: T.Optional[str]) -> build.Data:
+ install_mode: FileMode, rename: T.Optional[str],
+ tag: T.Optional[str]) -> build.Data:
"""Just the implementation with no validation."""
- data = build.Data(sources, install_dir, install_mode, self.subproject, rename)
+ data = build.Data(sources, install_dir, install_mode, self.subproject, rename, tag)
self.build.data.append(data)
return data
@@ -1928,6 +1932,7 @@ This will become a hard error in the future.''' % kwargs['input'], location=self
@typed_kwargs(
'install_subdir',
KwargInfo('install_dir', str, required=True),
+ KwargInfo('install_tag', str, since='0.60.0'),
KwargInfo('strip_directory', bool, default=False),
KwargInfo('exclude_files', ContainerTypeInfo(list, str),
default=[], listify=True, since='0.42.0',
@@ -1947,7 +1952,8 @@ This will become a hard error in the future.''' % kwargs['input'], location=self
kwargs['install_mode'],
exclude,
kwargs['strip_directory'],
- self.subproject)
+ self.subproject,
+ install_tag=kwargs['install_tag'])
self.build.install_dirs.append(idir)
return idir
@@ -1956,9 +1962,10 @@ This will become a hard error in the future.''' % kwargs['input'], location=self
@FeatureNewKwargs('configure_file', '0.41.0', ['capture'])
@FeatureNewKwargs('configure_file', '0.50.0', ['install'])
@FeatureNewKwargs('configure_file', '0.52.0', ['depfile'])
+ @FeatureNewKwargs('configure_file', '0.60.0', ['install_tag'])
@permittedKwargs({'input', 'output', 'configuration', 'command', 'copy', 'depfile',
'install_dir', 'install_mode', 'capture', 'install', 'format',
- 'output_format', 'encoding'})
+ 'output_format', 'encoding', 'install_tag'})
@noPosargs
def func_configure_file(self, node, args, kwargs):
if 'output' not in kwargs:
@@ -2139,7 +2146,10 @@ 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.subproject))
+ install_tag = kwargs.get('install_tag')
+ if install_tag is not None and not isinstance(install_tag, str):
+ raise InvalidArguments('install_tag keyword argument must be string')
+ self.build.data.append(build.Data([cfile], idir, install_mode, self.subproject, install_tag=install_tag))
return mesonlib.File.from_built_file(self.subdir, output)
def extract_incdirs(self, kwargs):
diff --git a/mesonbuild/interpreter/mesonmain.py b/mesonbuild/interpreter/mesonmain.py
index 97a695b..ea16b46 100644
--- a/mesonbuild/interpreter/mesonmain.py
+++ b/mesonbuild/interpreter/mesonmain.py
@@ -7,9 +7,9 @@ from .. import mlog
from ..mesonlib import MachineChoice, OptionKey
from ..programs import OverrideProgram, ExternalProgram
-from ..interpreterbase import (MesonInterpreterObject, FeatureNewKwargs, FeatureNew, FeatureDeprecated,
+from ..interpreterbase import (MesonInterpreterObject, FeatureNew, FeatureDeprecated,
typed_pos_args, permittedKwargs, noArgsFlattening, noPosargs, noKwargs,
- MesonVersionString, InterpreterException)
+ typed_kwargs, KwargInfo, MesonVersionString, InterpreterException)
from .interpreterobjects import (ExecutableHolder, ExternalProgramHolder,
CustomTargetHolder, CustomTargetIndexHolder,
@@ -107,20 +107,19 @@ class MesonMain(MesonInterpreterObject):
'0.55.0', self.interpreter.subproject)
return script_args
- @FeatureNewKwargs('add_install_script', '0.57.0', ['skip_if_destdir'])
- @permittedKwargs({'skip_if_destdir'})
+ @typed_kwargs('add_install_script',
+ KwargInfo('skip_if_destdir', bool, default=False, since='0.57.0'),
+ KwargInfo('install_tag', str, since='0.60.0'))
def add_install_script_method(self, args: 'T.Tuple[T.Union[str, mesonlib.File, ExecutableHolder], T.Union[str, mesonlib.File, CustomTargetHolder, CustomTargetIndexHolder], ...]', kwargs):
if len(args) < 1:
raise InterpreterException('add_install_script takes one or more arguments')
if isinstance(args[0], mesonlib.File):
FeatureNew.single_use('Passing file object to script parameter of add_install_script',
'0.57.0', self.interpreter.subproject)
- skip_if_destdir = kwargs.get('skip_if_destdir', False)
- if not isinstance(skip_if_destdir, bool):
- raise InterpreterException('skip_if_destdir keyword argument must be boolean')
script_args = self._process_script_args('add_install_script', args[1:], allow_built=True)
script = self._find_source_script(args[0], script_args)
- script.skip_if_destdir = skip_if_destdir
+ script.skip_if_destdir = kwargs['skip_if_destdir']
+ script.tag = kwargs['install_tag']
self.build.install_scripts.append(script)
@permittedKwargs(set())
diff --git a/mesonbuild/minstall.py b/mesonbuild/minstall.py
index d8faad6..6660d6c 100644
--- a/mesonbuild/minstall.py
+++ b/mesonbuild/minstall.py
@@ -25,7 +25,7 @@ import sys
import typing as T
from . import environment
-from .backend.backends import InstallData
+from .backend.backends import InstallData, InstallDataBase, TargetInstallData, ExecutableSerialisation
from .coredata import major_versions_differ, MesonVersionMismatchException
from .coredata import version as coredata_version
from .mesonlib import Popen_safe, RealPathAction, is_windows
@@ -56,6 +56,7 @@ if T.TYPE_CHECKING:
destdir: str
dry_run: bool
skip_subprojects: str
+ tags: str
symlink_warning = '''Warning: trying to copy a symlink that points to a file. This will copy the file,
@@ -81,6 +82,8 @@ def add_arguments(parser: argparse.ArgumentParser) -> None:
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)')
+ parser.add_argument('--tags', default=None,
+ help='Install only targets having one of the given tags. (Since 0.60.0)')
class DirMaker:
def __init__(self, lf: T.TextIO, makedirs: T.Callable[..., None]):
@@ -303,6 +306,7 @@ class Installer:
# ['*'] means skip all,
# ['sub1', ...] means skip only those.
self.skip_subprojects = [i.strip() for i in options.skip_subprojects.split(',')]
+ self.tags = [i.strip() for i in options.tags.split(',')] if options.tags else None
def remove(self, *args: T.Any, **kwargs: T.Any) -> None:
if not self.dry_run:
@@ -371,8 +375,10 @@ 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):
+ def should_install(self, d: T.Union[TargetInstallData, InstallDataBase, ExecutableSerialisation]) -> bool:
+ if d.subproject and (d.subproject in self.skip_subprojects or '*' in self.skip_subprojects):
+ return False
+ if self.tags and d.tag not in self.tags:
return False
return True
@@ -552,7 +558,7 @@ class Installer:
def install_subdirs(self, d: InstallData, dm: DirMaker, destdir: str, fullprefix: str) -> None:
for i in d.install_subdirs:
- if not self.install_subproject(i.subproject):
+ if not self.should_install(i):
continue
self.did_install_something = True
full_dst_dir = get_destdir_path(destdir, fullprefix, i.install_path)
@@ -562,7 +568,7 @@ class Installer:
def install_data(self, d: InstallData, dm: DirMaker, destdir: str, fullprefix: str) -> None:
for i in d.data:
- if not self.install_subproject(i.subproject):
+ if not self.should_install(i):
continue
fullfilename = i.path
outfilename = get_destdir_path(destdir, fullprefix, i.install_path)
@@ -573,7 +579,7 @@ class Installer:
def install_man(self, d: InstallData, dm: DirMaker, destdir: str, fullprefix: str) -> None:
for m in d.man:
- if not self.install_subproject(m.subproject):
+ if not self.should_install(m):
continue
full_source_filename = m.path
outfilename = get_destdir_path(destdir, fullprefix, m.install_path)
@@ -584,7 +590,7 @@ class Installer:
def install_headers(self, d: InstallData, dm: DirMaker, destdir: str, fullprefix: str) -> None:
for t in d.headers:
- if not self.install_subproject(t.subproject):
+ if not self.should_install(t):
continue
fullfilename = t.path
fname = os.path.basename(fullfilename)
@@ -605,7 +611,7 @@ class Installer:
env['MESON_INSTALL_QUIET'] = '1'
for i in d.install_scripts:
- if not self.install_subproject(i.subproject):
+ if not self.should_install(i):
continue
name = ' '.join(i.cmd_args)
if i.skip_if_destdir and destdir:
@@ -625,7 +631,7 @@ 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):
+ if not self.should_install(t):
continue
if not os.path.exists(t.fname):
# For example, import libraries of shared modules are optional
diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py
index a9cd1d3..a2f98bc 100644
--- a/mesonbuild/modules/gnome.py
+++ b/mesonbuild/modules/gnome.py
@@ -747,6 +747,7 @@ class GnomeModule(ExtensionModule):
scankwargs['install'] = kwargs['install']
scankwargs['install_dir'] = kwargs.get('install_dir_gir',
os.path.join(state.environment.get_datadir(), 'gir-1.0'))
+ scankwargs['install_tag'] = 'devel'
if 'build_by_default' in kwargs:
scankwargs['build_by_default'] = kwargs['build_by_default']
@@ -764,6 +765,7 @@ class GnomeModule(ExtensionModule):
typelib_kwargs['install'] = kwargs['install']
typelib_kwargs['install_dir'] = kwargs.get('install_dir_typelib',
os.path.join(state.environment.get_libdir(), 'girepository-1.0'))
+ typelib_kwargs['install_tag'] = 'typelib'
if 'build_by_default' in kwargs:
typelib_kwargs['build_by_default'] = kwargs['build_by_default']
@@ -1146,7 +1148,7 @@ class GnomeModule(ExtensionModule):
state.test(check_args, env=check_env, workdir=check_workdir, depends=custom_target)
res = [custom_target, alias_target]
if kwargs.get('install', True):
- res.append(state.backend.get_executable_serialisation(command + args))
+ res.append(state.backend.get_executable_serialisation(command + args, tag='doc'))
return ModuleReturnValue(custom_target, res)
def _get_build_args(self, kwargs, state, depends):
diff --git a/mesonbuild/modules/hotdoc.py b/mesonbuild/modules/hotdoc.py
index 4dccd06..19a1728 100644
--- a/mesonbuild/modules/hotdoc.py
+++ b/mesonbuild/modules/hotdoc.py
@@ -354,6 +354,7 @@ class HotdocTargetBuilder:
'--builddir', os.path.join(self.builddir, self.subdir)] +
self.hotdoc.get_command() +
['run', '--conf-file', hotdoc_config_name])
+ install_script.tag = 'doc'
return (target, install_script)
diff --git a/mesonbuild/modules/i18n.py b/mesonbuild/modules/i18n.py
index 57d25fd..539f82b 100644
--- a/mesonbuild/modules/i18n.py
+++ b/mesonbuild/modules/i18n.py
@@ -181,6 +181,7 @@ class I18nModule(ExtensionModule):
# to custom_targets. Crude hack: set the build target's subdir manually.
# Bonus: the build tree has something usable as an uninstalled bindtextdomain() target dir.
'install_dir': path.join(install_dir, l, 'LC_MESSAGES'),
+ 'install_tag': 'i18n',
}
gmotarget = build.CustomTarget(l+'.mo', path.join(state.subdir, l, 'LC_MESSAGES'), state.subproject, gmo_kwargs)
targets.append(gmotarget)
diff --git a/mesonbuild/modules/pkgconfig.py b/mesonbuild/modules/pkgconfig.py
index 48bbc34..e57c4f6 100644
--- a/mesonbuild/modules/pkgconfig.py
+++ b/mesonbuild/modules/pkgconfig.py
@@ -548,7 +548,7 @@ class PkgConfigModule(ExtensionModule):
self._generate_pkgconfig_file(state, deps, subdirs, name, description, url,
version, pcfile, conflicts, variables,
unescaped_variables, False, dataonly)
- res = build.Data([mesonlib.File(True, state.environment.get_scratch_dir(), pcfile)], pkgroot, None, state.subproject)
+ res = build.Data([mesonlib.File(True, state.environment.get_scratch_dir(), pcfile)], pkgroot, None, state.subproject, install_tag='devel')
variables = self.interpreter.extract_variables(kwargs, argname='uninstalled_variables', dict_new=True)
variables = parse_variable_list(variables)
unescaped_variables = self.interpreter.extract_variables(kwargs, argname='unescaped_uninstalled_variables')
diff --git a/mesonbuild/modules/python.py b/mesonbuild/modules/python.py
index f38becf..65a73a7 100644
--- a/mesonbuild/modules/python.py
+++ b/mesonbuild/modules/python.py
@@ -344,6 +344,7 @@ if T.TYPE_CHECKING:
pure: bool
subdir: str
+ install_tag: T.Optional[str]
class PythonInstallation(ExternalProgramHolder):
@@ -436,14 +437,15 @@ class PythonInstallation(ExternalProgramHolder):
return dep
@typed_pos_args('install_data', varargs=(str, mesonlib.File))
- @typed_kwargs('python_installation.install_sources', _PURE_KW, _SUBDIR_KW)
+ @typed_kwargs('python_installation.install_sources', _PURE_KW, _SUBDIR_KW,
+ KwargInfo('install_tag', str, since='0.60.0'))
def install_sources_method(self, args: T.Tuple[T.List[T.Union[str, mesonlib.File]]],
kwargs: 'PyInstallKw') -> 'Data':
+ tag = kwargs['install_tag'] or 'runtime'
return self.interpreter.install_data_impl(
self.interpreter.source_strings_to_files(args[0]),
self._get_install_dir_impl(kwargs['pure'], kwargs['subdir']),
- mesonlib.FileMode(),
- None)
+ mesonlib.FileMode(), rename=None, tag=tag)
@noPosargs
@typed_kwargs('python_installation.install_dir', _PURE_KW, _SUBDIR_KW)
diff --git a/mesonbuild/modules/qt.py b/mesonbuild/modules/qt.py
index 5efd668..ed66ae9 100644
--- a/mesonbuild/modules/qt.py
+++ b/mesonbuild/modules/qt.py
@@ -513,6 +513,7 @@ class QtBaseModule(ExtensionModule):
lrelease_kwargs = {'output': '@BASENAME@.qm',
'input': ts,
'install': kwargs.get('install', False),
+ 'install_tag': 'i18n',
'build_by_default': kwargs.get('build_by_default', False),
'command': cmd}
if install_dir is not None:
diff --git a/test cases/unit/98 install tag/bar-custom.txt b/test cases/unit/98 install tag/bar-custom.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test cases/unit/98 install tag/bar-custom.txt
diff --git a/test cases/unit/98 install tag/bar-devel.h b/test cases/unit/98 install tag/bar-devel.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test cases/unit/98 install tag/bar-devel.h
diff --git a/test cases/unit/98 install tag/bar-notag.txt b/test cases/unit/98 install tag/bar-notag.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test cases/unit/98 install tag/bar-notag.txt
diff --git a/test cases/unit/98 install tag/foo.in b/test cases/unit/98 install tag/foo.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test cases/unit/98 install tag/foo.in
diff --git a/test cases/unit/98 install tag/foo1-devel.h b/test cases/unit/98 install tag/foo1-devel.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test cases/unit/98 install tag/foo1-devel.h
diff --git a/test cases/unit/98 install tag/lib.c b/test cases/unit/98 install tag/lib.c
new file mode 100644
index 0000000..2ea9c7d
--- /dev/null
+++ b/test cases/unit/98 install tag/lib.c
@@ -0,0 +1,9 @@
+#if defined _WIN32 || defined __CYGWIN__
+#define DLL_PUBLIC __declspec(dllexport)
+#else
+#define DLL_PUBLIC
+#endif
+
+int DLL_PUBLIC foo(void) {
+ return 0;
+}
diff --git a/test cases/unit/98 install tag/main.c b/test cases/unit/98 install tag/main.c
new file mode 100644
index 0000000..0fb4389
--- /dev/null
+++ b/test cases/unit/98 install tag/main.c
@@ -0,0 +1,3 @@
+int main(int argc, char *argv[]) {
+ return 0;
+}
diff --git a/test cases/unit/98 install tag/meson.build b/test cases/unit/98 install tag/meson.build
new file mode 100644
index 0000000..ad1692a
--- /dev/null
+++ b/test cases/unit/98 install tag/meson.build
@@ -0,0 +1,73 @@
+project('install tag', 'c')
+
+# Those files should not be tagged
+configure_file(input: 'foo.in', output: 'foo-notag.h',
+ configuration: {'foo': 'bar'},
+ install_dir: get_option('datadir'),
+ install: true,
+)
+install_data('bar-notag.txt',
+ install_dir: get_option('datadir')
+)
+custom_target('ct1',
+ output: ['out1-notag.txt', 'out2-notag.txt'],
+ command: ['script.py', '@OUTPUT@'],
+ install_dir: get_option('datadir'),
+ install: true,
+)
+
+# Those files should be tagged as devel
+install_headers('foo1-devel.h')
+install_data('bar-devel.h',
+ install_dir: get_option('includedir'),
+)
+configure_file(input: 'foo.in', output: 'foo2-devel.h',
+ configuration: {'foo': 'bar'},
+ install_dir: get_option('includedir'),
+ install: true,
+)
+static_library('static', 'lib.c',
+ install: true,
+)
+
+# Those files should have 'runtime' tag
+executable('app', 'main.c',
+ install: true,
+)
+shared_library('shared', 'lib.c',
+ install: true,
+)
+both_libraries('both', 'lib.c',
+ install: true,
+)
+
+# Those files should have custom tag
+install_data('bar-custom.txt',
+ install_dir: get_option('datadir'),
+ install_tag: 'custom')
+configure_file(input: 'foo.in', output: 'foo-custom.h',
+ configuration: {'foo': 'bar'},
+ install_dir: get_option('datadir'),
+ install_tag: 'custom',
+ install: true,
+)
+both_libraries('bothcustom', 'lib.c',
+ install_tag: 'custom',
+ install: true,
+)
+custom_target('ct2',
+ output: ['out1-custom.txt', 'out2-custom.txt'],
+ command: ['script.py', '@OUTPUT@'],
+ install_dir: get_option('datadir'),
+ install_tag: 'custom',
+ install: true,
+)
+
+# First is custom, 2nd is devel, 3rd has no tag
+custom_target('ct3',
+ output: ['out3-custom.txt', 'out-devel.h', 'out3-notag.txt'],
+ command: ['script.py', '@OUTPUT@'],
+ install_dir: [get_option('datadir'), get_option('includedir'), get_option('datadir')],
+ install_tag: ['custom', 'devel', false],
+ install: true,
+)
diff --git a/test cases/unit/98 install tag/script.py b/test cases/unit/98 install tag/script.py
new file mode 100644
index 0000000..c5f3be9
--- /dev/null
+++ b/test cases/unit/98 install tag/script.py
@@ -0,0 +1,7 @@
+#!/usr/bin/env python3
+
+import sys
+
+for f in sys.argv[1:]:
+ with open(f, 'w') as f:
+ pass
diff --git a/unittests/allplatformstests.py b/unittests/allplatformstests.py
index a97ba63..32e084d 100644
--- a/unittests/allplatformstests.py
+++ b/unittests/allplatformstests.py
@@ -476,6 +476,13 @@ class AllPlatformTests(BasePlatformTests):
self.assertPathListEqual(intro[2]['install_filename'], ['/usr/include/first.h', None])
self.assertPathListEqual(intro[3]['install_filename'], [None, '/usr/bin/second.sh'])
+ def read_install_logs(self):
+ # Find logged files and directories
+ with Path(self.builddir, 'meson-logs', 'install-log.txt').open(encoding='utf-8') as f:
+ return list(map(lambda l: Path(l.strip()),
+ filter(lambda l: not l.startswith('#'),
+ f.readlines())))
+
def test_install_log_content(self):
'''
Tests that the install-log.txt is consistent with the installed files and directories.
@@ -490,13 +497,7 @@ class AllPlatformTests(BasePlatformTests):
expected = {installpath: 0}
for name in installpath.rglob('*'):
expected[name] = 0
- def read_logs():
- # Find logged files and directories
- with Path(self.builddir, 'meson-logs', 'install-log.txt').open(encoding='utf-8') as f:
- return list(map(lambda l: Path(l.strip()),
- filter(lambda l: not l.startswith('#'),
- f.readlines())))
- logged = read_logs()
+ logged = self.read_install_logs()
for name in logged:
self.assertTrue(name in expected, f'Log contains extra entry {name}')
expected[name] += 1
@@ -509,14 +510,14 @@ class AllPlatformTests(BasePlatformTests):
# actually installed
windows_proof_rmtree(self.installdir)
self._run(self.meson_command + ['install', '--dry-run', '--destdir', self.installdir], workdir=self.builddir)
- self.assertEqual(logged, read_logs())
+ self.assertEqual(logged, self.read_install_logs())
self.assertFalse(os.path.exists(self.installdir))
# If destdir is relative to build directory it should install
# exactly the same files.
rel_installpath = os.path.relpath(self.installdir, self.builddir)
self._run(self.meson_command + ['install', '--dry-run', '--destdir', rel_installpath, '-C', self.builddir])
- self.assertEqual(logged, read_logs())
+ self.assertEqual(logged, self.read_install_logs())
def test_uninstall(self):
exename = os.path.join(self.installdir, 'usr/bin/prog' + exe_suffix)
@@ -3756,3 +3757,109 @@ class AllPlatformTests(BasePlatformTests):
cc = detect_compiler_for(env, 'c', MachineChoice.HOST)
link_args = env.coredata.get_external_link_args(cc.for_machine, cc.language)
self.assertEqual(sorted(link_args), sorted(['-flto']))
+
+ def test_install_tag(self) -> None:
+ testdir = os.path.join(self.unit_test_dir, '98 install tag')
+ self.init(testdir)
+ self.build()
+
+ env = get_fake_env(testdir, self.builddir, self.prefix)
+ cc = detect_c_compiler(env, MachineChoice.HOST)
+
+ def shared_lib_name(name):
+ if cc.get_id() in {'msvc', 'clang-cl'}:
+ return f'bin/{name}.dll'
+ elif is_windows():
+ return f'bin/lib{name}.dll'
+ elif is_cygwin():
+ return f'bin/cyg{name}.dll'
+ elif is_osx():
+ return f'lib/lib{name}.dylib'
+ return f'lib/lib{name}.so'
+
+ def exe_name(name):
+ if is_windows() or is_cygwin():
+ return f'{name}.exe'
+ return name
+
+ installpath = Path(self.installdir)
+
+ expected_common = {
+ installpath,
+ Path(installpath, 'usr'),
+ }
+
+ expected_devel = expected_common | {
+ Path(installpath, 'usr/include'),
+ Path(installpath, 'usr/include/foo1-devel.h'),
+ Path(installpath, 'usr/include/bar-devel.h'),
+ Path(installpath, 'usr/include/foo2-devel.h'),
+ Path(installpath, 'usr/include/out-devel.h'),
+ Path(installpath, 'usr/lib'),
+ Path(installpath, 'usr/lib/libstatic.a'),
+ Path(installpath, 'usr/lib/libboth.a'),
+ }
+
+ if cc.get_id() in {'msvc', 'clang-cl'}:
+ expected_devel |= {
+ Path(installpath, 'usr/bin'),
+ Path(installpath, 'usr/bin/app.pdb'),
+ Path(installpath, 'usr/bin/both.pdb'),
+ Path(installpath, 'usr/bin/bothcustom.pdb'),
+ Path(installpath, 'usr/bin/shared.pdb'),
+ Path(installpath, 'usr/lib/both.lib'),
+ Path(installpath, 'usr/lib/bothcustom.lib'),
+ Path(installpath, 'usr/lib/shared.lib'),
+ }
+ elif is_windows() or is_cygwin():
+ expected_devel |= {
+ Path(installpath, 'usr/lib/libboth.dll.a'),
+ Path(installpath, 'usr/lib/libshared.dll.a'),
+ Path(installpath, 'usr/lib/libbothcustom.dll.a'),
+ }
+
+ expected_runtime = expected_common | {
+ Path(installpath, 'usr/bin'),
+ Path(installpath, 'usr/bin/' + exe_name('app')),
+ Path(installpath, 'usr/' + shared_lib_name('shared')),
+ Path(installpath, 'usr/' + shared_lib_name('both')),
+ }
+
+ expected_custom = expected_common | {
+ Path(installpath, 'usr/share'),
+ Path(installpath, 'usr/share/bar-custom.txt'),
+ Path(installpath, 'usr/share/foo-custom.h'),
+ Path(installpath, 'usr/share/out1-custom.txt'),
+ Path(installpath, 'usr/share/out2-custom.txt'),
+ Path(installpath, 'usr/share/out3-custom.txt'),
+ Path(installpath, 'usr/lib'),
+ Path(installpath, 'usr/lib/libbothcustom.a'),
+ Path(installpath, 'usr/' + shared_lib_name('bothcustom')),
+ }
+
+ if is_windows() or is_cygwin():
+ expected_custom |= {Path(installpath, 'usr/bin')}
+ else:
+ expected_runtime |= {Path(installpath, 'usr/lib')}
+
+ expected_runtime_custom = expected_runtime | expected_custom
+
+ expected_all = expected_devel | expected_runtime | expected_custom | {
+ Path(installpath, 'usr/share/foo-notag.h'),
+ Path(installpath, 'usr/share/bar-notag.txt'),
+ Path(installpath, 'usr/share/out1-notag.txt'),
+ Path(installpath, 'usr/share/out2-notag.txt'),
+ Path(installpath, 'usr/share/out3-notag.txt'),
+ }
+
+ def do_install(tags=None):
+ extra_args = ['--tags', tags] if tags else []
+ self._run(self.meson_command + ['install', '--dry-run', '--destdir', self.installdir] + extra_args, workdir=self.builddir)
+ installed = self.read_install_logs()
+ return sorted(installed)
+
+ self.assertEqual(sorted(expected_devel), do_install('devel'))
+ self.assertEqual(sorted(expected_runtime), do_install('runtime'))
+ self.assertEqual(sorted(expected_custom), do_install('custom'))
+ self.assertEqual(sorted(expected_runtime_custom), do_install('runtime,custom'))
+ self.assertEqual(sorted(expected_all), do_install())