aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/os_comp.yml1
-rw-r--r--docs/markdown/Builtin-options.md46
-rw-r--r--docs/markdown/Fs-module.md11
-rw-r--r--docs/markdown/Gnome-module.md17
-rw-r--r--docs/markdown/Reference-manual.md8
-rw-r--r--docs/markdown/Reference-tables.md1
-rw-r--r--docs/markdown/howtox.md17
-rw-r--r--docs/markdown/snippets/customtarget_env.md2
-rw-r--r--docs/markdown/snippets/fs_read.md40
-rw-r--r--docs/markdown/snippets/gnome_install_script.md9
-rw-r--r--docs/markdown/snippets/install_dry_run.md4
-rw-r--r--docs/markdown/snippets/lto_mode.md5
-rw-r--r--docs/markdown/snippets/lto_threads.md7
-rw-r--r--docs/markdown/snippets/meson_test_depends.md2
-rw-r--r--docs/markdown/snippets/unstable-rust-module.md2
-rw-r--r--docs/markdown/snippets/vala_unity_builds_disabled.md7
-rw-r--r--mesonbuild/backend/backends.py58
-rw-r--r--mesonbuild/backend/ninjabackend.py127
-rw-r--r--mesonbuild/backend/vs2010backend.py38
-rw-r--r--mesonbuild/backend/xcodebackend.py12
-rw-r--r--mesonbuild/build.py72
-rw-r--r--mesonbuild/compilers/compilers.py24
-rw-r--r--mesonbuild/compilers/mixins/clang.py21
-rw-r--r--mesonbuild/compilers/mixins/gnu.py17
-rw-r--r--mesonbuild/dependencies/cuda.py2
-rw-r--r--mesonbuild/envconfig.py2
-rw-r--r--mesonbuild/environment.py8
-rw-r--r--mesonbuild/interpreter.py23
-rw-r--r--mesonbuild/interpreterbase.py126
-rw-r--r--mesonbuild/minstall.py124
-rw-r--r--mesonbuild/modules/fs.py65
-rw-r--r--mesonbuild/modules/gnome.py93
-rw-r--r--mesonbuild/modules/i18n.py6
-rw-r--r--mesonbuild/modules/unstable_cuda.py2
-rw-r--r--mesonbuild/modules/unstable_rust.py15
-rw-r--r--mesonbuild/scripts/commandrunner.py84
-rw-r--r--mesonbuild/scripts/meson_exe.py11
-rwxr-xr-xrun_unittests.py298
-rw-r--r--test cases/cmake/15 object library advanced/meson.build4
-rw-r--r--test cases/cmake/4 code gen/meson.build4
-rw-r--r--test cases/cmake/8 custom command/meson.build4
-rw-r--r--test cases/common/241 get_file_contents/.gitattributes1
-rw-r--r--test cases/common/241 get_file_contents/VERSION1
-rw-r--r--test cases/common/241 get_file_contents/meson.build21
-rw-r--r--test cases/common/241 get_file_contents/other/meson.build3
-rw-r--r--test cases/common/241 get_file_contents/utf-16-textbin0 -> 150 bytes
-rw-r--r--test cases/common/52 run target/check-env.py9
-rw-r--r--test cases/common/52 run target/meson.build6
48 files changed, 1058 insertions, 402 deletions
diff --git a/.github/workflows/os_comp.yml b/.github/workflows/os_comp.yml
index 3cdcccd..5b49142 100644
--- a/.github/workflows/os_comp.yml
+++ b/.github/workflows/os_comp.yml
@@ -21,6 +21,7 @@ on:
- "run_unittests.py"
jobs:
+ arch:
name: ${{ matrix.cfg.name }}
runs-on: ubuntu-latest
strategy:
diff --git a/docs/markdown/Builtin-options.md b/docs/markdown/Builtin-options.md
index ef327e3..2d7c01c 100644
--- a/docs/markdown/Builtin-options.md
+++ b/docs/markdown/Builtin-options.md
@@ -119,30 +119,36 @@ no options.
The following options are available. Note that they may not be
available on all platforms or with all compilers:
-| Option | Default value | Possible values | Description |
-| ----------- | ------------- | --------------- | ----------- |
-| b_asneeded | true | true, false | Use -Wl,--as-needed when linking |
-| b_bitcode | false | true, false | Embed Apple bitcode, see below |
-| b_colorout | always | auto, always, never | Use colored output |
-| b_coverage | false | true, false | Enable coverage tracking |
-| b_lundef | true | true, false | Don't allow undefined symbols when linking |
-| b_lto | false | true, false | Use link time optimization |
-| b_ndebug | false | true, false, if-release | Disable asserts |
-| b_pch | true | true, false | Use precompiled headers |
-| b_pgo | off | off, generate, use | Use profile guided optimization |
-| b_sanitize | none | see below | Code sanitizer to use |
-| b_staticpic | true | true, false | Build static libraries as position independent |
-| b_pie | false | true, false | Build position-independent executables (since 0.49.0)|
-| b_vscrt | from_buildtype| none, md, mdd, mt, mtd, from_buildtype, static_from_buildtype | VS runtime library to use (since 0.48.0) (static_from_buildtype since 0.56.0) |
+| Option | Default value | Possible values | Description |
+|---------------|----------------|------------------------------------------------------------------|-------------------------------------------------------------------------------|
+| b_asneeded | true | true, false | Use -Wl,--as-needed when linking |
+| b_bitcode | false | true, false | Embed Apple bitcode, see below |
+| b_colorout | always | auto, always, never | Use colored output |
+| b_coverage | false | true, false | Enable coverage tracking |
+| b_lundef | true | true, false | Don't allow undefined symbols when linking |
+| b_lto | false | true, false | Use link time optimization |
+| b_lto_threads | 0 | Any integer* | Use multiple threads for lto. *(Added in 0.57.0)* |
+| b_lto_mode | default | default, thin | Select between lto modes, thin and default. *(Added in 0.57.0)* |
+| b_ndebug | false | true, false, if-release | Disable asserts |
+| b_pch | true | true, false | Use precompiled headers |
+| b_pgo | off | off, generate, use | Use profile guided optimization |
+| b_sanitize | none | see below | Code sanitizer to use |
+| b_staticpic | true | true, false | Build static libraries as position independent |
+| b_pie | false | true, false | Build position-independent executables (since 0.49.0) |
+| b_vscrt | from_buildtype | none, md, mdd, mt, mtd, from_buildtype, static_from_buildtype | VS runtime library to use (since 0.48.0) (static_from_buildtype since 0.56.0) |
The value of `b_sanitize` can be one of: `none`, `address`, `thread`,
`undefined`, `memory`, `address,undefined`.
-<a name="b_vscrt-from_buildtype"></a> The default value of `b_vscrt`
-is `from_buildtype`. The following table is used internally to pick
-the CRT compiler arguments for `from_buildtype` or
-`static_from_buildtype` *(since 0.56)* based on the value of the
-`buildtype` option:
+* < 0 means disable, == 0 means automatic selection, > 0 sets a specific number to use
+
+LLVM supports `thin` lto, for more discussion see [LLVM's documentation](https://clang.llvm.org/docs/ThinLTO.html)
+
+<a name="b_vscrt-from_buildtype"></a>
+The default value of `b_vscrt` is `from_buildtype`. The following table is
+used internally to pick the CRT compiler arguments for `from_buildtype` or
+`static_from_buildtype` *(since 0.56)* based on the value of the `buildtype`
+option:
| buildtype | from_buildtype | static_from_buildtype |
| -------- | -------------- | --------------------- |
diff --git a/docs/markdown/Fs-module.md b/docs/markdown/Fs-module.md
index d4945e9..df9f305 100644
--- a/docs/markdown/Fs-module.md
+++ b/docs/markdown/Fs-module.md
@@ -199,3 +199,14 @@ suffix
fs.stem('foo/bar/baz.dll') # baz
fs.stem('foo/bar/baz.dll.a') # baz.dll
```
+
+### read
+- `read(path, encoding: 'utf-8')` *(since 0.57.0)*:
+ return a [string](Syntax.md#strings) with the contents of the given `path`.
+ If the `encoding` keyword argument is not specified, the file specified by
+ `path` is assumed to be utf-8 encoded. Binary files are not supported. The
+ provided paths should be relative to the current `meson.current_source_dir()`
+ or an absolute path outside the build directory is accepted. If the file
+ specified by `path` changes, this will trigger Meson to reconfigure the
+ project. If the file specified by `path` is a `files()` object it
+ cannot refer to a built file.
diff --git a/docs/markdown/Gnome-module.md b/docs/markdown/Gnome-module.md
index fd58d51..4088061 100644
--- a/docs/markdown/Gnome-module.md
+++ b/docs/markdown/Gnome-module.md
@@ -357,3 +357,20 @@ Takes as argument a module name and returns the path where that
module's HTML files will be installed. Usually used with
`install_data` to install extra files, such as images, to the output
directory.
+
+### gnome.post_install()
+
+*Since 0.57.0*
+
+Post-install update of various system wide caches. Each script will be executed
+only once even if `gnome.post_install()` is called multiple times from multiple
+subprojects. If `DESTDIR` is specified during installation all scripts will be
+skipped.
+
+It takes the following keyword arguments:
+- `glib_compile_schemas`: If set to `true`, update `gschemas.compiled` file in
+ `<prefix>/<datadir>/glib-2.0/schemas`.
+- `gio_querymodules`: List of directories relative to `prefix` where
+ `giomodule.cache` file will be updated.
+- `gtk_update_icon_cache`: If set to `true`, update `icon-theme.cache` file in
+ `<prefix>/<datadir>/icons/hicolor`.
diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md
index d3a4f01..f12f695 100644
--- a/docs/markdown/Reference-manual.md
+++ b/docs/markdown/Reference-manual.md
@@ -1507,6 +1507,10 @@ and subdirectory the target was defined in, respectively.
- `depends` is a list of targets that this target depends on but which
are not listed in the command array (because, for example, the
script does file globbing internally)
+- `env` *(since 0.57.0)*: environment variables to set, such as
+ `{'NAME1': 'value1', 'NAME2': 'value2'}` or `['NAME1=value1', 'NAME2=value2']`,
+ or an [`environment()` object](#environment-object) which allows more
+ sophisticated environment juggling.
### set_variable()
@@ -1839,14 +1843,14 @@ the following methods.
script file can not be found in the staging directory, it is a hard
error. This command can only invoked from the main project, calling
it from a subproject is a hard error. *(since 0.49.0)* Accepts multiple arguments
- for the fscript. *(since 0.54.0)* The `MESON_SOURCE_ROOT` and `MESON_BUILD_ROOT`
+ for the script. *(since 0.54.0)* The `MESON_SOURCE_ROOT` and `MESON_BUILD_ROOT`
environment variables are set when dist scripts are run.
*(since 0.55.0)* The output of `configure_file`, `files`, and `find_program`
as well as strings.
*(since 0.57.0)* `file` objects and the output of `configure_file` may be
- *used as the `script_name` parameter.
+ used as the `script_name` parameter.
- `add_install_script(script_name, arg1, arg2, ...)`: causes the script
given as an argument to be run during the install step, this script
diff --git a/docs/markdown/Reference-tables.md b/docs/markdown/Reference-tables.md
index 806ba76..256aca4 100644
--- a/docs/markdown/Reference-tables.md
+++ b/docs/markdown/Reference-tables.md
@@ -89,6 +89,7 @@ set in the cross file.
| dspic | 16 bit Microchip dsPIC |
| e2k | MCST Elbrus processor |
| ia64 | Itanium processor |
+| loongarch64 | 64 bit Loongson processor|
| m68k | Motorola 68000 processor |
| microblaze | MicroBlaze processor |
| mips | 32 bit MIPS processor |
diff --git a/docs/markdown/howtox.md b/docs/markdown/howtox.md
index f05f3e8..8c8c0c0 100644
--- a/docs/markdown/howtox.md
+++ b/docs/markdown/howtox.md
@@ -139,6 +139,23 @@ cdata.set('SOMETHING', txt)
configure_file(...)
```
+## Generate configuration data from files
+
+`The [fs module](#Fs-modules) offers the `read` function` which enables adding
+the contents of arbitrary files to configuration data (among other uses):
+
+```meson
+fs = import('fs')
+cdata = configuration_data()
+copyright = fs.read('LICENSE')
+cdata.set('COPYRIGHT', copyright)
+if build_machine.system() == 'linux'
+ os_release = fs.read('/etc/os-release')
+ cdata.set('LINUX_BUILDER', os_release)
+endif
+configure_file(...)
+```
+
## Generate a runnable script with `configure_file`
`configure_file` preserves metadata so if your template file has
diff --git a/docs/markdown/snippets/customtarget_env.md b/docs/markdown/snippets/customtarget_env.md
index 69bfc0d..f2d651b 100644
--- a/docs/markdown/snippets/customtarget_env.md
+++ b/docs/markdown/snippets/customtarget_env.md
@@ -1,4 +1,4 @@
-## `custom_target()` now accepts an `env` keyword argument
+## `custom_target()` and `run_target()` now accepts an `env` keyword argument
Environment variables can now be passed to the `custom_target()` command.
diff --git a/docs/markdown/snippets/fs_read.md b/docs/markdown/snippets/fs_read.md
new file mode 100644
index 0000000..05c215a
--- /dev/null
+++ b/docs/markdown/snippets/fs_read.md
@@ -0,0 +1,40 @@
+## Support for reading files at configuration time with the `fs` module
+
+Reading text files during configuration is now supported. This can be done at
+any time after `project` has been called
+
+```meson
+project(myproject', 'c')
+license_text = run_command(
+ find_program('python3'), '-c', 'print(open("COPYING").read())'
+).stdout().strip()
+about_header = configuration_data()
+about_header.add('COPYRIGHT', license_text)
+about_header.add('ABOUT_STRING', meson.project_name())
+...
+```
+
+There are several problems with the above approach:
+1. It's ugly and confusing
+2. If `COPYING` changes after configuration, Meson won't correctly rebuild when
+ configuration data is based on the data in COPYING
+3. It has extra overhead
+
+`fs.read` replaces the above idiom thus:
+```meson
+project(myproject', 'c')
+fs = import('fs')
+license_text = fs.read('COPYING').strip()
+about_header = configuration_data()
+about_header.add('COPYRIGHT', license_text)
+about_header.add('ABOUT_STRING', meson.project_name())
+...
+```
+
+They are not equivalent, though. Files read with `fs.read` create a
+configuration dependency on the file, and so if the `COPYING` file is modified,
+Meson will automatically reconfigure, guaranteeing the build is consistent. It
+can be used for any properly encoded text files. It supports specification of
+non utf-8 encodings too, so if you're stuck with text files in a different
+encoding, it can be passed as an argument. See the [`meson`
+object](Reference-manual.md#meson-object) documentation for details.
diff --git a/docs/markdown/snippets/gnome_install_script.md b/docs/markdown/snippets/gnome_install_script.md
new file mode 100644
index 0000000..03fcfe4
--- /dev/null
+++ b/docs/markdown/snippets/gnome_install_script.md
@@ -0,0 +1,9 @@
+## `gnome.post_install()`
+
+Post-install update of various system wide caches. Each script will be executed
+only once even if `gnome.post_install()` is called multiple times from multiple
+subprojects. If `DESTDIR` is specified during installation all scripts will be
+skipped.
+
+Currently supports `glib-compile-schemas`, `gio-querymodules`, and
+`gtk-update-icon-cache`.
diff --git a/docs/markdown/snippets/install_dry_run.md b/docs/markdown/snippets/install_dry_run.md
new file mode 100644
index 0000000..8106a06
--- /dev/null
+++ b/docs/markdown/snippets/install_dry_run.md
@@ -0,0 +1,4 @@
+## meson install --dry-run
+
+New option to meson install command that does not actually install files, but
+only print messages.
diff --git a/docs/markdown/snippets/lto_mode.md b/docs/markdown/snippets/lto_mode.md
new file mode 100644
index 0000000..c1df066
--- /dev/null
+++ b/docs/markdown/snippets/lto_mode.md
@@ -0,0 +1,5 @@
+## Support added for LLVM's thinLTO
+
+A new `b_lto_mode` option has been added, which may be set to `default` or
+`thin`. Thin only works for clang, and only with gnu gold, lld variants, or
+ld64.
diff --git a/docs/markdown/snippets/lto_threads.md b/docs/markdown/snippets/lto_threads.md
new file mode 100644
index 0000000..77c8047
--- /dev/null
+++ b/docs/markdown/snippets/lto_threads.md
@@ -0,0 +1,7 @@
+## Knob to control LTO thread
+
+Both the gnu linker and lld support using threads for speeding up LTO, meson
+now provides a knob for this: `-Db_lto_threads`. Currently this is only
+supported for clang and gcc. Any positive integer is supported, `0` means
+`auto`. If the compiler or linker implements it's on `auto` we use that,
+otherwise the number of threads on the machine is used.
diff --git a/docs/markdown/snippets/meson_test_depends.md b/docs/markdown/snippets/meson_test_depends.md
index 905c59f..46ec328 100644
--- a/docs/markdown/snippets/meson_test_depends.md
+++ b/docs/markdown/snippets/meson_test_depends.md
@@ -12,5 +12,5 @@ using commands like the following:
This would find the broken commit automatically while at each step
rebuilding only those pieces of code needed to run the test.
-However, this change could cause failures when upgrading to 0.57, ifthe
+However, this change could cause failures when upgrading to 0.57, if the
dependencies are not specified correctly in `meson.build`.
diff --git a/docs/markdown/snippets/unstable-rust-module.md b/docs/markdown/snippets/unstable-rust-module.md
index 47790e3..e594ecf 100644
--- a/docs/markdown/snippets/unstable-rust-module.md
+++ b/docs/markdown/snippets/unstable-rust-module.md
@@ -1,4 +1,4 @@
-## Untable Rust module
+## Unstable Rust module
A new unstable module has been added to make using Rust with Meson easier.
Currently it adds a single function to ease defining Rust tests.
diff --git a/docs/markdown/snippets/vala_unity_builds_disabled.md b/docs/markdown/snippets/vala_unity_builds_disabled.md
new file mode 100644
index 0000000..80e6523
--- /dev/null
+++ b/docs/markdown/snippets/vala_unity_builds_disabled.md
@@ -0,0 +1,7 @@
+## Unity build with Vala disabled
+
+The approach that meson has used for Vala unity builds is incorrect, we
+combine the generated C files like we would any other C file. This is very
+fragile however, as the Vala compiler generates helper functions and macros
+which work fine when each file is a separate translation unit, but fail when
+they are combined.
diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py
index badc2d0..e19afca 100644
--- a/mesonbuild/backend/backends.py
+++ b/mesonbuild/backend/backends.py
@@ -21,8 +21,6 @@ import json
import os
import pickle
import re
-import shlex
-import textwrap
import typing as T
import hashlib
import copy
@@ -34,7 +32,7 @@ from .. import mlog
from ..compilers import LANGUAGES_USING_LDFLAGS
from ..mesonlib import (
File, MachineChoice, MesonException, OptionType, OrderedSet, OptionOverrideProxy,
- classify_unity_sources, unholder, OptionKey
+ classify_unity_sources, unholder, OptionKey, join_args
)
if T.TYPE_CHECKING:
@@ -49,7 +47,7 @@ if T.TYPE_CHECKING:
# Languages that can mix with C or C++ but don't support unity builds yet
# because the syntax we use for unity builds is specific to C/++/ObjC/++.
# Assembly files cannot be unitified and neither can LLVM IR files
-LANGS_CANT_UNITY = ('d', 'fortran')
+LANGS_CANT_UNITY = ('d', 'fortran', 'vala')
class TestProtocol(enum.Enum):
@@ -138,6 +136,7 @@ class ExecutableSerialisation:
self.capture = capture
self.pickled = False
self.skip_if_destdir = False
+ self.verbose = False
class TestSerialisation:
def __init__(self, name: str, project: str, suite: str, fname: T.List[str],
@@ -248,6 +247,17 @@ class Backend:
return self.environment.coredata.validate_option_value(option_name, override)
return self.environment.coredata.get_option(option_name.evolve(subproject=target.subproject))
+ def get_source_dir_include_args(self, target, compiler):
+ curdir = target.get_subdir()
+ tmppath = os.path.normpath(os.path.join(self.build_to_src, curdir))
+ return compiler.get_include_args(tmppath, False)
+
+ def get_build_dir_include_args(self, target, compiler):
+ curdir = target.get_subdir()
+ if curdir == '':
+ curdir = '.'
+ return compiler.get_include_args(curdir, False)
+
def get_target_filename_for_linking(self, target):
# On some platforms (msvc for instance), the file that is used for
# dynamic linking is not the same as the dynamic library itself. This
@@ -421,12 +431,14 @@ class Backend:
def as_meson_exe_cmdline(self, tname, exe, cmd_args, workdir=None,
extra_bdeps=None, capture=None, force_serialize=False,
- env: T.Optional[build.EnvironmentVariables] = None):
+ env: T.Optional[build.EnvironmentVariables] = None,
+ verbose: bool = False):
'''
Serialize an executable for running with a generator or a custom target
'''
cmd = [exe] + cmd_args
es = self.get_executable_serialisation(cmd, workdir, extra_bdeps, capture, env)
+ es.verbose = verbose
reasons = []
if es.extra_paths:
reasons.append('to set PATH')
@@ -976,18 +988,8 @@ class Backend:
if delta > 0.001:
raise MesonException('Clock skew detected. File {} has a time stamp {:.4f}s in the future.'.format(absf, delta))
- def build_target_to_cmd_array(self, bt, check_cross):
+ def build_target_to_cmd_array(self, bt):
if isinstance(bt, build.BuildTarget):
- if check_cross and isinstance(bt, build.Executable) and bt.for_machine is not MachineChoice.BUILD:
- if (self.environment.is_cross_build() and
- self.environment.exe_wrapper is None and
- self.environment.need_exe_wrapper()):
- s = textwrap.dedent('''
- Cannot use target {} as a generator because it is built for the
- host machine and no exe wrapper is defined or needs_exe_wrapper is
- true. You might want to set `native: true` instead to build it for
- the build machine.'''.format(bt.name))
- raise MesonException(s)
arr = [os.path.join(self.environment.get_build_dir(), self.get_target_filename(bt))]
else:
arr = bt.get_command()
@@ -1118,11 +1120,9 @@ class Backend:
inputs = self.get_custom_target_sources(target)
# Evaluate the command list
cmd = []
- index = -1
for i in target.command:
- index += 1
if isinstance(i, build.BuildTarget):
- cmd += self.build_target_to_cmd_array(i, (index == 0))
+ cmd += self.build_target_to_cmd_array(i)
continue
elif isinstance(i, build.CustomTarget):
# GIR scanner will attempt to execute this binary but
@@ -1135,10 +1135,7 @@ class Backend:
i = os.path.join(self.environment.get_build_dir(), i)
# FIXME: str types are blindly added ignoring 'target.absolute_paths'
# because we can't know if they refer to a file or just a string
- elif not isinstance(i, str):
- err_msg = 'Argument {0} is of unknown type {1}'
- raise RuntimeError(err_msg.format(str(i), str(type(i))))
- else:
+ elif isinstance(i, str):
if '@SOURCE_ROOT@' in i:
i = i.replace('@SOURCE_ROOT@', source_root)
if '@BUILD_ROOT@' in i:
@@ -1168,6 +1165,9 @@ class Backend:
else:
lead_dir = self.environment.get_build_dir()
i = i.replace(source, os.path.join(lead_dir, outdir))
+ else:
+ err_msg = 'Argument {0} is of unknown type {1}'
+ raise RuntimeError(err_msg.format(str(i), str(type(i))))
cmd.append(i)
# Substitute the rest of the template strings
values = mesonlib.get_filenames_templates_dict(inputs, outputs)
@@ -1193,11 +1193,21 @@ class Backend:
cmd = [i.replace('\\', '/') for i in cmd]
return inputs, outputs, cmd
+ def get_run_target_env(self, target: build.RunTarget) -> build.EnvironmentVariables:
+ env = target.env if target.env else build.EnvironmentVariables()
+ introspect_cmd = join_args(self.environment.get_build_command() + ['introspect'])
+ env.add_var(env.set, 'MESON_SOURCE_ROOT', [self.environment.get_source_dir()], {})
+ env.add_var(env.set, 'MESON_BUILD_ROOT', [self.environment.get_build_dir()], {})
+ env.add_var(env.set, 'MESON_SUBDIR', [target.subdir], {})
+ env.add_var(env.set, 'MESONINTROSPECT', [introspect_cmd], {})
+ return env
+
def run_postconf_scripts(self) -> None:
from ..scripts.meson_exe import run_exe
+ introspect_cmd = join_args(self.environment.get_build_command() + ['introspect'])
env = {'MESON_SOURCE_ROOT': self.environment.get_source_dir(),
'MESON_BUILD_ROOT': self.environment.get_build_dir(),
- 'MESONINTROSPECT': ' '.join([shlex.quote(x) for x in self.environment.get_build_command() + ['introspect']]),
+ 'MESONINTROSPECT': introspect_cmd,
}
for s in self.build.postconf_scripts:
diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py
index 36f1fd2..3eca3c0 100644
--- a/mesonbuild/backend/ninjabackend.py
+++ b/mesonbuild/backend/ninjabackend.py
@@ -28,7 +28,6 @@ from .. import modules
from .. import environment, mesonlib
from .. import build
from .. import mlog
-from .. import dependencies
from .. import compilers
from ..arglist import CompilerArgs
from ..compilers import (
@@ -812,25 +811,24 @@ int dummy;
# Generate compilation targets for C sources generated from Vala
# sources. This can be extended to other $LANG->C compilers later if
# necessary. This needs to be separate for at least Vala
+ #
+ # Do not try to unity-build the generated c files from vala, as these
+ # often contain duplicate symbols and will fail to compile properly
vala_generated_source_files = []
for src in vala_generated_sources:
dirpart, fnamepart = os.path.split(src)
raw_src = File(True, dirpart, fnamepart)
- if is_unity:
- unity_src.append(os.path.join(self.environment.get_build_dir(), src))
+ # Generated targets are ordered deps because the must exist
+ # before the sources compiling them are used. After the first
+ # compile we get precise dependency info from dep files.
+ # This should work in all cases. If it does not, then just
+ # move them from orderdeps to proper deps.
+ if self.environment.is_header(src):
header_deps.append(raw_src)
else:
- # Generated targets are ordered deps because the must exist
- # before the sources compiling them are used. After the first
- # compile we get precise dependency info from dep files.
- # This should work in all cases. If it does not, then just
- # move them from orderdeps to proper deps.
- if self.environment.is_header(src):
- header_deps.append(raw_src)
- else:
- # We gather all these and generate compile rules below
- # after `header_deps` (above) is fully generated
- vala_generated_source_files.append(raw_src)
+ # We gather all these and generate compile rules below
+ # after `header_deps` (above) is fully generated
+ vala_generated_source_files.append(raw_src)
for src in vala_generated_source_files:
# Passing 'vala' here signifies that we want the compile
# arguments to be specialized for C code generated by
@@ -975,7 +973,6 @@ int dummy;
elem.add_item('DEPFILE', rel_dfile)
if target.console:
elem.add_item('pool', 'console')
- cmd = self.replace_paths(target, cmd)
elem.add_item('COMMAND', cmd)
elem.add_item('description', desc.format(target.name, cmd_type))
self.add_build(elem)
@@ -989,65 +986,28 @@ int dummy;
return '{}{}'.format(subproject_prefix, target.name)
def generate_run_target(self, target):
- cmd = self.environment.get_build_command() + ['--internal', 'commandrunner']
- deps = self.unwrap_dep_list(target)
- arg_strings = []
- for i in target.args:
- if isinstance(i, str):
- arg_strings.append(i)
- elif isinstance(i, (build.BuildTarget, build.CustomTarget)):
- relfname = self.get_target_filename(i)
- arg_strings.append(os.path.join(self.environment.get_build_dir(), relfname))
- deps.append(relfname)
- elif isinstance(i, mesonlib.File):
- relfname = i.rel_to_builddir(self.build_to_src)
- arg_strings.append(os.path.join(self.environment.get_build_dir(), relfname))
- else:
- raise AssertionError('Unreachable code in generate_run_target: ' + str(i))
- cmd += [self.environment.get_source_dir(),
- self.environment.get_build_dir(),
- target.subdir] + self.environment.get_build_command()
- texe = target.command
- try:
- texe = texe.held_object
- except AttributeError:
- pass
- if isinstance(texe, build.Executable):
- abs_exe = os.path.join(self.environment.get_build_dir(), self.get_target_filename(texe))
- deps.append(self.get_target_filename(texe))
- if self.environment.is_cross_build():
- exe_wrap = self.environment.get_exe_wrapper()
- if exe_wrap:
- if not exe_wrap.found():
- msg = 'The exe_wrapper {!r} defined in the cross file is ' \
- 'needed by run target {!r}, but was not found. ' \
- 'Please check the command and/or add it to PATH.'
- raise MesonException(msg.format(exe_wrap.name, target.name))
- cmd += exe_wrap.get_command()
- cmd.append(abs_exe)
- elif isinstance(texe, dependencies.ExternalProgram):
- cmd += texe.get_command()
- elif isinstance(texe, build.CustomTarget):
- deps.append(self.get_target_filename(texe))
- cmd += [os.path.join(self.environment.get_build_dir(), self.get_target_filename(texe))]
- elif isinstance(texe, mesonlib.File):
- cmd.append(texe.absolute_path(self.environment.get_source_dir(), self.environment.get_build_dir()))
+ target_name = self.build_run_target_name(target)
+ if not target.command:
+ # This is an alias target, it has no command, it just depends on
+ # other targets.
+ elem = NinjaBuildElement(self.all_outputs, target_name, 'phony', [])
else:
- cmd.append(target.command)
- cmd += arg_strings
-
- if texe:
- target_name = 'meson-{}'.format(self.build_run_target_name(target))
- elem = NinjaBuildElement(self.all_outputs, target_name, 'CUSTOM_COMMAND', [])
- elem.add_item('COMMAND', cmd)
- elem.add_item('description', 'Running external command {}'.format(target.name))
+ target_env = self.get_run_target_env(target)
+ _, _, cmd = self.eval_custom_target_command(target)
+ desc = 'Running external command {}{}'
+ meson_exe_cmd, reason = self.as_meson_exe_cmdline(target_name, cmd[0], cmd[1:],
+ force_serialize=True, env=target_env,
+ verbose=True)
+ cmd_type = ' (wrapped by meson {})'.format(reason)
+ internal_target_name = 'meson-{}'.format(target_name)
+ elem = NinjaBuildElement(self.all_outputs, internal_target_name, 'CUSTOM_COMMAND', [])
+ elem.add_item('COMMAND', meson_exe_cmd)
+ elem.add_item('description', desc.format(target.name, cmd_type))
elem.add_item('pool', 'console')
# Alias that runs the target defined above with the name the user specified
- self.create_target_alias(target_name)
- else:
- target_name = self.build_run_target_name(target)
- elem = NinjaBuildElement(self.all_outputs, target_name, 'phony', [])
-
+ self.create_target_alias(internal_target_name)
+ deps = self.unwrap_dep_list(target)
+ deps += self.get_custom_target_depend_files(target)
elem.add_dep(deps)
self.add_build(elem)
self.processed_targets[target.get_id()] = True
@@ -1396,7 +1356,9 @@ int dummy;
break
return list(result)
- def split_vala_sources(self, t):
+ def split_vala_sources(self, t: build.Target) -> \
+ T.Tuple[T.MutableMapping[str, File], T.MutableMapping[str, File],
+ T.Tuple[T.MutableMapping[str, File], T.MutableMapping]]:
"""
Splits the target's sources into .vala, .gs, .vapi, and other sources.
Handles both pre-existing and generated sources.
@@ -1405,9 +1367,9 @@ int dummy;
the keys being the path to the file (relative to the build directory)
and the value being the object that generated or represents the file.
"""
- vala = OrderedDict()
- vapi = OrderedDict()
- others = OrderedDict()
+ vala: T.MutableMapping[str, File] = OrderedDict()
+ vapi: T.MutableMapping[str, File] = OrderedDict()
+ others: T.MutableMapping[str, File] = OrderedDict()
othersgen = OrderedDict()
# Split pre-existing sources
for s in t.get_sources():
@@ -1449,7 +1411,7 @@ int dummy;
srctype[f] = gensrc
return vala, vapi, (others, othersgen)
- def generate_vala_compile(self, target):
+ def generate_vala_compile(self, target: build.BuildTarget):
"""Vala is compiled into C. Set up all necessary build steps here."""
(vala_src, vapi_src, other_src) = self.split_vala_sources(target)
extra_dep_files = []
@@ -2104,7 +2066,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485'''))
generator = genlist.get_generator()
subdir = genlist.subdir
exe = generator.get_exe()
- exe_arr = self.build_target_to_cmd_array(exe, True)
+ exe_arr = self.build_target_to_cmd_array(exe)
infilelist = genlist.get_inputs()
outfilelist = genlist.get_outputs()
extra_dependencies = self.get_custom_target_depend_files(genlist)
@@ -2334,17 +2296,6 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485'''))
self.add_build(element)
return (rel_obj, rel_src)
- def get_source_dir_include_args(self, target, compiler):
- curdir = target.get_subdir()
- tmppath = os.path.normpath(os.path.join(self.build_to_src, curdir))
- return compiler.get_include_args(tmppath, False)
-
- def get_build_dir_include_args(self, target, compiler):
- curdir = target.get_subdir()
- if curdir == '':
- curdir = '.'
- return compiler.get_include_args(curdir, False)
-
@lru_cache(maxsize=None)
def get_normpath_target(self, source) -> str:
return os.path.normpath(source)
diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py
index c47fb4a..e94ab49 100644
--- a/mesonbuild/backend/vs2010backend.py
+++ b/mesonbuild/backend/vs2010backend.py
@@ -28,7 +28,7 @@ from .. import mlog
from .. import compilers
from ..interpreter import Interpreter
from ..mesonlib import (
- MesonException, File, python_command, replace_if_different, OptionKey,
+ MesonException, python_command, replace_if_different, OptionKey,
)
from ..environment import Environment, build_filename
@@ -121,7 +121,7 @@ class Vs2010Backend(backends.Backend):
infilelist = genlist.get_inputs()
outfilelist = genlist.get_outputs()
source_dir = os.path.join(down, self.build_to_src, genlist.subdir)
- exe_arr = self.build_target_to_cmd_array(exe, True)
+ exe_arr = self.build_target_to_cmd_array(exe)
idgroup = ET.SubElement(parent_node, 'ItemGroup')
for i in range(len(infilelist)):
if len(infilelist) == len(outfilelist):
@@ -257,9 +257,8 @@ class Vs2010Backend(backends.Backend):
for d in target.get_target_dependencies():
all_deps[d.get_id()] = d
elif isinstance(target, build.RunTarget):
- for d in [target.command] + target.args:
- if isinstance(d, (build.BuildTarget, build.CustomTarget)):
- all_deps[d.get_id()] = d
+ for d in target.get_dependencies():
+ all_deps[d.get_id()] = d
elif isinstance(target, build.BuildTarget):
for ldep in target.link_targets:
if isinstance(ldep, build.CustomTargetIndex):
@@ -534,27 +533,14 @@ class Vs2010Backend(backends.Backend):
# is probably a better way than running a this dummy command.
cmd_raw = python_command + ['-c', 'exit']
else:
- cmd_raw = [target.command] + target.args
- cmd = python_command + \
- [os.path.join(self.environment.get_script_dir(), 'commandrunner.py'),
- self.environment.get_source_dir(),
- self.environment.get_build_dir(),
- self.get_target_dir(target)] + self.environment.get_build_command()
- for i in cmd_raw:
- if isinstance(i, build.BuildTarget):
- cmd.append(os.path.join(self.environment.get_build_dir(), self.get_target_filename(i)))
- elif isinstance(i, dependencies.ExternalProgram):
- cmd += i.get_command()
- elif isinstance(i, File):
- relfname = i.rel_to_builddir(self.build_to_src)
- cmd.append(os.path.join(self.environment.get_build_dir(), relfname))
- elif isinstance(i, str):
- # Escape embedded quotes, because we quote the entire argument below.
- cmd.append(i.replace('"', '\\"'))
- else:
- cmd.append(i)
- cmd_templ = '''"%s" ''' * len(cmd)
- self.add_custom_build(root, 'run_target', cmd_templ % tuple(cmd))
+ _, _, cmd_raw = self.eval_custom_target_command(target)
+ depend_files = self.get_custom_target_depend_files(target)
+ target_env = self.get_run_target_env(target)
+ wrapper_cmd, _ = self.as_meson_exe_cmdline(target.name, cmd_raw[0], cmd_raw[1:],
+ force_serialize=True, env=target_env,
+ verbose=True)
+ self.add_custom_build(root, 'run_target', ' '.join(self.quote_arguments(wrapper_cmd)),
+ deps=depend_files)
ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.targets')
self.add_regen_dependency(root)
self.add_target_deps(root, target)
diff --git a/mesonbuild/backend/xcodebackend.py b/mesonbuild/backend/xcodebackend.py
index 09a40ef..7ee4e80 100644
--- a/mesonbuild/backend/xcodebackend.py
+++ b/mesonbuild/backend/xcodebackend.py
@@ -734,6 +734,9 @@ class XCodeBackend(backends.Backend):
else:
product_name = target.get_basename()
ldargs += target.link_args
+ linker, stdlib_args = self.determine_linker_and_stdlib_args(target)
+ ldargs += self.build.get_project_link_args(linker, target.subproject, target.for_machine)
+ ldargs += self.build.get_global_link_args(linker, target.for_machine)
cargs = []
for dep in target.get_external_deps():
cargs += dep.get_compile_args()
@@ -752,8 +755,13 @@ class XCodeBackend(backends.Backend):
targs = target.get_extra_args(lang)
args = pargs + gargs + targs
if args:
- langargs[langnamemap[lang]] = args
- langargs['C'] += cargs
+ langname = langnamemap[lang]
+ compiler = target.compilers.get(lang)
+ lang_cargs = cargs
+ if compiler and target.implicit_include_directories:
+ lang_cargs += self.get_build_dir_include_args(target, compiler)
+ langargs[langname] = args
+ langargs[langname] += lang_cargs
symroot = os.path.join(self.environment.get_build_dir(), target.subdir)
self.write_line('%s /* %s */ = {' % (valid, buildtype))
self.indent_level += 1
diff --git a/mesonbuild/build.py b/mesonbuild/build.py
index 5adbc51..160ee9a 100644
--- a/mesonbuild/build.py
+++ b/mesonbuild/build.py
@@ -29,7 +29,7 @@ from .mesonlib import (
File, MesonException, MachineChoice, PerMachine, OrderedSet, listify,
extract_as_list, typeslistify, stringlistify, classify_unity_sources,
get_filenames_templates_dict, substitute_values, has_path_sep, unholder,
- OptionKey,
+ OptionKey
)
from .compilers import (
Compiler, is_object, clink_langs, sort_clink, lang_suffixes,
@@ -2141,8 +2141,35 @@ class SharedModule(SharedLibrary):
def get_default_install_dir(self, environment):
return environment.get_shared_module_dir()
+class CommandBase:
+ def flatten_command(self, cmd):
+ cmd = unholder(listify(cmd))
+ final_cmd = []
+ for c in cmd:
+ if isinstance(c, str):
+ final_cmd.append(c)
+ elif isinstance(c, File):
+ self.depend_files.append(c)
+ final_cmd.append(c)
+ elif isinstance(c, dependencies.ExternalProgram):
+ if not c.found():
+ raise InvalidArguments('Tried to use not-found external program in "command"')
+ path = c.get_path()
+ if os.path.isabs(path):
+ # Can only add a dependency on an external program which we
+ # know the absolute path of
+ self.depend_files.append(File.from_absolute_file(path))
+ final_cmd += c.get_command()
+ elif isinstance(c, (BuildTarget, CustomTarget)):
+ self.dependencies.append(c)
+ final_cmd.append(c)
+ elif isinstance(c, list):
+ final_cmd += self.flatten_command(c)
+ else:
+ raise InvalidArguments('Argument {!r} in "command" is invalid'.format(c))
+ return final_cmd
-class CustomTarget(Target):
+class CustomTarget(Target, CommandBase):
known_kwargs = set([
'input',
'output',
@@ -2213,33 +2240,6 @@ class CustomTarget(Target):
bdeps.update(d.get_transitive_build_target_deps())
return bdeps
- def flatten_command(self, cmd):
- cmd = unholder(listify(cmd))
- final_cmd = []
- for c in cmd:
- if isinstance(c, str):
- final_cmd.append(c)
- elif isinstance(c, File):
- self.depend_files.append(c)
- final_cmd.append(c)
- elif isinstance(c, dependencies.ExternalProgram):
- if not c.found():
- raise InvalidArguments('Tried to use not-found external program in "command"')
- path = c.get_path()
- if os.path.isabs(path):
- # Can only add a dependency on an external program which we
- # know the absolute path of
- self.depend_files.append(File.from_absolute_file(path))
- final_cmd += c.get_command()
- elif isinstance(c, (BuildTarget, CustomTarget)):
- self.dependencies.append(c)
- final_cmd.append(c)
- elif isinstance(c, list):
- final_cmd += self.flatten_command(c)
- else:
- raise InvalidArguments('Argument {!r} in "command" is invalid'.format(c))
- return final_cmd
-
def process_kwargs(self, kwargs, backend):
self.process_kwargs_base(kwargs)
self.sources = unholder(extract_as_list(kwargs, 'input'))
@@ -2421,18 +2421,20 @@ class CustomTarget(Target):
for i in self.outputs:
yield CustomTargetIndex(self, i)
-class RunTarget(Target):
- def __init__(self, name, command, args, dependencies, subdir, subproject):
+class RunTarget(Target, CommandBase):
+ def __init__(self, name, command, dependencies, subdir, subproject, env=None):
self.typename = 'run'
# These don't produce output artifacts
super().__init__(name, subdir, subproject, False, MachineChoice.BUILD)
- self.command = command
- self.args = args
self.dependencies = dependencies
+ self.depend_files = []
+ self.command = self.flatten_command(command)
+ self.absolute_paths = False
+ self.env = env
def __repr__(self):
repr_str = "<{0} {1}: {2}>"
- return repr_str.format(self.__class__.__name__, self.get_id(), self.command)
+ return repr_str.format(self.__class__.__name__, self.get_id(), self.command[0])
def process_kwargs(self, kwargs):
return self.process_kwargs_base(kwargs)
@@ -2465,7 +2467,7 @@ class RunTarget(Target):
class AliasTarget(RunTarget):
def __init__(self, name, dependencies, subdir, subproject):
- super().__init__(name, '', [], dependencies, subdir, subproject)
+ super().__init__(name, [], dependencies, subdir, subproject)
class Jar(BuildTarget):
known_kwargs = known_jar_kwargs
diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py
index 0f83f4c..08db6d7 100644
--- a/mesonbuild/compilers/compilers.py
+++ b/mesonbuild/compilers/compilers.py
@@ -268,6 +268,11 @@ clike_debug_args = {False: [],
base_options: 'KeyedOptionDictType' = {
OptionKey('b_pch'): coredata.UserBooleanOption('Use precompiled headers', True),
OptionKey('b_lto'): coredata.UserBooleanOption('Use link time optimization', False),
+ OptionKey('b_lto'): coredata.UserBooleanOption('Use link time optimization', False),
+ OptionKey('b_lto_threads'): coredata.UserIntegerOption('Use multiple threads for Link Time Optimization', (None, None,0)),
+ OptionKey('b_lto_mode'): coredata.UserComboOption('Select between different LTO modes.',
+ ['default', 'thin'],
+ 'default'),
OptionKey('b_sanitize'): coredata.UserComboOption('Code sanitizer to use',
['none', 'address', 'thread', 'undefined', 'memory', 'address,undefined'],
'none'),
@@ -300,11 +305,26 @@ def option_enabled(boptions: T.Set[OptionKey], options: 'KeyedOptionDictType',
except KeyError:
return False
+
+def get_option_value(options: 'KeyedOptionDictType', opt: OptionKey, fallback: '_T') -> '_T':
+ """Get the value of an option, or the fallback value."""
+ try:
+ v: '_T' = options[opt].value
+ except KeyError:
+ return fallback
+
+ assert isinstance(v, type(fallback)), f'Should have {type(fallback)!r} but was {type(v)!r}'
+ # Mypy doesn't understand that the above assert ensures that v is type _T
+ return v
+
+
def get_base_compile_args(options: 'KeyedOptionDictType', compiler: 'Compiler') -> T.List[str]:
args = [] # type T.List[str]
try:
if options[OptionKey('b_lto')].value:
- args.extend(compiler.get_lto_compile_args())
+ args.extend(compiler.get_lto_compile_args(
+ threads=get_option_value(options, OptionKey('b_lto_threads'), 0),
+ mode=get_option_value(options, OptionKey('b_lto_mode'), 'default')))
except KeyError:
pass
try:
@@ -926,7 +946,7 @@ class Compiler(metaclass=abc.ABCMeta):
ret.append(arg)
return ret
- def get_lto_compile_args(self) -> T.List[str]:
+ def get_lto_compile_args(self, *, threads: int = 0, mode: str = 'default') -> T.List[str]:
return []
def get_lto_link_args(self) -> T.List[str]:
diff --git a/mesonbuild/compilers/mixins/clang.py b/mesonbuild/compilers/mixins/clang.py
index fcb2225..1778c31 100644
--- a/mesonbuild/compilers/mixins/clang.py
+++ b/mesonbuild/compilers/mixins/clang.py
@@ -19,7 +19,7 @@ import shutil
import typing as T
from ... import mesonlib
-from ...linkers import AppleDynamicLinker
+from ...linkers import AppleDynamicLinker, ClangClDynamicLinker, LLVMDynamicLinker, GnuGoldDynamicLinker
from ...mesonlib import OptionKey
from ..compilers import CompileCheckMode
from .gnu import GnuLikeCompiler
@@ -49,7 +49,9 @@ class ClangCompiler(GnuLikeCompiler):
super().__init__()
self.id = 'clang'
self.defines = defines or {}
- self.base_options.add(OptionKey('b_colorout'))
+ self.base_options.update(
+ {OptionKey('b_colorout'), OptionKey('b_lto_threads'), OptionKey('b_lto_mode')})
+
# TODO: this really should be part of the linker base_options, but
# linkers don't have base_options.
if isinstance(self.linker, AppleDynamicLinker):
@@ -135,3 +137,18 @@ class ClangCompiler(GnuLikeCompiler):
def get_coverage_link_args(self) -> T.List[str]:
return ['--coverage']
+
+ def get_lto_compile_args(self, *, threads: int = 0, mode: str = 'default') -> T.List[str]:
+ args: T.List[str] = []
+ if mode == 'thin':
+ # Thin LTO requires the use of gold, lld, ld64, or lld-link
+ if not isinstance(self.linker, (AppleDynamicLinker, ClangClDynamicLinker, LLVMDynamicLinker, GnuGoldDynamicLinker)):
+ raise mesonlib.MesonException(f"LLVM's thinLTO only works with gnu gold, lld, lld-link, and ld64, not {self.linker.id}")
+ args.append(f'-flto={mode}')
+ else:
+ assert mode == 'default', 'someone forgot to wire something up'
+ args.extend(super().get_lto_compile_args(threads=threads))
+ # In clang -flto=0 means auto
+ if threads >= 0:
+ args.append(f'-flto-jobs={threads}')
+ return args
diff --git a/mesonbuild/compilers/mixins/gnu.py b/mesonbuild/compilers/mixins/gnu.py
index 95bcd7c..464c664 100644
--- a/mesonbuild/compilers/mixins/gnu.py
+++ b/mesonbuild/compilers/mixins/gnu.py
@@ -17,6 +17,7 @@
import abc
import functools
import os
+import multiprocessing
import pathlib
import re
import subprocess
@@ -281,7 +282,9 @@ class GnuLikeCompiler(Compiler, metaclass=abc.ABCMeta):
return self._split_fetch_real_dirs(line.split('=', 1)[1])
return []
- def get_lto_compile_args(self) -> T.List[str]:
+ def get_lto_compile_args(self, *, threads: int = 0, mode: str = 'default') -> T.List[str]:
+ # This provides a base for many compilers, GCC and Clang override this
+ # for their specific arguments
return ['-flto']
def sanitizer_compile_args(self, value: str) -> T.List[str]:
@@ -330,7 +333,7 @@ class GnuCompiler(GnuLikeCompiler):
super().__init__()
self.id = 'gcc'
self.defines = defines or {}
- self.base_options.add(OptionKey('b_colorout'))
+ self.base_options.update({OptionKey('b_colorout'), OptionKey('b_lto_threads')})
def get_colorout_args(self, colortype: str) -> T.List[str]:
if mesonlib.version_compare(self.version, '>=4.9.0'):
@@ -383,3 +386,13 @@ class GnuCompiler(GnuLikeCompiler):
def get_prelink_args(self, prelink_name: str, obj_list: T.List[str]) -> T.List[str]:
return ['-r', '-o', prelink_name] + obj_list
+
+ def get_lto_compile_args(self, *, threads: int = 0, mode: str = 'default') -> T.List[str]:
+ if threads == 0:
+ if mesonlib.version_compare(self.version, '>= 10.0'):
+ return ['-flto=auto']
+ # This matches clang's behavior of using the number of cpus
+ return [f'-flto={multiprocessing.cpu_count()}']
+ elif threads > 0:
+ return [f'-flto={threads}']
+ return super().get_lto_compile_args(threads=threads)
diff --git a/mesonbuild/dependencies/cuda.py b/mesonbuild/dependencies/cuda.py
index c04e2fc..20f6569 100644
--- a/mesonbuild/dependencies/cuda.py
+++ b/mesonbuild/dependencies/cuda.py
@@ -219,7 +219,7 @@ class CudaDependency(ExternalDependency):
raise DependencyException(msg.format(arch, 'Windows'))
return os.path.join('lib', libdirs[arch])
elif machine.is_linux():
- libdirs = {'x86_64': 'lib64', 'ppc64': 'lib', 'aarch64': 'lib64'}
+ libdirs = {'x86_64': 'lib64', 'ppc64': 'lib', 'aarch64': 'lib64', 'loongarch64': 'lib64'}
if arch not in libdirs:
raise DependencyException(msg.format(arch, 'Linux'))
return libdirs[arch]
diff --git a/mesonbuild/envconfig.py b/mesonbuild/envconfig.py
index 6713135..ba35d16 100644
--- a/mesonbuild/envconfig.py
+++ b/mesonbuild/envconfig.py
@@ -45,6 +45,7 @@ known_cpu_families = (
'dspic',
'e2k',
'ia64',
+ 'loongarch64',
'm68k',
'microblaze',
'mips',
@@ -74,6 +75,7 @@ CPU_FAMILIES_64_BIT = [
'aarch64',
'alpha',
'ia64',
+ 'loongarch64',
'mips64',
'ppc64',
'riscv64',
diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py
index 219b0f2..b12728b 100644
--- a/mesonbuild/environment.py
+++ b/mesonbuild/environment.py
@@ -1006,7 +1006,7 @@ class Environment:
def _handle_exceptions(self, exceptions, binaries, bintype='compiler'):
errmsg = 'Unknown {}(s): {}'.format(bintype, binaries)
if exceptions:
- errmsg += '\nThe follow exceptions were encountered:'
+ errmsg += '\nThe following exception(s) were encountered:'
for (c, e) in exceptions.items():
errmsg += '\nRunning "{0}" gave "{1}"'.format(c, e)
raise EnvironmentException(errmsg)
@@ -1778,8 +1778,8 @@ class Environment:
mlog.warning(
'Please do not put -C linker= in your compiler '
'command, set rust_ld=command in your cross file '
- 'or use the RUST_LD environment variable. meson '
- 'will override your seletion otherwise.')
+ 'or use the RUST_LD environment variable, otherwise meson '
+ 'will override your selection.')
if override is None:
extra_args = {}
@@ -1818,7 +1818,7 @@ class Environment:
else:
# On linux and macos rust will invoke the c compiler for
# linking, on windows it will use lld-link or link.exe.
- # we will simply ask for the C compiler that coresponds to
+ # we will simply ask for the C compiler that corresponds to
# it, and use that.
cc = self._detect_c_or_cpp_compiler('c', for_machine, override_compiler=override)
linker = cc.linker
diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py
index 586e94e..176c1da 100644
--- a/mesonbuild/interpreter.py
+++ b/mesonbuild/interpreter.py
@@ -409,6 +409,7 @@ class ConfigurationDataHolder(MutableInterpreterObject, ObjectHolder):
return self.held_object.values[name] # (val, desc)
@FeatureNew('configuration_data.keys()', '0.57.0')
+ @noPosargs
def keys_method(self, args, kwargs):
return sorted(self.keys())
@@ -2378,7 +2379,7 @@ permitted_kwargs = {'add_global_arguments': {'language', 'native'},
'jar': build.known_jar_kwargs,
'project': {'version', 'meson_version', 'default_options', 'license', 'subproject_dir'},
'run_command': {'check', 'capture', 'env'},
- 'run_target': {'command', 'depends'},
+ 'run_target': {'command', 'depends', 'env'},
'shared_library': build.known_shlib_kwargs,
'shared_module': build.known_shmod_kwargs,
'static_library': build.known_stlib_kwargs,
@@ -2618,7 +2619,7 @@ class Interpreter(InterpreterBase):
def get_build_def_files(self) -> T.List[str]:
return self.build_def_files
- def add_build_def_file(self, f):
+ def add_build_def_file(self, f: mesonlib.FileOrString) -> None:
# Use relative path for files within source directory, and absolute path
# for system files. Skip files within build directory. Also skip not regular
# files (e.g. /dev/stdout) Normalize the path to avoid duplicates, this
@@ -3366,8 +3367,8 @@ external dependencies (including libraries) must go to "dependencies".''')
raise InterpreterException('Problem encountered: ' + args[0])
@noKwargs
+ @noPosargs
def func_exception(self, node, args, kwargs):
- self.validate_arguments(args, 0, [])
raise Exception()
def add_languages(self, args: T.Sequence[str], required: bool, for_machine: MachineChoice) -> bool:
@@ -3984,6 +3985,7 @@ external dependencies (including libraries) must go to "dependencies".''')
@permittedKwargs(permitted_kwargs['vcs_tag'])
@FeatureDeprecatedKwargs('custom_target', '0.47.0', ['build_always'],
'combine build_by_default and build_always_stale instead.')
+ @noPosargs
def func_vcs_tag(self, node, args, kwargs):
if 'input' not in kwargs or 'output' not in kwargs:
raise InterpreterException('Keyword arguments input and output must exist')
@@ -4024,12 +4026,9 @@ external dependencies (including libraries) must go to "dependencies".''')
return self._func_custom_target_impl(node, [kwargs['output']], kwargs)
@FeatureNew('subdir_done', '0.46.0')
- @stringArgs
+ @noPosargs
+ @noKwargs
def func_subdir_done(self, node, args, kwargs):
- if len(kwargs) > 0:
- raise InterpreterException('exit does not take named arguments')
- if len(args) > 0:
- raise InterpreterException('exit does not take any arguments')
raise SubdirDoneRequest()
@stringArgs
@@ -4060,6 +4059,7 @@ This will become a hard error in the future.''' % kwargs['input'], location=self
self.add_target(name, tg.held_object)
return tg
+ @FeatureNewKwargs('run_target', '0.57.0', ['env'])
@permittedKwargs(permitted_kwargs['run_target'])
def func_run_target(self, node, args, kwargs):
if len(args) > 1:
@@ -4088,8 +4088,8 @@ This will become a hard error in the future.''' % kwargs['input'], location=self
if not isinstance(d, (build.BuildTarget, build.CustomTarget)):
raise InterpreterException('Depends items must be build targets.')
cleaned_deps.append(d)
- command, *cmd_args = cleaned_args
- tg = RunTargetHolder(build.RunTarget(name, command, cmd_args, cleaned_deps, self.subdir, self.subproject), self)
+ env = self.unpack_env_kwarg(kwargs)
+ tg = RunTargetHolder(build.RunTarget(name, cleaned_args, cleaned_deps, self.subdir, self.subproject, env), self)
self.add_target(name, tg.held_object)
full_name = (self.subproject, name)
assert(full_name not in self.build.run_target_names)
@@ -4412,9 +4412,8 @@ This will become a hard error in the future.''' % kwargs['input'], location=self
@FeatureNewKwargs('configure_file', '0.50.0', ['install'])
@FeatureNewKwargs('configure_file', '0.52.0', ['depfile'])
@permittedKwargs(permitted_kwargs['configure_file'])
+ @noPosargs
def func_configure_file(self, node, args, kwargs):
- if len(args) > 0:
- raise InterpreterException("configure_file takes only keyword arguments.")
if 'output' not in kwargs:
raise InterpreterException('Required keyword argument "output" not defined.')
actions = set(['configuration', 'command', 'copy']).intersection(kwargs.keys())
diff --git a/mesonbuild/interpreterbase.py b/mesonbuild/interpreterbase.py
index f17dfba..e924e93 100644
--- a/mesonbuild/interpreterbase.py
+++ b/mesonbuild/interpreterbase.py
@@ -18,10 +18,11 @@
from . import mparser, mesonlib, mlog
from . import environment, dependencies
+from functools import wraps
import abc
-import os, copy, re
import collections.abc
-from functools import wraps
+import itertools
+import os, copy, re
import typing as T
TV_fw_var = T.Union[str, int, float, bool, list, dict, 'InterpreterObject', 'ObjectHolder']
@@ -228,6 +229,127 @@ class permittedKwargs:
return f(*wrapped_args, **wrapped_kwargs)
return T.cast(TV_func, wrapped)
+
+def typed_pos_args(name: str, *types: T.Union[T.Type, T.Tuple[T.Type, ...]],
+ varargs: T.Optional[T.Union[T.Type, T.Tuple[T.Type]]] = None,
+ optargs: T.Optional[T.List[T.Union[T.Type, T.Tuple[T.Type]]]] = None,
+ min_varargs: int = 0, max_varargs: int = 0) -> T.Callable[..., T.Any]:
+ """Decorator that types type checking of positional arguments.
+
+ This supports two different models of optional aguments, the first is the
+ variadic argument model. Variadic arguments are a possibly bounded,
+ possibly unbounded number of arguments of the same type (unions are
+ supported). The second is the standard default value model, in this case
+ a number of optional arguments may be provided, but they are still
+ ordered, and they may have different types.
+
+ This function does not support mixing variadic and default arguments.
+
+ :name: The name of the decorated function (as displayed in error messages)
+ :varargs: They type(s) of any variadic arguments the function takes. If
+ None the function takes no variadic args
+ :min_varargs: the minimum number of variadic arguments taken
+ :max_varargs: the maximum number of variadic arguments taken. 0 means unlimited
+ :optargs: The types of any optional arguments parameters taken. If None
+ then no optional paramters are taken.
+
+ Some examples of usage blow:
+ >>> @typed_pos_args('mod.func', str, (str, int))
+ ... def func(self, state: ModuleState, args: T.Tuple[str, T.Union[str, int]], kwargs: T.Dict[str, T.Any]) -> T.Any:
+ ... pass
+
+ >>> @typed_pos_args('method', str, varargs=str)
+ ... def method(self, node: BaseNode, args: T.Tuple[str, T.List[str]], kwargs: T.Dict[str, T.Any]) -> T.Any:
+ ... pass
+
+ >>> @typed_pos_args('method', varargs=str, min_varargs=1)
+ ... def method(self, node: BaseNode, args: T.Tuple[T.List[str]], kwargs: T.Dict[str, T.Any]) -> T.Any:
+ ... pass
+
+ >>> @typed_pos_args('method', str, optargs=[(str, int), str])
+ ... def method(self, node: BaseNode, args: T.Tuple[str, T.Optional[T.Union[str, int]], T.Optional[str]], kwargs: T.Dict[str, T.Any]) -> T.Any:
+ ... pass
+
+ When should you chose `typed_pos_args('name', varargs=str,
+ min_varargs=1)` vs `typed_pos_args('name', str, varargs=str)`?
+
+ The answer has to do with the semantics of the function, if all of the
+ inputs are the same type (such as with `files()`) then the former is
+ correct, all of the arguments are string names of files. If the first
+ argument is something else the it should be separated.
+ """
+ def inner(f: TV_func) -> TV_func:
+
+ @wraps(f)
+ def wrapper(*wrapped_args: T.Any, **wrapped_kwargs: T.Any) -> T.Any:
+ args = _get_callee_args(wrapped_args)[2]
+
+ # These are implementation programming errors, end users should never see them.
+ assert isinstance(args, list), args
+ assert max_varargs >= 0, 'max_varrags cannot be negative'
+ assert min_varargs >= 0, 'min_varrags cannot be negative'
+ assert optargs is None or varargs is None, \
+ 'varargs and optargs not supported together as this would be ambiguous'
+
+ num_args = len(args)
+ num_types = len(types)
+ a_types = types
+
+ if varargs:
+ min_args = num_types + min_varargs
+ max_args = num_types + max_varargs
+ if max_varargs == 0 and num_args < min_args:
+ raise InvalidArguments(f'{name} takes at least {min_args} arguments, but got {num_args}.')
+ elif max_varargs != 0 and (num_args < min_args or num_args > max_args):
+ raise InvalidArguments(f'{name} takes between {min_args} and {max_args} arguments, but got {num_args}.')
+ elif optargs:
+ if num_args < num_types:
+ raise InvalidArguments(f'{name} takes at least {num_types} arguments, but got {num_args}.')
+ elif num_args > num_types + len(optargs):
+ raise InvalidArguments(f'{name} takes at most {num_types + len(optargs)} arguments, but got {num_args}.')
+ # Add the number of positional arguments required
+ if num_args > num_types:
+ diff = num_args - num_types
+ a_types = tuple(list(types) + list(optargs[:diff]))
+ elif num_args != num_types:
+ raise InvalidArguments(f'{name} takes exactly {num_types} arguments, but got {num_args}.')
+
+ for i, (arg, type_) in enumerate(itertools.zip_longest(args, a_types, fillvalue=varargs), start=1):
+ if not isinstance(arg, type_):
+ if isinstance(type_, tuple):
+ shouldbe = 'one of: {}'.format(", ".join(f'"{t.__name__}"' for t in type_))
+ else:
+ shouldbe = f'"{type_.__name__}"'
+ raise InvalidArguments(f'{name} argument {i} was of type "{type(arg).__name__}" but should have been {shouldbe}')
+
+ # Ensure that we're actually passing a tuple.
+ # Depending on what kind of function we're calling the length of
+ # wrapped_args can vary.
+ nargs = list(wrapped_args)
+ i = nargs.index(args)
+ if varargs:
+ # if we have varargs we need to split them into a separate
+ # tuple, as python's typing doesn't understand tuples with
+ # fixed elements and variadic elements, only one or the other.
+ # so in that case we need T.Tuple[int, str, float, T.Tuple[str, ...]]
+ pos = args[:len(types)]
+ var = list(args[len(types):])
+ pos.append(var)
+ nargs[i] = tuple(pos)
+ elif optargs:
+ if num_args < num_types + len(optargs):
+ diff = num_types + len(optargs) - num_args
+ nargs[i] = tuple(list(args) + [None] * diff)
+ else:
+ nargs[i] = args
+ else:
+ nargs[i] = tuple(args)
+ return f(*nargs, **wrapped_kwargs)
+
+ return T.cast(TV_func, wrapper)
+ return inner
+
+
class FeatureCheckBase(metaclass=abc.ABCMeta):
"Base class for feature version checks"
diff --git a/mesonbuild/minstall.py b/mesonbuild/minstall.py
index 18dca8b..6c1810a 100644
--- a/mesonbuild/minstall.py
+++ b/mesonbuild/minstall.py
@@ -54,6 +54,7 @@ if T.TYPE_CHECKING:
quiet: bool
wd: str
destdir: str
+ dry_run: bool
symlink_warning = '''Warning: trying to copy a symlink that points to a file. This will copy the file,
@@ -75,20 +76,27 @@ def add_arguments(parser: argparse.Namespace) -> None:
help='Do not print every file that was installed.')
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.')
class DirMaker:
- def __init__(self, lf: T.TextIO):
+ def __init__(self, lf: T.TextIO, makedirs: T.Callable[..., None]):
self.lf = lf
self.dirs: T.List[str] = []
+ self.makedirs_impl = makedirs
def makedirs(self, path: str, exist_ok: bool = False) -> None:
dirname = os.path.normpath(path)
dirs = []
while dirname != os.path.dirname(dirname):
+ if dirname in self.dirs:
+ # In dry-run mode the directory does not exist but we would have
+ # created it with all its parents otherwise.
+ break
if not os.path.exists(dirname):
dirs.append(dirname)
dirname = os.path.dirname(dirname)
- os.makedirs(path, exist_ok=exist_ok)
+ self.makedirs_impl(path, exist_ok=exist_ok)
# store the directories in creation order, with the parent directory
# before the child directories. Future calls of makedir() will not
@@ -275,6 +283,74 @@ class Installer:
self.options = options
self.lf = lf
self.preserved_file_count = 0
+ self.dry_run = options.dry_run
+
+ def mkdir(self, *args: T.Any, **kwargs: T.Any) -> None:
+ if not self.dry_run:
+ os.mkdir(*args, **kwargs)
+
+ def remove(self, *args: T.Any, **kwargs: T.Any) -> None:
+ if not self.dry_run:
+ os.remove(*args, **kwargs)
+
+ def symlink(self, *args: T.Any, **kwargs: T.Any) -> None:
+ if not self.dry_run:
+ os.symlink(*args, **kwargs)
+
+ def makedirs(self, *args: T.Any, **kwargs: T.Any) -> None:
+ if not self.dry_run:
+ os.makedirs(*args, **kwargs)
+
+ def copy(self, *args: T.Any, **kwargs: T.Any) -> None:
+ if not self.dry_run:
+ shutil.copy(*args, **kwargs)
+
+ def copy2(self, *args: T.Any, **kwargs: T.Any) -> None:
+ if not self.dry_run:
+ shutil.copy2(*args, **kwargs)
+
+ def copyfile(self, *args: T.Any, **kwargs: T.Any) -> None:
+ if not self.dry_run:
+ shutil.copyfile(*args, **kwargs)
+
+ def copystat(self, *args: T.Any, **kwargs: T.Any) -> None:
+ if not self.dry_run:
+ shutil.copystat(*args, **kwargs)
+
+ def fix_rpath(self, *args: T.Any, **kwargs: T.Any) -> None:
+ if not self.dry_run:
+ depfixer.fix_rpath(*args, **kwargs)
+
+ def set_chown(self, *args: T.Any, **kwargs: T.Any) -> None:
+ if not self.dry_run:
+ set_chown(*args, **kwargs)
+
+ def set_chmod(self, *args: T.Any, **kwargs: T.Any) -> None:
+ if not self.dry_run:
+ set_chmod(*args, **kwargs)
+
+ def sanitize_permissions(self, *args: T.Any, **kwargs: T.Any) -> None:
+ if not self.dry_run:
+ sanitize_permissions(*args, **kwargs)
+
+ def set_mode(self, *args: T.Any, **kwargs: T.Any) -> None:
+ if not self.dry_run:
+ set_mode(*args, **kwargs)
+
+ def restore_selinux_contexts(self) -> None:
+ if not self.dry_run:
+ restore_selinux_contexts()
+
+ def Popen_safe(self, *args: T.Any, **kwargs: T.Any) -> T.Tuple[int, str, str]:
+ if not self.dry_run:
+ p, o, e = Popen_safe(*args, **kwargs)
+ return p.returncode, o, e
+ return 0, '', ''
+
+ def run_exe(self, *args: T.Any, **kwargs: T.Any) -> int:
+ if not self.dry_run:
+ return run_exe(*args, **kwargs)
+ return 0
def log(self, msg: str) -> None:
if not self.options.quiet:
@@ -307,7 +383,7 @@ class Installer:
append_to_log(self.lf, '# Preserving old file {}\n'.format(to_file))
self.preserved_file_count += 1
return False
- os.remove(to_file)
+ self.remove(to_file)
elif makedirs:
# Unpack tuple
dirmaker, outdir = makedirs
@@ -317,14 +393,14 @@ class Installer:
if os.path.islink(from_file):
if not os.path.exists(from_file):
# Dangling symlink. Replicate as is.
- shutil.copy(from_file, outdir, follow_symlinks=False)
+ self.copy(from_file, outdir, follow_symlinks=False)
else:
# Remove this entire branch when changing the behaviour to duplicate
# symlinks rather than copying what they point to.
print(symlink_warning)
- shutil.copy2(from_file, to_file)
+ self.copy2(from_file, to_file)
else:
- shutil.copy2(from_file, to_file)
+ self.copy2(from_file, to_file)
selinux_updates.append(to_file)
append_to_log(self.lf, to_file)
return True
@@ -378,8 +454,8 @@ class Installer:
print('Tried to copy directory {} but a file of that name already exists.'.format(abs_dst))
sys.exit(1)
dm.makedirs(abs_dst)
- shutil.copystat(abs_src, abs_dst)
- sanitize_permissions(abs_dst, data.install_umask)
+ self.copystat(abs_src, abs_dst)
+ self.sanitize_permissions(abs_dst, data.install_umask)
for f in files:
abs_src = os.path.join(root, f)
filepart = os.path.relpath(abs_src, start=src_dir)
@@ -391,11 +467,11 @@ class Installer:
sys.exit(1)
parent_dir = os.path.dirname(abs_dst)
if not os.path.isdir(parent_dir):
- os.mkdir(parent_dir)
- shutil.copystat(os.path.dirname(abs_src), parent_dir)
+ self.mkdir(parent_dir)
+ self.copystat(os.path.dirname(abs_src), parent_dir)
# FIXME: what about symlinks?
self.do_copyfile(abs_src, abs_dst)
- set_mode(abs_dst, install_mode, data.install_umask)
+ self.set_mode(abs_dst, install_mode, data.install_umask)
@staticmethod
def check_installdata(obj: InstallData) -> InstallData:
@@ -422,13 +498,13 @@ class Installer:
self.did_install_something = False
try:
- with DirMaker(self.lf) as dm:
+ with DirMaker(self.lf, self.makedirs) as dm:
self.install_subdirs(d, dm, destdir, fullprefix) # Must be first, because it needs to delete the old subtree.
self.install_targets(d, dm, destdir, fullprefix)
self.install_headers(d, dm, destdir, fullprefix)
self.install_man(d, dm, destdir, fullprefix)
self.install_data(d, dm, destdir, fullprefix)
- restore_selinux_contexts()
+ self.restore_selinux_contexts()
self.run_install_script(d, destdir, fullprefix)
if not self.did_install_something:
self.log('Nothing to install.')
@@ -460,7 +536,7 @@ class Installer:
outdir = os.path.dirname(outfilename)
if self.do_copyfile(fullfilename, outfilename, makedirs=(dm, outdir)):
self.did_install_something = True
- set_mode(outfilename, mode, d.install_umask)
+ self.set_mode(outfilename, mode, d.install_umask)
def install_man(self, d: InstallData, dm: DirMaker, destdir: str, fullprefix: str) -> None:
for m in d.man:
@@ -470,7 +546,7 @@ class Installer:
install_mode = m[2]
if self.do_copyfile(full_source_filename, outfilename, makedirs=(dm, outdir)):
self.did_install_something = True
- set_mode(outfilename, install_mode, d.install_umask)
+ self.set_mode(outfilename, install_mode, d.install_umask)
def install_headers(self, d: InstallData, dm: DirMaker, destdir: str, fullprefix: str) -> None:
for t in d.headers:
@@ -481,7 +557,7 @@ class Installer:
install_mode = t[2]
if self.do_copyfile(fullfilename, outfilename, makedirs=(dm, outdir)):
self.did_install_something = True
- set_mode(outfilename, install_mode, d.install_umask)
+ self.set_mode(outfilename, install_mode, d.install_umask)
def run_install_script(self, d: InstallData, destdir: str, fullprefix: str) -> None:
env = {'MESON_SOURCE_ROOT': d.source_dir,
@@ -501,7 +577,7 @@ class Installer:
self.did_install_something = True # Custom script must report itself if it does nothing.
self.log('Running custom install script {!r}'.format(name))
try:
- rc = run_exe(i, env)
+ rc = self.run_exe(i, env)
except OSError:
print('FAILED: install script \'{}\' could not be run, stopped'.format(name))
# POSIX shells return 127 when a command could not be found
@@ -533,14 +609,14 @@ class Installer:
raise RuntimeError('File {!r} could not be found'.format(fname))
elif os.path.isfile(fname):
file_copied = self.do_copyfile(fname, outname, makedirs=(dm, outdir))
- set_mode(outname, install_mode, d.install_umask)
+ self.set_mode(outname, install_mode, d.install_umask)
if should_strip and d.strip_bin is not None:
if fname.endswith('.jar'):
self.log('Not stripping jar target: {}'.format(os.path.basename(fname)))
continue
self.log('Stripping target {!r} using {}.'.format(fname, d.strip_bin[0]))
- ps, stdo, stde = Popen_safe(d.strip_bin + [outname])
- if ps.returncode != 0:
+ returncode, stdo, stde = self.Popen_safe(d.strip_bin + [outname])
+ if returncode != 0:
print('Could not strip file.\n')
print('Stdout:\n{}\n'.format(stdo))
print('Stderr:\n{}\n'.format(stde))
@@ -564,10 +640,10 @@ class Installer:
try:
symlinkfilename = os.path.join(outdir, alias)
try:
- os.remove(symlinkfilename)
+ self.remove(symlinkfilename)
except FileNotFoundError:
pass
- os.symlink(to, symlinkfilename)
+ self.symlink(to, symlinkfilename)
append_to_log(self.lf, symlinkfilename)
except (NotImplementedError, OSError):
if not printed_symlink_error:
@@ -577,8 +653,8 @@ class Installer:
if file_copied:
self.did_install_something = True
try:
- depfixer.fix_rpath(outname, t.rpath_dirs_to_remove, install_rpath, final_path,
- install_name_mappings, verbose=False)
+ self.fix_rpath(outname, t.rpath_dirs_to_remove, install_rpath, final_path,
+ install_name_mappings, verbose=False)
except SystemExit as e:
if isinstance(e.code, int) and e.code == 0:
pass
diff --git a/mesonbuild/modules/fs.py b/mesonbuild/modules/fs.py
index 2ff256b..44986f8 100644
--- a/mesonbuild/modules/fs.py
+++ b/mesonbuild/modules/fs.py
@@ -14,18 +14,25 @@
import typing as T
import hashlib
+import os
from pathlib import Path, PurePath, PureWindowsPath
from .. import mlog
from . import ExtensionModule
from . import ModuleReturnValue
-from ..mesonlib import MesonException
+from ..mesonlib import (
+ File,
+ FileOrString,
+ MesonException,
+ path_is_in_root,
+)
from ..interpreterbase import FeatureNew
from ..interpreterbase import stringArgs, noKwargs
if T.TYPE_CHECKING:
from ..interpreter import Interpreter, ModuleState
+
class FSModule(ExtensionModule):
def __init__(self, interpreter: 'Interpreter') -> None:
@@ -193,5 +200,61 @@ class FSModule(ExtensionModule):
new = original.stem
return ModuleReturnValue(str(new), [])
+ #@permittedKwargs({'encoding'})
+ @FeatureNew('fs.read', '0.57.0')
+ def read(
+ self,
+ state: 'ModuleState',
+ args: T.Sequence['FileOrString'],
+ kwargs: T.Dict[str, T.Any]
+ ) -> ModuleReturnValue:
+ '''
+ Read a file from the source tree and return its value as a decoded
+ string. If the encoding is not specified, the file is assumed to be
+ utf-8 encoded. Paths must be relative by default (to prevent accidents)
+ and are forbidden to be read from the build directory (to prevent build
+ loops)
+ '''
+ if len(args) != 1:
+ raise MesonException('expected single positional <path> arg')
+
+ path = args[0]
+ if not isinstance(path, (str, File)):
+ raise MesonException(
+ '<path> positional argument must be string or files() object')
+
+ encoding = kwargs.get('encoding', 'utf-8')
+ if not isinstance(encoding, str):
+ raise MesonException('`encoding` parameter must be a string')
+
+ src_dir = self.interpreter.environment.source_dir
+ sub_dir = self.interpreter.subdir
+ build_dir = self.interpreter.environment.get_build_dir()
+
+ if isinstance(path, File):
+ if path.is_built:
+ raise MesonException(
+ 'fs.read_file does not accept built files() objects')
+ path = os.path.join(src_dir, path.relative_name())
+ else:
+ if sub_dir:
+ src_dir = os.path.join(src_dir, sub_dir)
+ path = os.path.join(src_dir, path)
+
+ path = os.path.abspath(path)
+ if path_is_in_root(Path(path), Path(build_dir), resolve=True):
+ raise MesonException('path must not be in the build tree')
+ try:
+ with open(path, 'r', encoding=encoding) as f:
+ data = f.read()
+ except UnicodeDecodeError:
+ raise MesonException(f'decoding failed for {path}')
+ # Reconfigure when this file changes as it can contain data used by any
+ # part of the build configuration (e.g. `project(..., version:
+ # fs.read_file('VERSION')` or `configure_file(...)`
+ self.interpreter.add_build_def_file(path)
+ return ModuleReturnValue(data, [])
+
+
def initialize(*args: T.Any, **kwargs: T.Any) -> FSModule:
return FSModule(*args, **kwargs)
diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py
index f564eb4..f966083 100644
--- a/mesonbuild/modules/gnome.py
+++ b/mesonbuild/modules/gnome.py
@@ -34,7 +34,7 @@ from ..mesonlib import (
join_args, unholder,
)
from ..dependencies import Dependency, PkgConfigDependency, InternalDependency, ExternalProgram
-from ..interpreterbase import noKwargs, permittedKwargs, FeatureNew, FeatureNewKwargs, FeatureDeprecatedKwargs
+from ..interpreterbase import noPosargs, noKwargs, permittedKwargs, FeatureNew, FeatureNewKwargs, FeatureDeprecatedKwargs
if T.TYPE_CHECKING:
from ..compilers import Compiler
@@ -51,6 +51,10 @@ native_glib_version = None
class GnomeModule(ExtensionModule):
gir_dep = None
+ install_glib_compile_schemas = False
+ install_gio_querymodules = []
+ install_gtk_update_icon_cache = False
+
@staticmethod
def _get_native_glib_version(state):
global native_glib_version
@@ -80,6 +84,65 @@ class GnomeModule(ExtensionModule):
mlog.bold('https://github.com/mesonbuild/meson/issues/1387'),
once=True)
+ def _get_native_dep(self, state, depname, required=True):
+ kwargs = {'native': True, 'required': required}
+ holder = self.interpreter.func_dependency(state.current_node, [depname], kwargs)
+ return holder.held_object
+
+ def _get_native_binary(self, state, name, depname, varname, required=True):
+ # Look in overrides in case glib/gtk/etc are built as subproject
+ prog = self.interpreter.program_from_overrides([name], [])
+ if prog is not None:
+ return unholder(prog)
+
+ # Look in machine file
+ prog = state.environment.lookup_binary_entry(MachineChoice.HOST, name)
+ if prog is not None:
+ return ExternalProgram.from_entry(name, prog)
+
+ # Check if pkgconfig has a variable
+ dep = self._get_native_dep(state, depname, required=False)
+ if dep.found() and dep.type_name == 'pkgconfig':
+ value = dep.get_pkgconfig_variable(varname, {})
+ if value:
+ return ExternalProgram(name, value)
+
+ # Normal program lookup
+ return unholder(self.interpreter.find_program_impl(name, required=required))
+
+ @permittedKwargs({'glib_compile_schemas', 'gio_querymodules', 'gtk_update_icon_cache'})
+ @noPosargs
+ @FeatureNew('gnome.post_install', '0.57.0')
+ def post_install(self, state, args, kwargs):
+ rv = []
+ datadir_abs = os.path.join(state.environment.get_prefix(), state.environment.get_datadir())
+ if kwargs.get('glib_compile_schemas', False) and not self.install_glib_compile_schemas:
+ self.install_glib_compile_schemas = True
+ prog = self._get_native_binary(state, 'glib-compile-schemas', 'gio-2.0', 'glib_compile_schemas')
+ schemasdir = os.path.join(datadir_abs, 'glib-2.0', 'schemas')
+ script = state.backend.get_executable_serialisation([prog, schemasdir])
+ script.skip_if_destdir = True
+ rv.append(script)
+ for d in mesonlib.extract_as_list(kwargs, 'gio_querymodules'):
+ if d not in self.install_gio_querymodules:
+ self.install_gio_querymodules.append(d)
+ prog = self._get_native_binary(state, 'gio-querymodules', 'gio-2.0', 'gio_querymodules')
+ moduledir = os.path.join(state.environment.get_prefix(), d)
+ script = state.backend.get_executable_serialisation([prog, moduledir])
+ script.skip_if_destdir = True
+ rv.append(script)
+ if kwargs.get('gtk_update_icon_cache', False) and not self.install_gtk_update_icon_cache:
+ self.install_gtk_update_icon_cache = True
+ prog = self._get_native_binary(state, 'gtk4-update-icon-cache', 'gtk-4.0', 'gtk4_update_icon_cache', required=False)
+ found = isinstance(prog, build.Executable) or prog.found()
+ if not found:
+ prog = self._get_native_binary(state, 'gtk-update-icon-cache', 'gtk+-3.0', 'gtk_update_icon_cache')
+ icondir = os.path.join(datadir_abs, 'icons', 'hicolor')
+ script = state.backend.get_executable_serialisation([prog, '-q', '-t' ,'-f', icondir])
+ script.skip_if_destdir = True
+ rv.append(script)
+ return ModuleReturnValue(None, rv)
+
@FeatureNewKwargs('gnome.compile_resources', '0.37.0', ['gresource_bundle', 'export', 'install_header'])
@permittedKwargs({'source_dir', 'c_name', 'dependencies', 'export', 'gresource_bundle', 'install_header',
'install', 'install_dir', 'extra_args', 'build_by_default'})
@@ -418,23 +481,9 @@ class GnomeModule(ExtensionModule):
def _get_gir_dep(self, state):
if not self.gir_dep:
- kwargs = {'native': True, 'required': True}
- holder = self.interpreter.func_dependency(state.current_node, ['gobject-introspection-1.0'], kwargs)
- self.gir_dep = holder.held_object
- giscanner = state.environment.lookup_binary_entry(MachineChoice.HOST, 'g-ir-scanner')
- if giscanner is not None:
- self.giscanner = ExternalProgram.from_entry('g-ir-scanner', giscanner)
- elif self.gir_dep.type_name == 'pkgconfig':
- self.giscanner = ExternalProgram('g_ir_scanner', self.gir_dep.get_pkgconfig_variable('g_ir_scanner', {}))
- else:
- self.giscanner = self.interpreter.find_program_impl('g-ir-scanner')
- gicompiler = state.environment.lookup_binary_entry(MachineChoice.HOST, 'g-ir-compiler')
- if gicompiler is not None:
- self.gicompiler = ExternalProgram.from_entry('g-ir-compiler', gicompiler)
- elif self.gir_dep.type_name == 'pkgconfig':
- self.gicompiler = ExternalProgram('g_ir_compiler', self.gir_dep.get_pkgconfig_variable('g_ir_compiler', {}))
- else:
- self.gicompiler = self.interpreter.find_program_impl('g-ir-compiler')
+ self.gir_dep = self._get_native_dep(state, 'gobject-introspection-1.0')
+ self.giscanner = self._get_native_binary(state, 'g-ir-scanner', 'gobject-introspection-1.0', 'g_ir_scanner')
+ self.gicompiler = self._get_native_binary(state, 'g-ir-compiler', 'gobject-introspection-1.0', 'g_ir_compiler')
return self.gir_dep, self.giscanner, self.gicompiler
@functools.lru_cache(maxsize=None)
@@ -907,8 +956,8 @@ class GnomeModule(ExtensionModule):
'--id=' + project_id,
'--sources=' + source_str,
]
- pottarget = build.RunTarget('help-' + project_id + '-pot', potargs[0],
- potargs[1:], [], state.subdir, state.subproject)
+ pottarget = build.RunTarget('help-' + project_id + '-pot', potargs,
+ [], state.subdir, state.subproject)
poargs = state.environment.get_build_command() + [
'--internal', 'yelphelper', 'update-po',
@@ -917,8 +966,8 @@ class GnomeModule(ExtensionModule):
'--sources=' + source_str,
'--langs=' + '@@'.join(langs),
]
- potarget = build.RunTarget('help-' + project_id + '-update-po', poargs[0],
- poargs[1:], [], state.subdir, state.subproject)
+ potarget = build.RunTarget('help-' + project_id + '-update-po', poargs,
+ [], state.subdir, state.subproject)
rv = [inscript, pottarget, potarget]
return ModuleReturnValue(None, rv)
diff --git a/mesonbuild/modules/i18n.py b/mesonbuild/modules/i18n.py
index ae24e6e..54faf4c 100644
--- a/mesonbuild/modules/i18n.py
+++ b/mesonbuild/modules/i18n.py
@@ -152,12 +152,12 @@ class I18nModule(ExtensionModule):
potargs.append(datadirs)
if extra_args:
potargs.append(extra_args)
- pottarget = build.RunTarget(packagename + '-pot', potargs[0], potargs[1:], [], state.subdir, state.subproject)
+ pottarget = build.RunTarget(packagename + '-pot', potargs, [], state.subdir, state.subproject)
gmoargs = state.environment.get_build_command() + ['--internal', 'gettext', 'gen_gmo']
if lang_arg:
gmoargs.append(lang_arg)
- gmotarget = build.RunTarget(packagename + '-gmo', gmoargs[0], gmoargs[1:], [], state.subdir, state.subproject)
+ gmotarget = build.RunTarget(packagename + '-gmo', gmoargs, [], state.subdir, state.subproject)
updatepoargs = state.environment.get_build_command() + ['--internal', 'gettext', 'update_po', pkg_arg]
if lang_arg:
@@ -166,7 +166,7 @@ class I18nModule(ExtensionModule):
updatepoargs.append(datadirs)
if extra_args:
updatepoargs.append(extra_args)
- updatepotarget = build.RunTarget(packagename + '-update-po', updatepoargs[0], updatepoargs[1:], [], state.subdir, state.subproject)
+ updatepotarget = build.RunTarget(packagename + '-update-po', updatepoargs, [], state.subdir, state.subproject)
targets = [pottarget, gmotarget, updatepotarget]
diff --git a/mesonbuild/modules/unstable_cuda.py b/mesonbuild/modules/unstable_cuda.py
index 0f9d681..0a5f031 100644
--- a/mesonbuild/modules/unstable_cuda.py
+++ b/mesonbuild/modules/unstable_cuda.py
@@ -43,6 +43,8 @@ class CudaModule(ExtensionModule):
cuda_version = args[0]
driver_version_table = [
+ {'cuda_version': '>=11.2.0', 'windows': '460.89', 'linux': '460.27.04'},
+ {'cuda_version': '>=11.1.1', 'windows': '456.81', 'linux': '455.32'},
{'cuda_version': '>=11.1.0', 'windows': '456.38', 'linux': '455.23'},
{'cuda_version': '>=11.0.3', 'windows': '451.82', 'linux': '450.51.06'},
{'cuda_version': '>=11.0.2', 'windows': '451.48', 'linux': '450.51.05'},
diff --git a/mesonbuild/modules/unstable_rust.py b/mesonbuild/modules/unstable_rust.py
index d215376..e74c181 100644
--- a/mesonbuild/modules/unstable_rust.py
+++ b/mesonbuild/modules/unstable_rust.py
@@ -18,8 +18,8 @@ from . import ExtensionModule, ModuleReturnValue
from .. import mlog
from ..build import BuildTarget, Executable, InvalidArguments
from ..dependencies import Dependency, ExternalLibrary
-from ..interpreter import ExecutableHolder, permitted_kwargs
-from ..interpreterbase import InterpreterException, permittedKwargs, FeatureNew
+from ..interpreter import ExecutableHolder, BuildTargetHolder, permitted_kwargs
+from ..interpreterbase import InterpreterException, permittedKwargs, FeatureNew, typed_pos_args
from ..mesonlib import stringlistify, unholder, listify
if T.TYPE_CHECKING:
@@ -35,7 +35,8 @@ class RustModule(ExtensionModule):
super().__init__(interpreter)
@permittedKwargs(permitted_kwargs['test'] | {'dependencies'} ^ {'protocol'})
- def test(self, state: 'ModuleState', args: T.List, kwargs: T.Dict[str, T.Any]) -> ModuleReturnValue:
+ @typed_pos_args('rust.test', str, BuildTargetHolder)
+ def test(self, state: 'ModuleState', args: T.Tuple[str, BuildTargetHolder], kwargs: T.Dict[str, T.Any]) -> ModuleReturnValue:
"""Generate a rust test target from a given rust target.
Rust puts it's unitests inside it's main source files, unlike most
@@ -77,14 +78,8 @@ class RustModule(ExtensionModule):
rust.test('rust_lib_test', rust_lib)
```
"""
- if len(args) != 2:
- raise InterpreterException('rustmod.test() takes exactly 2 positional arguments')
- name: str = args[0]
- if not isinstance(name, str):
- raise InterpreterException('First positional argument to rustmod.test() must be a string')
+ name = args[0]
base_target: BuildTarget = unholder(args[1])
- if not isinstance(base_target, BuildTarget):
- raise InterpreterException('Second positional argument to rustmod.test() must be a library or executable')
if not base_target.uses_rust():
raise InterpreterException('Second positional argument to rustmod.test() must be a rust based target')
extra_args = stringlistify(kwargs.get('args', []))
diff --git a/mesonbuild/scripts/commandrunner.py b/mesonbuild/scripts/commandrunner.py
deleted file mode 100644
index aeeaa3b..0000000
--- a/mesonbuild/scripts/commandrunner.py
+++ /dev/null
@@ -1,84 +0,0 @@
-# Copyright 2014 The Meson development team
-
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-
-# http://www.apache.org/licenses/LICENSE-2.0
-
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""This program is a wrapper to run external commands. It determines
-what to run, sets up the environment and executes the command."""
-
-import sys, os, subprocess, shutil, shlex
-import re
-import typing as T
-
-def run_command(source_dir: str, build_dir: str, subdir: str, meson_command: T.List[str], command: str, arguments: T.List[str]) -> subprocess.Popen:
- env = {'MESON_SOURCE_ROOT': source_dir,
- 'MESON_BUILD_ROOT': build_dir,
- 'MESON_SUBDIR': subdir,
- 'MESONINTROSPECT': ' '.join([shlex.quote(x) for x in meson_command + ['introspect']]),
- }
- cwd = os.path.join(source_dir, subdir)
- child_env = os.environ.copy()
- child_env.update(env)
-
- # Is the command an executable in path?
- exe = shutil.which(command)
- if exe is not None:
- command_array = [exe] + arguments
- else:# No? Maybe it is a script in the source tree.
- fullpath = os.path.join(source_dir, subdir, command)
- command_array = [fullpath] + arguments
- try:
- return subprocess.Popen(command_array, env=child_env, cwd=cwd)
- except FileNotFoundError:
- print('Could not execute command "%s". File not found.' % command)
- sys.exit(1)
- except PermissionError:
- print('Could not execute command "%s". File not executable.' % command)
- sys.exit(1)
- except OSError as err:
- print('Could not execute command "{}": {}'.format(command, err))
- sys.exit(1)
- except subprocess.SubprocessError as err:
- print('Could not execute command "{}": {}'.format(command, err))
- sys.exit(1)
-
-def is_python_command(cmdname: str) -> bool:
- end_py_regex = r'python(3|3\.\d+)?(\.exe)?$'
- return re.search(end_py_regex, cmdname) is not None
-
-def run(args: T.List[str]) -> int:
- if len(args) < 4:
- print('commandrunner.py <source dir> <build dir> <subdir> <command> [arguments]')
- return 1
- src_dir = args[0]
- build_dir = args[1]
- subdir = args[2]
- meson_bin = args[3]
- if is_python_command(meson_bin):
- meson_command = [meson_bin, args[4]]
- command = args[5]
- arguments = args[6:]
- else:
- meson_command = [meson_bin]
- command = args[4]
- arguments = args[5:]
- pc = run_command(src_dir, build_dir, subdir, meson_command, command, arguments)
- while True:
- try:
- pc.wait()
- break
- except KeyboardInterrupt:
- pass
- return pc.returncode
-
-if __name__ == '__main__':
- sys.exit(run(sys.argv[1:]))
diff --git a/mesonbuild/scripts/meson_exe.py b/mesonbuild/scripts/meson_exe.py
index 620f579..27db144 100644
--- a/mesonbuild/scripts/meson_exe.py
+++ b/mesonbuild/scripts/meson_exe.py
@@ -52,10 +52,13 @@ def run_exe(exe: ExecutableSerialisation, extra_env: T.Optional[dict] = None) ->
['Z:' + p for p in exe.extra_paths] + child_env.get('WINEPATH', '').split(';')
)
+ pipe = subprocess.PIPE
+ if exe.verbose:
+ assert not exe.capture, 'Cannot capture and print to console at the same time'
+ pipe = None
+
p = subprocess.Popen(cmd_args, env=child_env, cwd=exe.workdir,
- close_fds=False,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
+ close_fds=False, stdout=pipe, stderr=pipe)
stdout, stderr = p.communicate()
if p.returncode == 0xc0000135:
@@ -65,6 +68,8 @@ def run_exe(exe: ExecutableSerialisation, extra_env: T.Optional[dict] = None) ->
if p.returncode != 0:
if exe.pickled:
print('while executing {!r}'.format(cmd_args))
+ if exe.verbose:
+ return p.returncode
if not exe.capture:
print('--- stdout ---')
print(stdout.decode())
diff --git a/run_unittests.py b/run_unittests.py
index ebe8f48..2e7fe91 100755
--- a/run_unittests.py
+++ b/run_unittests.py
@@ -51,12 +51,13 @@ import mesonbuild.mesonlib
import mesonbuild.coredata
import mesonbuild.modules.gnome
from mesonbuild.interpreter import Interpreter, ObjectHolder
+from mesonbuild.interpreterbase import typed_pos_args, InvalidArguments
from mesonbuild.ast import AstInterpreter
from mesonbuild.mesonlib import (
BuildDirLock, LibType, MachineChoice, PerMachine, Version, is_windows,
is_osx, is_cygwin, is_dragonflybsd, is_openbsd, is_haiku, is_sunos,
windows_proof_rmtree, python_command, version_compare, split_args,
- quote_arg, relpath, is_linux, git, GIT
+ quote_arg, relpath, is_linux, git
)
from mesonbuild.environment import detect_ninja
from mesonbuild.mesonlib import MesonException, EnvironmentException, OptionKey
@@ -336,7 +337,6 @@ class InternalTests(unittest.TestCase):
self.assertEqual(searchfunc('2016.oops 1.2.3'), '1.2.3')
self.assertEqual(searchfunc('2016.x'), 'unknown version')
-
def test_mode_symbolic_to_bits(self):
modefunc = mesonbuild.mesonlib.FileMode.perms_s_to_bits
self.assertEqual(modefunc('---------'), 0)
@@ -1294,6 +1294,195 @@ class InternalTests(unittest.TestCase):
self.assertFalse(errors)
+ def test_typed_pos_args_types(self) -> None:
+ @typed_pos_args('foo', str, int, bool)
+ def _(obj, node, args: T.Tuple[str, int, bool], kwargs) -> None:
+ self.assertIsInstance(args, tuple)
+ self.assertIsInstance(args[0], str)
+ self.assertIsInstance(args[1], int)
+ self.assertIsInstance(args[2], bool)
+
+ _(None, mock.Mock(), ['string', 1, False], None)
+
+ def test_typed_pos_args_types_invalid(self) -> None:
+ @typed_pos_args('foo', str, int, bool)
+ def _(obj, node, args: T.Tuple[str, int, bool], kwargs) -> None:
+ self.assertTrue(False) # should not be reachable
+
+ with self.assertRaises(InvalidArguments) as cm:
+ _(None, mock.Mock(), ['string', 1.0, False], None)
+ self.assertEqual(str(cm.exception), 'foo argument 2 was of type "float" but should have been "int"')
+
+ def test_typed_pos_args_types_wrong_number(self) -> None:
+ @typed_pos_args('foo', str, int, bool)
+ def _(obj, node, args: T.Tuple[str, int, bool], kwargs) -> None:
+ self.assertTrue(False) # should not be reachable
+
+ with self.assertRaises(InvalidArguments) as cm:
+ _(None, mock.Mock(), ['string', 1], None)
+ self.assertEqual(str(cm.exception), 'foo takes exactly 3 arguments, but got 2.')
+
+ with self.assertRaises(InvalidArguments) as cm:
+ _(None, mock.Mock(), ['string', 1, True, True], None)
+ self.assertEqual(str(cm.exception), 'foo takes exactly 3 arguments, but got 4.')
+
+ def test_typed_pos_args_varargs(self) -> None:
+ @typed_pos_args('foo', str, varargs=str)
+ def _(obj, node, args: T.Tuple[str, T.List[str]], kwargs) -> None:
+ self.assertIsInstance(args, tuple)
+ self.assertIsInstance(args[0], str)
+ self.assertIsInstance(args[1], list)
+ self.assertIsInstance(args[1][0], str)
+ self.assertIsInstance(args[1][1], str)
+
+ _(None, mock.Mock(), ['string', 'var', 'args'], None)
+
+ def test_typed_pos_args_varargs_not_given(self) -> None:
+ @typed_pos_args('foo', str, varargs=str)
+ def _(obj, node, args: T.Tuple[str, T.List[str]], kwargs) -> None:
+ self.assertIsInstance(args, tuple)
+ self.assertIsInstance(args[0], str)
+ self.assertIsInstance(args[1], list)
+ self.assertEqual(args[1], [])
+
+ _(None, mock.Mock(), ['string'], None)
+
+ def test_typed_pos_args_varargs_invalid(self) -> None:
+ @typed_pos_args('foo', str, varargs=str)
+ def _(obj, node, args: T.Tuple[str, T.List[str]], kwargs) -> None:
+ self.assertTrue(False) # should not be reachable
+
+ with self.assertRaises(InvalidArguments) as cm:
+ _(None, mock.Mock(), ['string', 'var', 'args', 0], None)
+ self.assertEqual(str(cm.exception), 'foo argument 4 was of type "int" but should have been "str"')
+
+ def test_typed_pos_args_varargs_invalid_mulitple_types(self) -> None:
+ @typed_pos_args('foo', str, varargs=(str, list))
+ def _(obj, node, args: T.Tuple[str, T.List[str]], kwargs) -> None:
+ self.assertTrue(False) # should not be reachable
+
+ with self.assertRaises(InvalidArguments) as cm:
+ _(None, mock.Mock(), ['string', 'var', 'args', 0], None)
+ self.assertEqual(str(cm.exception), 'foo argument 4 was of type "int" but should have been one of: "str", "list"')
+
+ def test_typed_pos_args_max_varargs(self) -> None:
+ @typed_pos_args('foo', str, varargs=str, max_varargs=5)
+ def _(obj, node, args: T.Tuple[str, T.List[str]], kwargs) -> None:
+ self.assertIsInstance(args, tuple)
+ self.assertIsInstance(args[0], str)
+ self.assertIsInstance(args[1], list)
+ self.assertIsInstance(args[1][0], str)
+ self.assertIsInstance(args[1][1], str)
+
+ _(None, mock.Mock(), ['string', 'var', 'args'], None)
+
+ def test_typed_pos_args_max_varargs_exceeded(self) -> None:
+ @typed_pos_args('foo', str, varargs=str, max_varargs=1)
+ def _(obj, node, args: T.Tuple[str, T.Tuple[str, ...]], kwargs) -> None:
+ self.assertTrue(False) # should not be reachable
+
+ with self.assertRaises(InvalidArguments) as cm:
+ _(None, mock.Mock(), ['string', 'var', 'args'], None)
+ self.assertEqual(str(cm.exception), 'foo takes between 1 and 2 arguments, but got 3.')
+
+ def test_typed_pos_args_min_varargs(self) -> None:
+ @typed_pos_args('foo', varargs=str, max_varargs=2, min_varargs=1)
+ def _(obj, node, args: T.Tuple[str, T.List[str]], kwargs) -> None:
+ self.assertIsInstance(args, tuple)
+ self.assertIsInstance(args[0], list)
+ self.assertIsInstance(args[0][0], str)
+ self.assertIsInstance(args[0][1], str)
+
+ _(None, mock.Mock(), ['string', 'var'], None)
+
+ def test_typed_pos_args_min_varargs_not_met(self) -> None:
+ @typed_pos_args('foo', str, varargs=str, min_varargs=1)
+ def _(obj, node, args: T.Tuple[str, T.List[str]], kwargs) -> None:
+ self.assertTrue(False) # should not be reachable
+
+ with self.assertRaises(InvalidArguments) as cm:
+ _(None, mock.Mock(), ['string'], None)
+ self.assertEqual(str(cm.exception), 'foo takes at least 2 arguments, but got 1.')
+
+ def test_typed_pos_args_min_and_max_varargs_exceeded(self) -> None:
+ @typed_pos_args('foo', str, varargs=str, min_varargs=1, max_varargs=2)
+ def _(obj, node, args: T.Tuple[str, T.Tuple[str, ...]], kwargs) -> None:
+ self.assertTrue(False) # should not be reachable
+
+ with self.assertRaises(InvalidArguments) as cm:
+ _(None, mock.Mock(), ['string', 'var', 'args', 'bar'], None)
+ self.assertEqual(str(cm.exception), 'foo takes between 2 and 3 arguments, but got 4.')
+
+ def test_typed_pos_args_min_and_max_varargs_not_met(self) -> None:
+ @typed_pos_args('foo', str, varargs=str, min_varargs=1, max_varargs=2)
+ def _(obj, node, args: T.Tuple[str, T.Tuple[str, ...]], kwargs) -> None:
+ self.assertTrue(False) # should not be reachable
+
+ with self.assertRaises(InvalidArguments) as cm:
+ _(None, mock.Mock(), ['string'], None)
+ self.assertEqual(str(cm.exception), 'foo takes between 2 and 3 arguments, but got 1.')
+
+ def test_typed_pos_args_variadic_and_optional(self) -> None:
+ @typed_pos_args('foo', str, optargs=[str], varargs=str, min_varargs=0)
+ def _(obj, node, args: T.Tuple[str, T.List[str]], kwargs) -> None:
+ self.assertTrue(False) # should not be reachable
+
+ with self.assertRaises(AssertionError) as cm:
+ _(None, mock.Mock(), ['string'], None)
+ self.assertEqual(
+ str(cm.exception),
+ 'varargs and optargs not supported together as this would be ambiguous')
+
+ def test_typed_pos_args_min_optargs_not_met(self) -> None:
+ @typed_pos_args('foo', str, str, optargs=[str])
+ def _(obj, node, args: T.Tuple[str, T.Optional[str]], kwargs) -> None:
+ self.assertTrue(False) # should not be reachable
+
+ with self.assertRaises(InvalidArguments) as cm:
+ _(None, mock.Mock(), ['string'], None)
+ self.assertEqual(str(cm.exception), 'foo takes at least 2 arguments, but got 1.')
+
+ def test_typed_pos_args_min_optargs_max_exceeded(self) -> None:
+ @typed_pos_args('foo', str, optargs=[str])
+ def _(obj, node, args: T.Tuple[str, T.Optional[str]], kwargs) -> None:
+ self.assertTrue(False) # should not be reachable
+
+ with self.assertRaises(InvalidArguments) as cm:
+ _(None, mock.Mock(), ['string', '1', '2'], None)
+ self.assertEqual(str(cm.exception), 'foo takes at most 2 arguments, but got 3.')
+
+ def test_typed_pos_args_optargs_not_given(self) -> None:
+ @typed_pos_args('foo', str, optargs=[str])
+ def _(obj, node, args: T.Tuple[str, T.Optional[str]], kwargs) -> None:
+ self.assertEqual(len(args), 2)
+ self.assertIsInstance(args[0], str)
+ self.assertEqual(args[0], 'string')
+ self.assertIsNone(args[1])
+
+ _(None, mock.Mock(), ['string'], None)
+
+ def test_typed_pos_args_optargs_some_given(self) -> None:
+ @typed_pos_args('foo', str, optargs=[str, int])
+ def _(obj, node, args: T.Tuple[str, T.Optional[str], T.Optional[int]], kwargs) -> None:
+ self.assertEqual(len(args), 3)
+ self.assertIsInstance(args[0], str)
+ self.assertEqual(args[0], 'string')
+ self.assertIsInstance(args[1], str)
+ self.assertEqual(args[1], '1')
+ self.assertIsNone(args[2])
+
+ _(None, mock.Mock(), ['string', '1'], None)
+
+ def test_typed_pos_args_optargs_all_given(self) -> None:
+ @typed_pos_args('foo', str, optargs=[str])
+ def _(obj, node, args: T.Tuple[str, T.Optional[str]], kwargs) -> None:
+ self.assertEqual(len(args), 2)
+ self.assertIsInstance(args[0], str)
+ self.assertEqual(args[0], 'string')
+ self.assertIsInstance(args[1], str)
+
+ _(None, mock.Mock(), ['string', '1'], None)
+
@unittest.skipIf(is_tarball(), 'Skipping because this is a tarball release')
class DataTests(unittest.TestCase):
@@ -1459,6 +1648,7 @@ class DataTests(unittest.TestCase):
res = re.search(r'syn keyword mesonBuiltin(\s+\\\s\w+)+', f.read(), re.MULTILINE)
defined = set([a.strip() for a in res.group().split('\\')][1:])
self.assertEqual(defined, set(chain(interp.funcs.keys(), interp.builtin.keys())))
+
def test_all_functions_defined_in_ast_interpreter(self):
'''
Ensure that the all functions defined in the Interpreter are also defined
@@ -1490,7 +1680,6 @@ class DataTests(unittest.TestCase):
for p in i.iterdir():
data_files += [(p.relative_to(mesonbuild_dir).as_posix(), hashlib.sha256(p.read_bytes()).hexdigest())]
- from pprint import pprint
current_files = set(mesondata.keys())
scanned_files = set([x[0] for x in data_files])
@@ -2192,6 +2381,7 @@ class AllPlatformTests(BasePlatformTests):
testdir = os.path.join(self.common_test_dir, '52 run target')
self.init(testdir)
self.run_target('check_exists')
+ self.run_target('check-env')
def test_install_introspection(self):
'''
@@ -2270,11 +2460,13 @@ class AllPlatformTests(BasePlatformTests):
expected = {installpath: 0}
for name in installpath.rglob('*'):
expected[name] = 0
- # Find logged files and directories
- with Path(self.builddir, 'meson-logs', 'install-log.txt').open() as f:
- logged = list(map(lambda l: Path(l.strip()),
- filter(lambda l: not l.startswith('#'),
- f.readlines())))
+ def read_logs():
+ # Find logged files and directories
+ with Path(self.builddir, 'meson-logs', 'install-log.txt').open() as f:
+ return list(map(lambda l: Path(l.strip()),
+ filter(lambda l: not l.startswith('#'),
+ f.readlines())))
+ logged = read_logs()
for name in logged:
self.assertTrue(name in expected, 'Log contains extra entry {}'.format(name))
expected[name] += 1
@@ -2283,6 +2475,13 @@ class AllPlatformTests(BasePlatformTests):
self.assertGreater(count, 0, 'Log is missing entry for {}'.format(name))
self.assertLess(count, 2, 'Log has multiple entries for {}'.format(name))
+ # Verify that with --dry-run we obtain the same logs but with nothing
+ # 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.assertFalse(os.path.exists(self.installdir))
+
def test_uninstall(self):
exename = os.path.join(self.installdir, 'usr/bin/prog' + exe_suffix)
testdir = os.path.join(self.common_test_dir, '8 install')
@@ -2763,7 +2962,7 @@ class AllPlatformTests(BasePlatformTests):
for env_var in ['CPPFLAGS', 'CFLAGS']:
env = {}
env[env_var] = '-D{}="{}"'.format(define, value)
- env['LDFLAGS'] = '-DMESON_FAIL_VALUE=cflags-read'.format(define)
+ env['LDFLAGS'] = '-DMESON_FAIL_VALUE=cflags-read'
self.init(testdir, extra_args=['-D{}={}'.format(define, value)], override_envvars=env)
def test_custom_target_exe_data_deterministic(self):
@@ -2853,6 +3052,52 @@ class AllPlatformTests(BasePlatformTests):
self.build()
self.run_tests()
+ @skip_if_not_base_option('b_lto_threads')
+ def test_lto_threads(self):
+ testdir = os.path.join(self.common_test_dir, '6 linkshared')
+
+ env = get_fake_env(testdir, self.builddir, self.prefix)
+ cc = env.detect_c_compiler(MachineChoice.HOST)
+ if cc.get_id() == 'clang' and is_windows():
+ raise unittest.SkipTest('LTO not (yet) supported by windows clang')
+
+ self.init(testdir, extra_args=['-Db_lto=true', '-Db_lto_threads=8'])
+ self.build()
+ self.run_tests()
+
+ expected = set(cc.get_lto_compile_args(threads=8))
+ targets = self.introspect('--targets')
+ # This assumes all of the targets support lto
+ for t in targets:
+ for s in t['target_sources']:
+ for e in expected:
+ self.assertIn(e, s['parameters'])
+
+ @skip_if_not_base_option('b_lto_mode')
+ @skip_if_not_base_option('b_lto_threads')
+ def test_lto_mode(self):
+ testdir = os.path.join(self.common_test_dir, '6 linkshared')
+
+ env = get_fake_env(testdir, self.builddir, self.prefix)
+ cc = env.detect_c_compiler(MachineChoice.HOST)
+ if cc.get_id() != 'clang':
+ raise unittest.SkipTest('Only clang currently supports thinLTO')
+ if cc.linker.id not in {'ld.lld', 'ld.gold', 'ld64', 'lld-link'}:
+ raise unittest.SkipTest('thinLTO requires ld.lld, ld.gold, ld64, or lld-link')
+ elif is_windows():
+ raise unittest.SkipTest('LTO not (yet) supported by windows clang')
+
+ self.init(testdir, extra_args=['-Db_lto=true', '-Db_lto_mode=thin', '-Db_lto_threads=8'])
+ self.build()
+ self.run_tests()
+
+ expected = set(cc.get_lto_compile_args(threads=8, mode='thin'))
+ targets = self.introspect('--targets')
+ # This assumes all of the targets support lto
+ for t in targets:
+ for s in t['target_sources']:
+ assert expected.issubset(set(s['parameters'])), f'Incorrect values for {t["name"]}'
+
def test_dist_git(self):
if not shutil.which('git'):
raise unittest.SkipTest('Git not found')
@@ -2882,7 +3127,6 @@ class AllPlatformTests(BasePlatformTests):
except FileNotFoundError:
return False
-
def test_dist_hg(self):
if not self.has_working_hg():
raise unittest.SkipTest('Mercurial not found or broken.')
@@ -6372,7 +6616,7 @@ class LinuxlikeTests(BasePlatformTests):
elif compiler.language == 'cpp':
env_flag_name = 'CXXFLAGS'
else:
- raise NotImplementedError('Language {} not defined.'.format(p))
+ raise NotImplementedError('Language {} not defined.'.format(compiler.language))
env = {}
env[env_flag_name] = cmd_std
with self.assertRaises((subprocess.CalledProcessError, mesonbuild.mesonlib.EnvironmentException),
@@ -7615,22 +7859,32 @@ class LinuxCrossMingwTests(BaseLinuxCrossTests):
self.meson_cross_file = os.path.join(testdir, 'broken-cross.txt')
# Force tracebacks so we can detect them properly
env = {'MESON_FORCE_BACKTRACE': '1'}
- with self.assertRaisesRegex(MesonException, 'exe_wrapper.*target.*use-exe-wrapper'):
+ error_message = "An exe_wrapper is needed but was not found. Please define one in cross file and check the command and/or add it to PATH."
+ error_message2 = "The exe_wrapper 'broken' defined in the cross file is needed by run target 'run-prog', but was not found. Please check the command and/or add it to PATH."
+
+ with self.assertRaises(MesonException) as cm:
# Must run in-process or we'll get a generic CalledProcessError
self.init(testdir, extra_args='-Drun-target=false',
inprocess=True,
override_envvars=env)
- with self.assertRaisesRegex(MesonException, 'exe_wrapper.*run target.*run-prog'):
+ self.assertEqual(str(cm.exception), error_message)
+
+ with self.assertRaises(MesonException) as cm:
# Must run in-process or we'll get a generic CalledProcessError
self.init(testdir, extra_args='-Dcustom-target=false',
inprocess=True,
override_envvars=env)
+ self.assertEqual(str(cm.exception), error_message2)
+
self.init(testdir, extra_args=['-Dcustom-target=false', '-Drun-target=false'],
override_envvars=env)
self.build()
- with self.assertRaisesRegex(MesonException, 'exe_wrapper.*PATH'):
+
+ with self.assertRaises(MesonException) as cm:
# Must run in-process or we'll get a generic CalledProcessError
self.run_tests(inprocess=True, override_envvars=env)
+ self.assertEqual(str(cm.exception),
+ "The exe_wrapper defined in the cross file 'broken' was not found. Please check the command and/or add it to PATH.")
@skipIfNoPkgconfig
def test_cross_pkg_config_option(self):
@@ -8577,22 +8831,6 @@ class NativeFileTests(BasePlatformTests):
else:
self.fail('Did not find bindir in build options?')
- def test_builtin_options_paths_legacy(self):
- testcase = os.path.join(self.common_test_dir, '1 trivial')
- config = self.helper_create_native_file({
- 'built-in options': {'default_library': 'static'},
- 'paths': {'bindir': 'bar'},
- })
-
- self.init(testcase, extra_args=['--native-file', config])
- configuration = self.introspect('--buildoptions')
- for each in configuration:
- if each['name'] == 'bindir':
- self.assertEqual(each['value'], 'bar')
- break
- else:
- self.fail('Did not find bindir in build options?')
-
class CrossFileTests(BasePlatformTests):
diff --git a/test cases/cmake/15 object library advanced/meson.build b/test cases/cmake/15 object library advanced/meson.build
index 6a4448b..4009a0d 100644
--- a/test cases/cmake/15 object library advanced/meson.build
+++ b/test cases/cmake/15 object library advanced/meson.build
@@ -1,5 +1,9 @@
project('cmake_object_lib_test', 'cpp', default_options: ['cpp_std=c++11'])
+if meson.is_cross_build()
+ error('MESON_SKIP_TEST this test does not cross compile correctly.')
+endif
+
cm = import('cmake')
sub_pro = cm.subproject('cmObjLib')
diff --git a/test cases/cmake/4 code gen/meson.build b/test cases/cmake/4 code gen/meson.build
index 592f903..80c801f 100644
--- a/test cases/cmake/4 code gen/meson.build
+++ b/test cases/cmake/4 code gen/meson.build
@@ -1,5 +1,9 @@
project('cmake_code_gen', ['c', 'cpp'])
+if meson.is_cross_build()
+ error('MESON_SKIP_TEST this test does not cross compile correctly.')
+endif
+
cm = import('cmake')
# Subproject with the "code generator"
diff --git a/test cases/cmake/8 custom command/meson.build b/test cases/cmake/8 custom command/meson.build
index 799e339..a262252 100644
--- a/test cases/cmake/8 custom command/meson.build
+++ b/test cases/cmake/8 custom command/meson.build
@@ -1,5 +1,9 @@
project('cmakeSubTest', ['c', 'cpp'])
+if meson.is_cross_build()
+ error('MESON_SKIP_TEST this test does not cross compile correctly.')
+endif
+
cm = import('cmake')
sub_pro = cm.subproject('cmMod')
diff --git a/test cases/common/241 get_file_contents/.gitattributes b/test cases/common/241 get_file_contents/.gitattributes
new file mode 100644
index 0000000..abec47d
--- /dev/null
+++ b/test cases/common/241 get_file_contents/.gitattributes
@@ -0,0 +1 @@
+utf-16-text binary
diff --git a/test cases/common/241 get_file_contents/VERSION b/test cases/common/241 get_file_contents/VERSION
new file mode 100644
index 0000000..26aaba0
--- /dev/null
+++ b/test cases/common/241 get_file_contents/VERSION
@@ -0,0 +1 @@
+1.2.0
diff --git a/test cases/common/241 get_file_contents/meson.build b/test cases/common/241 get_file_contents/meson.build
new file mode 100644
index 0000000..a8c68d6
--- /dev/null
+++ b/test cases/common/241 get_file_contents/meson.build
@@ -0,0 +1,21 @@
+project(
+ 'meson-fs-read-file',
+ [],
+ version: files('VERSION')
+)
+fs = import('fs')
+
+assert(fs.read('VERSION').strip() == meson.project_version(), 'file misread')
+
+expected = (
+ '∮ E⋅da = Q, n → ∞, ∑ f(i) = ∏ g(i), ∀x∈ℝ: ⌈x⌉ = −⌊−x⌋, α ∧ ¬β = ¬(¬α ∨ β)'
+)
+assert(
+ fs.read('utf-16-text', encoding: 'utf-16').strip() == expected,
+ 'file was not decoded correctly'
+)
+
+# Make sure we handle `files()` objects properly, too
+version_file = files('VERSION')
+
+subdir('other')
diff --git a/test cases/common/241 get_file_contents/other/meson.build b/test cases/common/241 get_file_contents/other/meson.build
new file mode 100644
index 0000000..9a7e4be
--- /dev/null
+++ b/test cases/common/241 get_file_contents/other/meson.build
@@ -0,0 +1,3 @@
+fs = import('fs')
+assert(fs.read(version_file).strip() == '1.2.0')
+assert(fs.read('../VERSION').strip() == '1.2.0')
diff --git a/test cases/common/241 get_file_contents/utf-16-text b/test cases/common/241 get_file_contents/utf-16-text
new file mode 100644
index 0000000..ed1fefe
--- /dev/null
+++ b/test cases/common/241 get_file_contents/utf-16-text
Binary files differ
diff --git a/test cases/common/52 run target/check-env.py b/test cases/common/52 run target/check-env.py
new file mode 100644
index 0000000..8df3e28
--- /dev/null
+++ b/test cases/common/52 run target/check-env.py
@@ -0,0 +1,9 @@
+#!/usr/bin/env python3
+
+import os
+
+assert 'MESON_SOURCE_ROOT' in os.environ
+assert 'MESON_BUILD_ROOT' in os.environ
+assert 'MESON_SUBDIR' in os.environ
+assert 'MESONINTROSPECT' in os.environ
+assert 'MY_ENV' in os.environ
diff --git a/test cases/common/52 run target/meson.build b/test cases/common/52 run target/meson.build
index 9abe698..a28d218 100644
--- a/test cases/common/52 run target/meson.build
+++ b/test cases/common/52 run target/meson.build
@@ -72,3 +72,9 @@ run_target('ctags',
run_target('clang-format',
command : converter)
+
+# Check we can pass env to the program
+run_target('check-env',
+ command: [find_program('check-env.py')],
+ env: {'MY_ENV': '1'},
+)