diff options
-rw-r--r-- | .github/workflows/os_comp.yml | 1 | ||||
-rw-r--r-- | docs/markdown/Gnome-module.md | 17 | ||||
-rw-r--r-- | docs/markdown/Reference-tables.md | 1 | ||||
-rw-r--r-- | docs/markdown/snippets/gnome_install_script.md | 9 | ||||
-rw-r--r-- | docs/markdown/snippets/vala_unity_builds_disabled.md | 7 | ||||
-rw-r--r-- | mesonbuild/backend/backends.py | 13 | ||||
-rw-r--r-- | mesonbuild/backend/ninjabackend.py | 48 | ||||
-rw-r--r-- | mesonbuild/backend/xcodebackend.py | 13 | ||||
-rw-r--r-- | mesonbuild/dependencies/cuda.py | 2 | ||||
-rw-r--r-- | mesonbuild/envconfig.py | 2 | ||||
-rw-r--r-- | mesonbuild/interpreter.py | 14 | ||||
-rw-r--r-- | mesonbuild/interpreterbase.py | 126 | ||||
-rw-r--r-- | mesonbuild/modules/gnome.py | 85 | ||||
-rw-r--r-- | mesonbuild/modules/unstable_rust.py | 15 | ||||
-rwxr-xr-x | run_unittests.py | 232 | ||||
-rw-r--r-- | test cases/cmake/15 object library advanced/meson.build | 4 | ||||
-rw-r--r-- | test cases/cmake/4 code gen/meson.build | 4 | ||||
-rw-r--r-- | test cases/cmake/8 custom command/meson.build | 4 |
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') |