aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/os_comp.yml1
-rw-r--r--docs/markdown/Gnome-module.md17
-rw-r--r--docs/markdown/Reference-tables.md1
-rw-r--r--docs/markdown/snippets/gnome_install_script.md9
-rw-r--r--docs/markdown/snippets/vala_unity_builds_disabled.md7
-rw-r--r--mesonbuild/backend/backends.py13
-rw-r--r--mesonbuild/backend/ninjabackend.py48
-rw-r--r--mesonbuild/backend/xcodebackend.py13
-rw-r--r--mesonbuild/dependencies/cuda.py2
-rw-r--r--mesonbuild/envconfig.py2
-rw-r--r--mesonbuild/interpreter.py14
-rw-r--r--mesonbuild/interpreterbase.py126
-rw-r--r--mesonbuild/modules/gnome.py85
-rw-r--r--mesonbuild/modules/unstable_rust.py15
-rwxr-xr-xrun_unittests.py232
-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
18 files changed, 500 insertions, 97 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/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-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/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/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 d96ac9a..e19afca 100644
--- a/mesonbuild/backend/backends.py
+++ b/mesonbuild/backend/backends.py
@@ -47,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):
@@ -247,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
diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py
index fcc7128..3eca3c0 100644
--- a/mesonbuild/backend/ninjabackend.py
+++ b/mesonbuild/backend/ninjabackend.py
@@ -811,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
@@ -1357,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.
@@ -1366,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():
@@ -1410,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 = []
@@ -2295,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/xcodebackend.py b/mesonbuild/backend/xcodebackend.py
index eb0db1f..7ee4e80 100644
--- a/mesonbuild/backend/xcodebackend.py
+++ b/mesonbuild/backend/xcodebackend.py
@@ -734,8 +734,10 @@ 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 = []
- cargs.append('-I.')
for dep in target.get_external_deps():
cargs += dep.get_compile_args()
ldargs += dep.get_link_args()
@@ -753,8 +755,13 @@ class XCodeBackend(backends.Backend):
targs = target.get_extra_args(lang)
args = pargs + gargs + targs
if args:
- langargs[langnamemap[lang]] = args
- langargs[langnamemap[lang]] += 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/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/interpreter.py b/mesonbuild/interpreter.py
index cf147c7..f3d7502 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())
@@ -3365,8 +3366,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:
@@ -3983,6 +3984,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')
@@ -4023,12 +4025,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
@@ -4412,9 +4411,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/modules/gnome.py b/mesonbuild/modules/gnome.py
index 48b80e0..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)
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/run_unittests.py b/run_unittests.py
index 857ce6f..7981df6 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])
@@ -2763,7 +2952,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):
@@ -2928,7 +3117,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.')
@@ -6418,7 +6606,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),
@@ -7661,22 +7849,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):
@@ -8623,22 +8821,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')