From 77db04ffa6c37496f5a800f2f5cd5a0c19e47d8b Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Thu, 16 May 2024 20:46:45 +0200 Subject: [PATCH 01/91] Fix handling of C standard support for Emscripten. Emscripten version numbers are unrelated to Clang version numbers, so it is necessary to change the version checks for `c_std=c17` & co. Without that, no project that defaults to C17 or newer will build with Emscripten. See https://github.com/pyodide/pyodide/discussions/4762 for more context. Also note that this bug caused defaulting to C17 in scikit-learn to be reverted (scikit-learn#29015), and it may be a problem for SciPy 1.14.0 too since that release will upgrade from C99 to C17. Co-authored-by: Loic Esteve --- mesonbuild/compilers/c.py | 10 ++++++++++ test cases/wasm/1 basic/meson.build | 7 ++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/mesonbuild/compilers/c.py b/mesonbuild/compilers/c.py index 7e2146111563..18b25d46d464 100644 --- a/mesonbuild/compilers/c.py +++ b/mesonbuild/compilers/c.py @@ -201,6 +201,16 @@ class EmscriptenCCompiler(EmscriptenMixin, ClangCCompiler): id = 'emscripten' + # Emscripten uses different version numbers than Clang; `emcc -v` will show + # the Clang version number used as well (but `emcc --version` does not). + # See https://github.com/pyodide/pyodide/discussions/4762 for more on + # emcc <--> clang versions. Note that c17/c18/c2x are always available, since + # the lowest supported Emscripten version used a new-enough Clang version. + _C17_VERSION = '>=1.38.35' + _C18_VERSION = '>=1.38.35' + _C2X_VERSION = '>=1.38.35' # 1.38.35 used Clang 9.0.0 + _C23_VERSION = '>=3.0.0' # 3.0.0 used Clang 18.0.0 + def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool, info: 'MachineInfo', linker: T.Optional['DynamicLinker'] = None, diff --git a/test cases/wasm/1 basic/meson.build b/test cases/wasm/1 basic/meson.build index 1092a9ba7e85..d27599271b1c 100644 --- a/test cases/wasm/1 basic/meson.build +++ b/test cases/wasm/1 basic/meson.build @@ -1,4 +1,9 @@ -project('emcctest', 'c', 'cpp') +project('emcctest', 'c', 'cpp', + default_options: [ + 'c_std=c17', + 'cpp_std=c++17', + ] +) executable('hello-c', 'hello.c') executable('hello', 'hello.cpp') From 128f0e828e425793203f3112c23cb31f959b4b3e Mon Sep 17 00:00:00 2001 From: Jussi Pakkanen Date: Sun, 19 May 2024 20:58:46 +0300 Subject: [PATCH 02/91] Pass wrapmode value around as a string. (#13229) --- mesonbuild/build.py | 7 ++----- mesonbuild/coredata.py | 7 +------ mesonbuild/interpreter/dependencyfallbacks.py | 3 +-- mesonbuild/interpreter/interpreter.py | 4 ++-- mesonbuild/modules/__init__.py | 3 +-- 5 files changed, 7 insertions(+), 17 deletions(-) diff --git a/mesonbuild/build.py b/mesonbuild/build.py index da4c4373d551..d8afd1bc6157 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -46,7 +46,6 @@ from .mesonlib import ExecutableSerialisation, FileMode, FileOrString from .modules import ModuleState from .mparser import BaseNode - from .wrap import WrapMode GeneratedTypes = T.Union['CustomTarget', 'CustomTargetIndex', 'GeneratedList'] LibTypes = T.Union['SharedLibrary', 'StaticLibrary', 'CustomTarget', 'CustomTargetIndex'] @@ -662,12 +661,10 @@ def set_option_overrides(self, option_overrides: T.Dict[OptionKey, str]) -> None def get_options(self) -> coredata.OptionsView: return self.options - def get_option(self, key: 'OptionKey') -> T.Union[str, int, bool, 'WrapMode']: - # We don't actually have wrapmode here to do an assert, so just do a - # cast, we know what's in coredata anyway. + def get_option(self, key: 'OptionKey') -> T.Union[str, int, bool]: # TODO: if it's possible to annotate get_option or validate_option_value # in the future we might be able to remove the cast here - return T.cast('T.Union[str, int, bool, WrapMode]', self.options[key].value) + return T.cast('T.Union[str, int, bool]', self.options[key].value) @staticmethod def parse_overrides(kwargs: T.Dict[str, T.Any]) -> T.Dict[OptionKey, str]: diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index 7213115603a2..6e67587b27a0 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -23,7 +23,6 @@ listify_array_value, OptionKey, OptionType, stringlistify, pickle_load ) -from .wrap import WrapMode import ast import argparse import configparser @@ -749,11 +748,9 @@ def init_backend_options(self, backend_name: str) -> None: 'Default project to execute in Visual Studio', '') - def get_option(self, key: OptionKey) -> T.Union[T.List[str], str, int, bool, WrapMode]: + def get_option(self, key: OptionKey) -> T.Union[T.List[str], str, int, bool]: try: v = self.options[key].value - if key.name == 'wrap_mode': - return WrapMode[v] return v except KeyError: pass @@ -761,8 +758,6 @@ def get_option(self, key: OptionKey) -> T.Union[T.List[str], str, int, bool, Wra try: v = self.options[key.as_root()] if v.yielding: - if key.name == 'wrap_mode': - return WrapMode[v.value] return v.value except KeyError: pass diff --git a/mesonbuild/interpreter/dependencyfallbacks.py b/mesonbuild/interpreter/dependencyfallbacks.py index eca6a2c71796..d5e0740e0974 100644 --- a/mesonbuild/interpreter/dependencyfallbacks.py +++ b/mesonbuild/interpreter/dependencyfallbacks.py @@ -315,8 +315,7 @@ def lookup(self, kwargs: TYPE_nkwargs, force_fallback: bool = False) -> Dependen return self._notfound_dependency() # Check if usage of the subproject fallback is forced - wrap_mode = self.coredata.get_option(OptionKey('wrap_mode')) - assert isinstance(wrap_mode, WrapMode), 'for mypy' + wrap_mode = WrapMode.from_string(self.coredata.get_option(OptionKey('wrap_mode'))) force_fallback_for = self.coredata.get_option(OptionKey('force_fallback_for')) assert isinstance(force_fallback_for, list), 'for mypy' self.nofallback = wrap_mode == WrapMode.nofallback diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index 08bd054f20f0..50780bab2149 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -1284,7 +1284,7 @@ def func_project(self, node: mparser.FunctionNode, args: T.Tuple[str, T.List[str self.build.subproject_dir = self.subproject_dir # Load wrap files from this (sub)project. - wrap_mode = self.coredata.get_option(OptionKey('wrap_mode')) + wrap_mode = WrapMode.from_string(self.coredata.get_option(OptionKey('wrap_mode'))) if not self.is_subproject() or wrap_mode != WrapMode.nopromote: subdir = os.path.join(self.subdir, spdirname) r = wrap.Resolver(self.environment.get_source_dir(), subdir, self.subproject, wrap_mode) @@ -1679,7 +1679,7 @@ def program_lookup(self, args: T.List[mesonlib.FileOrString], for_machine: Machi return ExternalProgram('meson', self.environment.get_build_command(), silent=True) fallback = None - wrap_mode = self.coredata.get_option(OptionKey('wrap_mode')) + wrap_mode = WrapMode.from_string(self.coredata.get_option(OptionKey('wrap_mode'))) if wrap_mode != WrapMode.nofallback and self.environment.wrap_resolver: fallback = self.environment.wrap_resolver.find_program_provider(args) if fallback and wrap_mode == WrapMode.forcefallback: diff --git a/mesonbuild/modules/__init__.py b/mesonbuild/modules/__init__.py index 8d6b20f5a3b5..046c530a8404 100644 --- a/mesonbuild/modules/__init__.py +++ b/mesonbuild/modules/__init__.py @@ -18,7 +18,6 @@ from ..interpreter.interpreter import ProgramVersionFunc from ..interpreterbase import TYPE_var, TYPE_kwargs from ..programs import OverrideProgram - from ..wrap import WrapMode from ..dependencies import Dependency class ModuleState: @@ -134,7 +133,7 @@ def test(self, args: T.Tuple[str, T.Union[build.Executable, build.Jar, 'External def get_option(self, name: str, subproject: str = '', machine: MachineChoice = MachineChoice.HOST, lang: T.Optional[str] = None, - module: T.Optional[str] = None) -> T.Union[T.List[str], str, int, bool, 'WrapMode']: + module: T.Optional[str] = None) -> T.Union[T.List[str], str, int, bool]: return self.environment.coredata.get_option(mesonlib.OptionKey(name, subproject, machine, lang, module)) def is_user_defined_option(self, name: str, subproject: str = '', From 29a62ff794df3d72616740be9723b87f6b491722 Mon Sep 17 00:00:00 2001 From: Eli Schwartz Date: Thu, 16 May 2024 19:24:07 -0400 Subject: [PATCH 03/91] pylint: fix false positive for missing else branch We cover every case as if/elif/elif. mypy can handle this fine, but pylint doesn't do control flow or type checking and thinks in the missing else case, the variable might not be defined. For mypy as well, doing this instance check is unnecessary as it can be inferred. So just micro-optimize the check and allow pylint to safely analyze the logic. --- mesonbuild/linkers/detect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mesonbuild/linkers/detect.py b/mesonbuild/linkers/detect.py index 65d77f6424c8..1db3948c1c59 100644 --- a/mesonbuild/linkers/detect.py +++ b/mesonbuild/linkers/detect.py @@ -45,7 +45,7 @@ def guess_win_linker(env: 'Environment', compiler: T.List[str], comp_class: T.Ty check_args = ['/logo', '--version'] elif isinstance(comp_class.LINKER_PREFIX, str): check_args = [comp_class.LINKER_PREFIX + '/logo', comp_class.LINKER_PREFIX + '--version'] - elif isinstance(comp_class.LINKER_PREFIX, list): + else: # list check_args = comp_class.LINKER_PREFIX + ['/logo'] + comp_class.LINKER_PREFIX + ['--version'] check_args += env.coredata.get_external_link_args(for_machine, comp_class.language) From e343590e7d59b4bfd6bc4913ac20ac9c46e702a4 Mon Sep 17 00:00:00 2001 From: Eli Schwartz Date: Thu, 16 May 2024 19:27:34 -0400 Subject: [PATCH 04/91] pylint: ignore possibly/used-before-assignment as it is prone to FP It does no control flow analysis, and upgrading to pylint 3.2.0 produced many incorrect warnings. Also ignore contextmanager-generator-missing-cleanup for now. It has FP when there is no code after a yield (and thus no cleanup needs to be handled), which is what we do. Currently under discussion upstream: https://github.com/pylint-dev/pylint/issues/9625 --- .pylintrc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.pylintrc b/.pylintrc index b457544a6226..64316fe6e70e 100644 --- a/.pylintrc +++ b/.pylintrc @@ -17,6 +17,7 @@ disable= cell-var-from-loop, consider-using-f-string, consider-using-with, + contextmanager-generator-missing-cleanup, cyclic-import, deprecated-decorator, duplicate-code, @@ -47,6 +48,7 @@ disable= not-an-iterable, not-callable, pointless-string-statement, + possibly-used-before-assignment, protected-access, raise-missing-from, redeclared-assigned-name, @@ -75,6 +77,7 @@ disable= unsubscriptable-object, unused-argument, unused-variable, + used-before-assignment, useless-super-delegation, wrong-import-order, wrong-import-position, From 125d3344ec3361b2fb6d35c6c75c3f6d4f500556 Mon Sep 17 00:00:00 2001 From: Eli Schwartz Date: Thu, 16 May 2024 19:36:32 -0400 Subject: [PATCH 05/91] pylint: fix false positive for variable defined in different copies of conditional We do: ``` if is_thing: assignme = value if_is_thing: ... else: assignme = [] ``` It is always defined on both sides, but there was no particular reason we had to assign it in the later copy. pylint reported it as a false positive, and it may prove confusing in general, and it's harmless to move, so do so. --- mesonbuild/modules/gnome.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py index 410bf7411a36..4405c40cc4a8 100644 --- a/mesonbuild/modules/gnome.py +++ b/mesonbuild/modules/gnome.py @@ -440,6 +440,8 @@ def compile_resources(self, state: 'ModuleState', args: T.Tuple[str, 'FileOrStri depend_files, depends, subdirs = self._get_gresource_dependencies( state, ifile, source_dirs, dependencies) + else: + depend_files = [] # Make source dirs relative to build dir now source_dirs = [os.path.join(state.build_to_src, state.subdir, d) for d in source_dirs] @@ -492,7 +494,6 @@ def compile_resources(self, state: 'ModuleState', args: T.Tuple[str, 'FileOrStri target_cmd = cmd else: depfile = f'{output}.d' - depend_files = [] target_cmd = copy.copy(cmd) + ['--dependency-file', '@DEPFILE@'] target_c = GResourceTarget( name, From aa9b7b9445ad5bf0948cadac86109ba40e193424 Mon Sep 17 00:00:00 2001 From: Eli Schwartz Date: Thu, 16 May 2024 20:17:19 -0400 Subject: [PATCH 06/91] pylint: fix useless-return A bare return at the end of a function doesn't do anything. And in the case of a try/except, it's really just an elaborate "pass" anyway. --- mesonbuild/ast/introspection.py | 2 +- mesonbuild/dependencies/misc.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/mesonbuild/ast/introspection.py b/mesonbuild/ast/introspection.py index 5bf7e051a168..fa11feb0873e 100644 --- a/mesonbuild/ast/introspection.py +++ b/mesonbuild/ast/introspection.py @@ -145,7 +145,7 @@ def do_subproject(self, dirname: SubProject) -> None: subi.project_data['name'] = dirname self.project_data['subprojects'] += [subi.project_data] except (mesonlib.MesonException, RuntimeError): - return + pass def func_add_languages(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> None: kwargs = self.flatten_kwargs(kwargs) diff --git a/mesonbuild/dependencies/misc.py b/mesonbuild/dependencies/misc.py index fd5965233dc6..72c7cf0a8825 100644 --- a/mesonbuild/dependencies/misc.py +++ b/mesonbuild/dependencies/misc.py @@ -422,7 +422,6 @@ def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any]): if self.static: if not self._add_sub_dependency(iconv_factory(env, self.for_machine, {'static': True})): self.is_found = False - return class OpensslSystemDependency(SystemDependency): From 8d7ffe6e863834f0190eb6ae9dfe0891c9083c31 Mon Sep 17 00:00:00 2001 From: Axel Ricard Date: Tue, 21 May 2024 14:22:40 +0200 Subject: [PATCH 07/91] fix sanity check for d cross-compilation --- mesonbuild/compilers/d.py | 14 ++++++++++++-- mesonbuild/compilers/detect.py | 3 ++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/mesonbuild/compilers/d.py b/mesonbuild/compilers/d.py index de344c05781a..46cffdd0fc43 100644 --- a/mesonbuild/compilers/d.py +++ b/mesonbuild/compilers/d.py @@ -443,10 +443,18 @@ def sanity_check(self, work_dir: str, environment: 'Environment') -> None: output_name = os.path.join(work_dir, 'dtest') with open(source_name, 'w', encoding='utf-8') as ofile: ofile.write('''void main() { }''') - pc = subprocess.Popen(self.exelist + self.get_output_args(output_name) + self._get_target_arch_args() + [source_name], cwd=work_dir) + + compile_cmdlist = self.exelist + self.get_output_args(output_name) + self._get_target_arch_args() + [source_name] + + # If cross-compiling, we can't run the sanity check, only compile it. + if environment.need_exe_wrapper(self.for_machine) and not environment.has_exe_wrapper(): + compile_cmdlist += self.get_compile_only_args() + + pc = subprocess.Popen(compile_cmdlist, cwd=work_dir) pc.wait() if pc.returncode != 0: raise EnvironmentException('D compiler %s cannot compile programs.' % self.name_string()) + if environment.need_exe_wrapper(self.for_machine): if not environment.has_exe_wrapper(): # Can't check if the binaries run so we have to assume they do @@ -545,7 +553,9 @@ def _get_target_arch_args(self) -> T.List[str]: # LDC2 on Windows targets to current OS architecture, but # it should follow the target specified by the MSVC toolchain. if self.info.is_windows(): - if self.arch == 'x86_64': + if self.is_cross: + return [f'-mtriple={self.arch}-windows-msvc'] + elif self.arch == 'x86_64': return ['-m64'] return ['-m32'] return [] diff --git a/mesonbuild/compilers/detect.py b/mesonbuild/compilers/detect.py index 90a3ac597ebb..62187b9c2448 100644 --- a/mesonbuild/compilers/detect.py +++ b/mesonbuild/compilers/detect.py @@ -1161,7 +1161,8 @@ def detect_d_compiler(env: 'Environment', for_machine: MachineChoice) -> Compile return cls( exelist, version, for_machine, info, arch, - full_version=full_version, linker=linker, version_output=out) + full_version=full_version, linker=linker, + is_cross=is_cross, version_output=out) elif 'gdc' in out: cls = d.GnuDCompiler linker = guess_nix_linker(env, exelist, cls, version, for_machine) From 4c6d370a0ea5e705c32ee50bb06b1ca7d42b3548 Mon Sep 17 00:00:00 2001 From: Axel Ricard Date: Tue, 21 May 2024 14:23:37 +0200 Subject: [PATCH 08/91] add cross-compile argument for ldc linker guessing --- mesonbuild/compilers/detect.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mesonbuild/compilers/detect.py b/mesonbuild/compilers/detect.py index 62187b9c2448..d6aeff93a182 100644 --- a/mesonbuild/compilers/detect.py +++ b/mesonbuild/compilers/detect.py @@ -1143,11 +1143,15 @@ def detect_d_compiler(env: 'Environment', for_machine: MachineChoice) -> Compile try: if info.is_windows() or info.is_cygwin(): objfile = os.path.basename(f)[:-1] + 'obj' + extra_args = [f] + if is_cross: + extra_args.append(f'-mtriple={info.cpu}-windows') + linker = guess_win_linker(env, exelist, cls, full_version, for_machine, use_linker_prefix=True, invoked_directly=False, - extra_args=[f]) + extra_args=extra_args) else: # LDC writes an object file to the current working directory. # Clean it up. From 4861079360423298b7c282becab46bdcdee8f8f6 Mon Sep 17 00:00:00 2001 From: David Seifert Date: Wed, 22 May 2024 15:55:55 +0200 Subject: [PATCH 09/91] cuda: fix `cuda.find_library()` hardcoded to yield true * Previously, cuda would just plainly prepend `-l` to the libname. * By relying on the host compiler to find libraries, we now get more subtle failures, such as CUDA modules not being found anymore. * We need to simplify these CUDA modules when nvcc is used for linking, since this may have side-effects from the cuda toolchain. Closes: #13240 --- mesonbuild/compilers/compilers.py | 2 +- mesonbuild/compilers/cuda.py | 2 +- mesonbuild/dependencies/cuda.py | 43 ++++++++++++++++++++----------- 3 files changed, 30 insertions(+), 17 deletions(-) diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index 44b998a67cda..673b63f9d581 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -1046,7 +1046,7 @@ def get_dependency_compile_args(self, dep: 'Dependency') -> T.List[str]: return dep.get_compile_args() def get_dependency_link_args(self, dep: 'Dependency') -> T.List[str]: - return dep.get_link_args() + return dep.get_link_args(self.get_language()) @classmethod def use_linker_args(cls, linker: str, version: str) -> T.List[str]: diff --git a/mesonbuild/compilers/cuda.py b/mesonbuild/compilers/cuda.py index 3761019b9945..7231a267c02b 100644 --- a/mesonbuild/compilers/cuda.py +++ b/mesonbuild/compilers/cuda.py @@ -768,7 +768,7 @@ def get_std_exe_link_args(self) -> T.List[str]: def find_library(self, libname: str, env: 'Environment', extra_dirs: T.List[str], libtype: LibType = LibType.PREFER_SHARED, lib_prefix_warning: bool = True) -> T.Optional[T.List[str]]: - return ['-l' + libname] # FIXME + return self.host_compiler.find_library(libname, env, extra_dirs, libtype, lib_prefix_warning) def get_crt_compile_args(self, crt_val: str, buildtype: str) -> T.List[str]: return self._to_host_flags(self.host_compiler.get_crt_compile_args(crt_val, buildtype)) diff --git a/mesonbuild/dependencies/cuda.py b/mesonbuild/dependencies/cuda.py index a38e325b8741..82bf5ad82b94 100644 --- a/mesonbuild/dependencies/cuda.py +++ b/mesonbuild/dependencies/cuda.py @@ -41,11 +41,6 @@ def __init__(self, environment: 'Environment', kwargs: T.Dict[str, T.Any]) -> No req_modules = ['cudart'] if kwargs.get('static', True): req_modules = ['cudart_static'] - machine = self.env.machines[self.for_machine] - if machine.is_linux(): - # extracted by running - # nvcc -v foo.o - req_modules += ['rt', 'pthread', 'dl'] self.requested_modules = req_modules + self.requested_modules (self.cuda_path, self.version, self.is_found) = self._detect_cuda_path_and_version() @@ -61,12 +56,9 @@ def __init__(self, environment: 'Environment', kwargs: T.Dict[str, T.Any]) -> No self.incdir = os.path.join(self.cuda_path, 'include') self.compile_args += [f'-I{self.incdir}'] - if self.language != 'cuda': - arch_libdir = self._detect_arch_libdir() - self.libdir = os.path.join(self.cuda_path, arch_libdir) - mlog.debug('CUDA library directory is', mlog.bold(self.libdir)) - else: - self.libdir = None + arch_libdir = self._detect_arch_libdir() + self.libdir = os.path.join(self.cuda_path, arch_libdir) + mlog.debug('CUDA library directory is', mlog.bold(self.libdir)) self.is_found = self._find_requested_libraries() @@ -244,7 +236,14 @@ def _find_requested_libraries(self) -> bool: all_found = True for module in self.requested_modules: - args = self.clib_compiler.find_library(module, self.env, [self.libdir] if self.libdir else []) + args = self.clib_compiler.find_library(module, self.env, [self.libdir]) + if module == 'cudart_static' and self.language != 'cuda': + machine = self.env.machines[self.for_machine] + if machine.is_linux(): + # extracted by running + # nvcc -v foo.o + args += ['-lrt', '-lpthread', '-ldl'] + if args is None: self._report_dependency_error(f'Couldn\'t find requested CUDA module \'{module}\'') all_found = False @@ -286,10 +285,24 @@ def get_requested(self, kwargs: T.Dict[str, T.Any]) -> T.List[str]: def get_link_args(self, language: T.Optional[str] = None, raw: bool = False) -> T.List[str]: args: T.List[str] = [] - if self.libdir: - args += self.clib_compiler.get_linker_search_args(self.libdir) for lib in self.requested_modules: - args += self.lib_modules[lib] + link_args = self.lib_modules[lib] + # Turn canonical arguments like + # /opt/cuda/lib64/libcublas.so + # back into + # -lcublas + # since this is how CUDA modules were passed to nvcc since time immemorial + if language == 'cuda': + if lib in frozenset(['cudart', 'cudart_static']): + # nvcc always links these unconditionally + mlog.debug(f'Not adding \'{lib}\' to dependency, since nvcc will link it implicitly') + link_args = [] + elif link_args and link_args[0].startswith(self.libdir): + # module included with CUDA, nvcc knows how to find these itself + mlog.debug(f'CUDA module \'{lib}\' found in CUDA libdir') + link_args = ['-l' + lib] + args += link_args + return args packages['cuda'] = CudaDependency From 7efddcb90951cdbd177024721bafabd38c8d0dad Mon Sep 17 00:00:00 2001 From: David Seifert Date: Wed, 22 May 2024 15:55:56 +0200 Subject: [PATCH 10/91] cuda: add test for negative `find_library()` result --- test cases/cuda/1 simple/meson.build | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test cases/cuda/1 simple/meson.build b/test cases/cuda/1 simple/meson.build index 4f111d1b9ee1..e3069df9093b 100644 --- a/test cases/cuda/1 simple/meson.build +++ b/test cases/cuda/1 simple/meson.build @@ -1,4 +1,8 @@ project('simple', 'cuda', version : '1.0.0') +# https://github.com/mesonbuild/meson/issues/13240 +d = meson.get_compiler('cuda').find_library('doesnotexist', required : false) +assert(not d.found()) + exe = executable('prog', 'prog.cu') test('cudatest', exe) From 631b38577ee01d9273f7f42747762d5f8cabc15a Mon Sep 17 00:00:00 2001 From: Michael Gene Brockus Date: Wed, 22 May 2024 14:24:00 -0600 Subject: [PATCH 11/91] unify listing for FSCL to point to all --- docs/markdown/Users.md | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/docs/markdown/Users.md b/docs/markdown/Users.md index fe353a05977a..90892fac52be 100644 --- a/docs/markdown/Users.md +++ b/docs/markdown/Users.md @@ -168,18 +168,7 @@ format files - [ThorVG](https://www.thorvg.org/), vector-based scenes and animations library - [Tilix](https://github.com/gnunn1/tilix), a tiling terminal emulator for Linux using GTK+ 3 - [Tizonia](https://github.com/tizonia/tizonia-openmax-il), a command-line cloud music player for Linux with support for Spotify, Google Play Music, YouTube, SoundCloud, TuneIn, Plex servers and Chromecast devices - - [FSCL XTest](https://github.com/fossil-lib/tscl-xtest-c), a framework for testing C/C++ code - - [FSCL XMock](https://github.com/fossil-lib/tscl-xmock-c), a framework for mocking C/C++ code - - [FSCL XCore](https://github.com/fossil-lib/tscl-xcore-c), essintal compoments for C code - - [FSCL XTool](https://github.com/fossil-lib/tscl-xtool-c), essintal tool for low-level related task - - [FSCL XString](https://github.com/fossil-lib/tscl-xstring-c), string and char types in C - - [FSCL XToFu](https://github.com/fossil-lib/tscl-xtofu-c), a framework for generic types in C - - [FSCL XJellyfish](https://github.com/fossil-lib/tscl-xfish-c), a framework for AI development using JellyFish in C - - [FSCL XStructure](https://github.com/fossil-lib/tscl-xstructure-c), a framework for data structures in C - - [FSCL XAlgorithm](https://github.com/fossil-lib/tscl-xalgorithm-c), a framework for algorithms C - - [FSCL XPattern](https://github.com/fossil-lib/tscl-xpattern-c), a framework for design patterns C - - [FSCL XScience](https://github.com/fossil-lib/tscl-xscience-c), a framework for scientific projects in C - - [FSCL XCube](https://github.com/fossil-lib/tscl-xcube-c), a framework for creating a portable curses TUI in C + - [Fossil Logic Standard](https://github.com/fossil-lib), a collection frameworks in C/C++, Objective-C and Objective-C++. - [UFJF-MLTK](https://github.com/mateus558/UFJF-Machine-Learning-Toolkit), A C++ cross-platform framework for machine learning algorithms development and testing - [Vala Language Server](https://github.com/benwaffle/vala-language-server), code intelligence engine for the Vala and Genie programming languages - [Valum](https://github.com/valum-framework/valum), a micro web framework written in Vala From 5365d9a842938dc9182b52bcdb9674a434475200 Mon Sep 17 00:00:00 2001 From: Jussi Pakkanen Date: Wed, 22 May 2024 23:59:02 +0300 Subject: [PATCH 12/91] Refactor option classes to their own file. --- mesonbuild/ast/introspection.py | 6 +- mesonbuild/cargo/interpreter.py | 8 +- mesonbuild/compilers/c.py | 34 +- mesonbuild/compilers/compilers.py | 47 +- mesonbuild/compilers/cpp.py | 54 +- mesonbuild/compilers/cuda.py | 5 +- mesonbuild/compilers/cython.py | 6 +- mesonbuild/compilers/fortran.py | 4 +- mesonbuild/compilers/mixins/emscripten.py | 3 +- mesonbuild/compilers/objc.py | 3 +- mesonbuild/compilers/objcpp.py | 3 +- mesonbuild/compilers/rust.py | 4 +- mesonbuild/coredata.py | 569 ++---------------- mesonbuild/interpreter/compiler.py | 5 +- mesonbuild/interpreter/interpreter.py | 15 +- mesonbuild/interpreter/interpreterobjects.py | 26 +- mesonbuild/interpreter/kwargs.py | 4 +- mesonbuild/interpreter/type_checking.py | 2 +- mesonbuild/interpreterbase/helpers.py | 2 +- mesonbuild/mconf.py | 21 +- mesonbuild/mintro.py | 20 +- mesonbuild/modules/_qt.py | 4 +- mesonbuild/modules/pkgconfig.py | 2 +- mesonbuild/modules/python.py | 2 +- mesonbuild/optinterpreter.py | 35 +- mesonbuild/options.py | 480 +++++++++++++++ run_project_tests.py | 3 +- run_tests.py | 3 +- .../unit/116 empty project/expected_mods.json | 3 +- unittests/datatests.py | 5 +- unittests/platformagnostictests.py | 2 +- 31 files changed, 713 insertions(+), 667 deletions(-) create mode 100644 mesonbuild/options.py diff --git a/mesonbuild/ast/introspection.py b/mesonbuild/ast/introspection.py index fa11feb0873e..c7dcf73b1443 100644 --- a/mesonbuild/ast/introspection.py +++ b/mesonbuild/ast/introspection.py @@ -10,7 +10,7 @@ import os import typing as T -from .. import compilers, environment, mesonlib, optinterpreter +from .. import compilers, environment, mesonlib, optinterpreter, options from .. import coredata as cdata from ..build import Executable, Jar, SharedLibrary, SharedModule, StaticLibrary from ..compilers import detect_compiler_for @@ -150,8 +150,8 @@ def do_subproject(self, dirname: SubProject) -> None: def func_add_languages(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> None: kwargs = self.flatten_kwargs(kwargs) required = kwargs.get('required', True) - assert isinstance(required, (bool, cdata.UserFeatureOption)), 'for mypy' - if isinstance(required, cdata.UserFeatureOption): + assert isinstance(required, (bool, options.UserFeatureOption)), 'for mypy' + if isinstance(required, options.UserFeatureOption): required = required.is_enabled() if 'native' in kwargs: native = kwargs.get('native', False) diff --git a/mesonbuild/cargo/interpreter.py b/mesonbuild/cargo/interpreter.py index e1b0928978f8..57f2bedeadf5 100644 --- a/mesonbuild/cargo/interpreter.py +++ b/mesonbuild/cargo/interpreter.py @@ -23,7 +23,7 @@ from . import builder from . import version from ..mesonlib import MesonException, Popen_safe, OptionKey -from .. import coredata +from .. import coredata, options if T.TYPE_CHECKING: from types import ModuleType @@ -712,11 +712,11 @@ def interpret(subp_name: str, subdir: str, env: Environment) -> T.Tuple[mparser. build = builder.Builder(filename) # Generate project options - options: T.Dict[OptionKey, coredata.UserOption] = {} + project_options: T.Dict[OptionKey, options.UserOption] = {} for feature in cargo.features: key = OptionKey(_option_name(feature), subproject=subp_name) enabled = feature == 'default' - options[key] = coredata.UserBooleanOption(key.name, f'Cargo {feature} feature', enabled) + project_options[key] = options.UserBooleanOption(key.name, f'Cargo {feature} feature', enabled) ast = _create_project(cargo, build) ast += [build.assign(build.function('import', [build.string('rust')]), 'rust')] @@ -730,4 +730,4 @@ def interpret(subp_name: str, subdir: str, env: Environment) -> T.Tuple[mparser. for crate_type in cargo.lib.crate_type: ast.extend(_create_lib(cargo, build, crate_type)) - return build.block(ast), options + return build.block(ast), project_options diff --git a/mesonbuild/compilers/c.py b/mesonbuild/compilers/c.py index 18b25d46d464..d0326d795016 100644 --- a/mesonbuild/compilers/c.py +++ b/mesonbuild/compilers/c.py @@ -6,7 +6,7 @@ import os.path import typing as T -from .. import coredata +from .. import options from .. import mlog from ..mesonlib import MesonException, version_compare, OptionKey from .c_function_attributes import C_FUNC_ATTRIBUTES @@ -96,7 +96,7 @@ def get_options(self) -> 'MutableKeyedOptionDictType': opts = super().get_options() key = OptionKey('std', machine=self.for_machine, lang=self.language) opts.update({ - key: coredata.UserStdOption('C', _ALL_STDS), + key: options.UserStdOption('C', _ALL_STDS), }) return opts @@ -128,7 +128,7 @@ def get_options(self) -> 'MutableKeyedOptionDictType': if version_compare(self.version, self._C23_VERSION): stds += ['c23'] std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] - assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(stds, gnu=True) return opts @@ -154,7 +154,7 @@ def get_options(self) -> 'MutableKeyedOptionDictType': if self.info.is_windows() or self.info.is_cygwin(): self.update_options( opts, - self.create_option(coredata.UserArrayOption, + self.create_option(options.UserArrayOption, OptionKey('winlibs', machine=self.for_machine, lang=self.language), 'Standard Win libraries to link against', gnu_winlibs), @@ -247,7 +247,7 @@ def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_ def get_options(self) -> 'MutableKeyedOptionDictType': opts = CCompiler.get_options(self) std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] - assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(['c90', 'c99', 'c11'], gnu=True) return opts @@ -298,12 +298,12 @@ def get_options(self) -> 'MutableKeyedOptionDictType': stds += ['c23'] key = OptionKey('std', machine=self.for_machine, lang=self.language) std_opt = opts[key] - assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(stds, gnu=True) if self.info.is_windows() or self.info.is_cygwin(): self.update_options( opts, - self.create_option(coredata.UserArrayOption, + self.create_option(options.UserArrayOption, key.evolve('winlibs'), 'Standard Win libraries to link against', gnu_winlibs), @@ -377,7 +377,7 @@ def get_options(self) -> 'MutableKeyedOptionDictType': if version_compare(self.version, '>=1.26.00'): stds += ['c17', 'c18', 'iso9899:2017', 'iso9899:2018', 'gnu17', 'gnu18'] std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] - assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(stds) return opts @@ -416,7 +416,7 @@ def get_options(self) -> 'MutableKeyedOptionDictType': if version_compare(self.version, '>=16.0.0'): stds += ['c11'] std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] - assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(stds, gnu=True) return opts @@ -441,7 +441,7 @@ def get_options(self) -> MutableKeyedOptionDictType: return self.update_options( super().get_options(), self.create_option( - coredata.UserArrayOption, + options.UserArrayOption, OptionKey('winlibs', machine=self.for_machine, lang=self.language), 'Windows libs to link against.', msvc_winlibs, @@ -480,7 +480,7 @@ def get_options(self) -> 'MutableKeyedOptionDictType': if version_compare(self.version, self._C17_VERSION): stds += ['c17', 'c18'] std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] - assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(stds, gnu=True, gnu_deprecated=True) return opts @@ -529,7 +529,7 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic def get_options(self) -> 'MutableKeyedOptionDictType': opts = super().get_options() std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] - assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(['c89', 'c99', 'c11']) return opts @@ -562,7 +562,7 @@ def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_ def get_options(self) -> 'MutableKeyedOptionDictType': opts = CCompiler.get_options(self) std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] - assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(['c89', 'c99', 'c11']) return opts @@ -591,7 +591,7 @@ def get_always_args(self) -> T.List[str]: def get_options(self) -> 'MutableKeyedOptionDictType': opts = CCompiler.get_options(self) std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] - assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(['c89', 'c99']) return opts @@ -638,7 +638,7 @@ def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_ def get_options(self) -> 'MutableKeyedOptionDictType': opts = CCompiler.get_options(self) std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] - assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(['c89', 'c99'], gnu=True) return opts @@ -683,7 +683,7 @@ def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_ def get_options(self) -> 'MutableKeyedOptionDictType': opts = CCompiler.get_options(self) std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] - assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(['c89', 'c99']) return opts @@ -720,7 +720,7 @@ def get_always_args(self) -> T.List[str]: def get_options(self) -> 'MutableKeyedOptionDictType': opts = CCompiler.get_options(self) std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] - assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(['c89', 'c99', 'c11']) return opts diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index 673b63f9d581..4267384c5232 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -15,6 +15,7 @@ from .. import coredata from .. import mlog from .. import mesonlib +from .. import options from ..mesonlib import ( HoldableObject, EnvironmentException, MesonException, @@ -35,7 +36,7 @@ CompilerType = T.TypeVar('CompilerType', bound='Compiler') _T = T.TypeVar('_T') - UserOptionType = T.TypeVar('UserOptionType', bound=coredata.UserOption) + UserOptionType = T.TypeVar('UserOptionType', bound=options.UserOption) """This file contains the data files of all compilers Meson knows about. To support a new compiler, add its information below. @@ -209,40 +210,40 @@ class CompileCheckMode(enum.Enum): MSCRT_VALS = ['none', 'md', 'mdd', 'mt', 'mtd'] @dataclass -class BaseOption(T.Generic[coredata._T, coredata._U]): - opt_type: T.Type[coredata._U] +class BaseOption(T.Generic[options._T, options._U]): + opt_type: T.Type[options._U] description: str default: T.Any = None choices: T.Any = None - def init_option(self, name: OptionKey) -> coredata._U: + def init_option(self, name: OptionKey) -> options._U: keywords = {'value': self.default} if self.choices: keywords['choices'] = self.choices return self.opt_type(name.name, self.description, **keywords) BASE_OPTIONS: T.Mapping[OptionKey, BaseOption] = { - OptionKey('b_pch'): BaseOption(coredata.UserBooleanOption, 'Use precompiled headers', True), - OptionKey('b_lto'): BaseOption(coredata.UserBooleanOption, 'Use link time optimization', False), - OptionKey('b_lto_threads'): BaseOption(coredata.UserIntegerOption, 'Use multiple threads for Link Time Optimization', (None, None, 0)), - OptionKey('b_lto_mode'): BaseOption(coredata.UserComboOption, 'Select between different LTO modes.', 'default', + OptionKey('b_pch'): BaseOption(options.UserBooleanOption, 'Use precompiled headers', True), + OptionKey('b_lto'): BaseOption(options.UserBooleanOption, 'Use link time optimization', False), + OptionKey('b_lto_threads'): BaseOption(options.UserIntegerOption, 'Use multiple threads for Link Time Optimization', (None, None, 0)), + OptionKey('b_lto_mode'): BaseOption(options.UserComboOption, 'Select between different LTO modes.', 'default', choices=['default', 'thin']), - OptionKey('b_thinlto_cache'): BaseOption(coredata.UserBooleanOption, 'Use LLVM ThinLTO caching for faster incremental builds', False), - OptionKey('b_thinlto_cache_dir'): BaseOption(coredata.UserStringOption, 'Directory to store ThinLTO cache objects', ''), - OptionKey('b_sanitize'): BaseOption(coredata.UserComboOption, 'Code sanitizer to use', 'none', + OptionKey('b_thinlto_cache'): BaseOption(options.UserBooleanOption, 'Use LLVM ThinLTO caching for faster incremental builds', False), + OptionKey('b_thinlto_cache_dir'): BaseOption(options.UserStringOption, 'Directory to store ThinLTO cache objects', ''), + OptionKey('b_sanitize'): BaseOption(options.UserComboOption, 'Code sanitizer to use', 'none', choices=['none', 'address', 'thread', 'undefined', 'memory', 'leak', 'address,undefined']), - OptionKey('b_lundef'): BaseOption(coredata.UserBooleanOption, 'Use -Wl,--no-undefined when linking', True), - OptionKey('b_asneeded'): BaseOption(coredata.UserBooleanOption, 'Use -Wl,--as-needed when linking', True), - OptionKey('b_pgo'): BaseOption(coredata.UserComboOption, 'Use profile guided optimization', 'off', + OptionKey('b_lundef'): BaseOption(options.UserBooleanOption, 'Use -Wl,--no-undefined when linking', True), + OptionKey('b_asneeded'): BaseOption(options.UserBooleanOption, 'Use -Wl,--as-needed when linking', True), + OptionKey('b_pgo'): BaseOption(options.UserComboOption, 'Use profile guided optimization', 'off', choices=['off', 'generate', 'use']), - OptionKey('b_coverage'): BaseOption(coredata.UserBooleanOption, 'Enable coverage tracking.', False), - OptionKey('b_colorout'): BaseOption(coredata.UserComboOption, 'Use colored output', 'always', + OptionKey('b_coverage'): BaseOption(options.UserBooleanOption, 'Enable coverage tracking.', False), + OptionKey('b_colorout'): BaseOption(options.UserComboOption, 'Use colored output', 'always', choices=['auto', 'always', 'never']), - OptionKey('b_ndebug'): BaseOption(coredata.UserComboOption, 'Disable asserts', 'false', choices=['true', 'false', 'if-release']), - OptionKey('b_staticpic'): BaseOption(coredata.UserBooleanOption, 'Build static libraries as position independent', True), - OptionKey('b_pie'): BaseOption(coredata.UserBooleanOption, 'Build executables as position independent', False), - OptionKey('b_bitcode'): BaseOption(coredata.UserBooleanOption, 'Generate and embed bitcode (only macOS/iOS/tvOS)', False), - OptionKey('b_vscrt'): BaseOption(coredata.UserComboOption, 'VS run-time library type to use.', 'from_buildtype', + OptionKey('b_ndebug'): BaseOption(options.UserComboOption, 'Disable asserts', 'false', choices=['true', 'false', 'if-release']), + OptionKey('b_staticpic'): BaseOption(options.UserBooleanOption, 'Build static libraries as position independent', True), + OptionKey('b_pie'): BaseOption(options.UserBooleanOption, 'Build executables as position independent', False), + OptionKey('b_bitcode'): BaseOption(options.UserBooleanOption, 'Generate and embed bitcode (only macOS/iOS/tvOS)', False), + OptionKey('b_vscrt'): BaseOption(options.UserComboOption, 'VS run-time library type to use.', 'from_buildtype', choices=MSCRT_VALS + ['from_buildtype', 'static_from_buildtype']), } @@ -1365,12 +1366,12 @@ def get_global_options(lang: str, comp_options = env.options.get(comp_key, []) link_options = env.options.get(largkey, []) - cargs = coredata.UserArrayOption( + cargs = options.UserArrayOption( f'{lang}_{argkey.name}', description + ' compiler', comp_options, split_args=True, allow_dups=True) - largs = coredata.UserArrayOption( + largs = options.UserArrayOption( f'{lang}_{largkey.name}', description + ' linker', link_options, split_args=True, allow_dups=True) diff --git a/mesonbuild/compilers/cpp.py b/mesonbuild/compilers/cpp.py index 525c9fcdf378..ea5182370fe3 100644 --- a/mesonbuild/compilers/cpp.py +++ b/mesonbuild/compilers/cpp.py @@ -8,7 +8,7 @@ import os.path import typing as T -from .. import coredata +from .. import options from .. import mlog from ..mesonlib import MesonException, version_compare, OptionKey @@ -174,7 +174,7 @@ def get_options(self) -> 'MutableKeyedOptionDictType': opts = super().get_options() key = OptionKey('std', machine=self.for_machine, lang=self.language) opts.update({ - key: coredata.UserStdOption('C++', _ALL_STDS), + key: options.UserStdOption('C++', _ALL_STDS), }) return opts @@ -242,16 +242,16 @@ def get_options(self) -> 'MutableKeyedOptionDictType': key = OptionKey('key', machine=self.for_machine, lang=self.language) self.update_options( opts, - self.create_option(coredata.UserComboOption, + self.create_option(options.UserComboOption, key.evolve('eh'), 'C++ exception handling type.', ['none', 'default', 'a', 's', 'sc'], 'default'), - self.create_option(coredata.UserBooleanOption, + self.create_option(options.UserBooleanOption, key.evolve('rtti'), 'Enable RTTI', True), - self.create_option(coredata.UserBooleanOption, + self.create_option(options.UserBooleanOption, key.evolve('debugstl'), 'STL debug mode', False), @@ -264,12 +264,12 @@ def get_options(self) -> 'MutableKeyedOptionDictType': if version_compare(self.version, self._CPP26_VERSION): cppstd_choices.append('c++26') std_opt = opts[key.evolve('std')] - assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(cppstd_choices, gnu=True) if self.info.is_windows() or self.info.is_cygwin(): self.update_options( opts, - self.create_option(coredata.UserArrayOption, + self.create_option(options.UserArrayOption, key.evolve('winlibs'), 'Standard Win libraries to link against', gnu_winlibs), @@ -393,14 +393,14 @@ def get_options(self) -> 'MutableKeyedOptionDictType': key = OptionKey('std', machine=self.for_machine, lang=self.language) self.update_options( opts, - self.create_option(coredata.UserComboOption, + self.create_option(options.UserComboOption, key.evolve('eh'), 'C++ exception handling type.', ['none', 'default', 'a', 's', 'sc'], 'default'), ) std_opt = opts[key] - assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(['c++98', 'c++03', 'c++11', 'c++14', 'c++17'], gnu=True) return opts @@ -442,16 +442,16 @@ def get_options(self) -> 'MutableKeyedOptionDictType': opts = CPPCompiler.get_options(self) self.update_options( opts, - self.create_option(coredata.UserComboOption, + self.create_option(options.UserComboOption, key.evolve('eh'), 'C++ exception handling type.', ['none', 'default', 'a', 's', 'sc'], 'default'), - self.create_option(coredata.UserBooleanOption, + self.create_option(options.UserBooleanOption, key.evolve('rtti'), 'Enable RTTI', True), - self.create_option(coredata.UserBooleanOption, + self.create_option(options.UserBooleanOption, key.evolve('debugstl'), 'STL debug mode', False), @@ -465,12 +465,12 @@ def get_options(self) -> 'MutableKeyedOptionDictType': if version_compare(self.version, '>=14.0.0'): cppstd_choices.append('c++26') std_opt = opts[key] - assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(cppstd_choices, gnu=True) if self.info.is_windows() or self.info.is_cygwin(): self.update_options( opts, - self.create_option(coredata.UserArrayOption, + self.create_option(options.UserArrayOption, key.evolve('winlibs'), 'Standard Win libraries to link against', gnu_winlibs), @@ -582,18 +582,18 @@ def get_options(self) -> 'MutableKeyedOptionDictType': key = OptionKey('std', machine=self.for_machine, lang=self.language) self.update_options( opts, - self.create_option(coredata.UserComboOption, + self.create_option(options.UserComboOption, key.evolve('eh'), 'C++ exception handling type.', ['none', 'default', 'a', 's', 'sc'], 'default'), - self.create_option(coredata.UserBooleanOption, + self.create_option(options.UserBooleanOption, key.evolve('debugstl'), 'STL debug mode', False), ) std_opt = opts[key] - assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(cpp_stds, gnu=True) return opts @@ -661,22 +661,22 @@ def get_options(self) -> 'MutableKeyedOptionDictType': key = OptionKey('std', machine=self.for_machine, lang=self.language) self.update_options( opts, - self.create_option(coredata.UserComboOption, + self.create_option(options.UserComboOption, key.evolve('eh'), 'C++ exception handling type.', ['none', 'default', 'a', 's', 'sc'], 'default'), - self.create_option(coredata.UserBooleanOption, + self.create_option(options.UserBooleanOption, key.evolve('rtti'), 'Enable RTTI', True), - self.create_option(coredata.UserBooleanOption, + self.create_option(options.UserBooleanOption, key.evolve('debugstl'), 'STL debug mode', False), ) std_opt = opts[key] - assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(c_stds + g_stds) return opts @@ -734,22 +734,22 @@ def _get_options_impl(self, opts: 'MutableKeyedOptionDictType', cpp_stds: T.List key = OptionKey('std', machine=self.for_machine, lang=self.language) self.update_options( opts, - self.create_option(coredata.UserComboOption, + self.create_option(options.UserComboOption, key.evolve('eh'), 'C++ exception handling type.', ['none', 'default', 'a', 's', 'sc'], 'default'), - self.create_option(coredata.UserBooleanOption, + self.create_option(options.UserBooleanOption, key.evolve('rtti'), 'Enable RTTI', True), - self.create_option(coredata.UserArrayOption, + self.create_option(options.UserArrayOption, key.evolve('winlibs'), 'Windows libs to link against.', msvc_winlibs), ) std_opt = opts[key] - assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(cpp_stds) return opts @@ -912,7 +912,7 @@ def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_ def get_options(self) -> 'MutableKeyedOptionDictType': opts = CPPCompiler.get_options(self) std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] - assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(['c++03', 'c++11']) return opts @@ -973,7 +973,7 @@ def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_ def get_options(self) -> 'MutableKeyedOptionDictType': opts = CPPCompiler.get_options(self) std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] - assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(['c++03']) return opts diff --git a/mesonbuild/compilers/cuda.py b/mesonbuild/compilers/cuda.py index 7231a267c02b..8680cf076401 100644 --- a/mesonbuild/compilers/cuda.py +++ b/mesonbuild/compilers/cuda.py @@ -9,6 +9,7 @@ import typing as T from .. import coredata +from .. import options from .. import mlog from ..mesonlib import ( EnvironmentException, Popen_safe, @@ -643,12 +644,12 @@ def get_options(self) -> 'MutableKeyedOptionDictType': return self.update_options( super().get_options(), - self.create_option(coredata.UserComboOption, + self.create_option(options.UserComboOption, OptionKey('std', machine=self.for_machine, lang=self.language), 'C++ language standard to use with CUDA', cpp_stds, 'none'), - self.create_option(coredata.UserStringOption, + self.create_option(options.UserStringOption, OptionKey('ccbindir', machine=self.for_machine, lang=self.language), 'CUDA non-default toolchain directory to use (-ccbin)', ''), diff --git a/mesonbuild/compilers/cython.py b/mesonbuild/compilers/cython.py index 30cec81e369f..61e339bdda82 100644 --- a/mesonbuild/compilers/cython.py +++ b/mesonbuild/compilers/cython.py @@ -6,7 +6,7 @@ import typing as T -from .. import coredata +from .. import options from ..mesonlib import EnvironmentException, OptionKey, version_compare from .compilers import Compiler @@ -69,12 +69,12 @@ def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str], def get_options(self) -> 'MutableKeyedOptionDictType': return self.update_options( super().get_options(), - self.create_option(coredata.UserComboOption, + self.create_option(options.UserComboOption, OptionKey('version', machine=self.for_machine, lang=self.language), 'Python version to target', ['2', '3'], '3'), - self.create_option(coredata.UserComboOption, + self.create_option(options.UserComboOption, OptionKey('language', machine=self.for_machine, lang=self.language), 'Output C or C++ files', ['c', 'cpp'], diff --git a/mesonbuild/compilers/fortran.py b/mesonbuild/compilers/fortran.py index 428251560535..7b2f5d9d49e2 100644 --- a/mesonbuild/compilers/fortran.py +++ b/mesonbuild/compilers/fortran.py @@ -6,7 +6,7 @@ import typing as T import os -from .. import coredata +from .. import options from .compilers import ( clike_debug_args, Compiler, @@ -114,7 +114,7 @@ def has_multi_link_arguments(self, args: T.List[str], env: 'Environment') -> T.T def get_options(self) -> 'MutableKeyedOptionDictType': return self.update_options( super().get_options(), - self.create_option(coredata.UserComboOption, + self.create_option(options.UserComboOption, OptionKey('std', machine=self.for_machine, lang=self.language), 'Fortran language standard to use', ['none'], diff --git a/mesonbuild/compilers/mixins/emscripten.py b/mesonbuild/compilers/mixins/emscripten.py index bb8a520546d4..110dbc6050f4 100644 --- a/mesonbuild/compilers/mixins/emscripten.py +++ b/mesonbuild/compilers/mixins/emscripten.py @@ -9,6 +9,7 @@ import typing as T from ... import coredata +from ... import options from ... import mesonlib from ...mesonlib import OptionKey from ...mesonlib import LibType @@ -59,7 +60,7 @@ def get_options(self) -> coredata.MutableKeyedOptionDictType: return self.update_options( super().get_options(), self.create_option( - coredata.UserIntegerOption, + options.UserIntegerOption, OptionKey('thread_count', machine=self.for_machine, lang=self.language), 'Number of threads to use in web assembly, set to 0 to disable', (0, None, 4), # Default was picked at random diff --git a/mesonbuild/compilers/objc.py b/mesonbuild/compilers/objc.py index 7c19c1b7d591..4d33ec8b23b7 100644 --- a/mesonbuild/compilers/objc.py +++ b/mesonbuild/compilers/objc.py @@ -6,6 +6,7 @@ import typing as T from .. import coredata +from .. import options from ..mesonlib import OptionKey from .compilers import Compiler @@ -80,7 +81,7 @@ def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_ def get_options(self) -> 'coredata.MutableKeyedOptionDictType': return self.update_options( super().get_options(), - self.create_option(coredata.UserComboOption, + self.create_option(options.UserComboOption, OptionKey('std', machine=self.for_machine, lang='c'), 'C language standard to use', ['none', 'c89', 'c99', 'c11', 'c17', 'gnu89', 'gnu99', 'gnu11', 'gnu17'], diff --git a/mesonbuild/compilers/objcpp.py b/mesonbuild/compilers/objcpp.py index 46eaa504904d..e28e3ed30a41 100644 --- a/mesonbuild/compilers/objcpp.py +++ b/mesonbuild/compilers/objcpp.py @@ -6,6 +6,7 @@ import typing as T from .. import coredata +from .. import options from ..mesonlib import OptionKey from .mixins.clike import CLikeCompiler @@ -80,7 +81,7 @@ def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_ def get_options(self) -> coredata.MutableKeyedOptionDictType: return self.update_options( super().get_options(), - self.create_option(coredata.UserComboOption, + self.create_option(options.UserComboOption, OptionKey('std', machine=self.for_machine, lang='cpp'), 'C++ language standard to use', ['none', 'c++98', 'c++11', 'c++14', 'c++17', 'c++20', 'c++2b', diff --git a/mesonbuild/compilers/rust.py b/mesonbuild/compilers/rust.py index ce1079190d98..0f52dbb5ebe5 100644 --- a/mesonbuild/compilers/rust.py +++ b/mesonbuild/compilers/rust.py @@ -9,7 +9,7 @@ import re import typing as T -from .. import coredata +from .. import options from ..mesonlib import EnvironmentException, MesonException, Popen_safe_logged, OptionKey from .compilers import Compiler, clike_debug_args @@ -158,7 +158,7 @@ def use_linker_args(cls, linker: str, version: str) -> T.List[str]: # use_linker_args method instead. def get_options(self) -> MutableKeyedOptionDictType: - return dict((self.create_option(coredata.UserComboOption, + return dict((self.create_option(options.UserComboOption, OptionKey('std', machine=self.for_machine, lang=self.language), 'Rust edition to use', ['none', '2015', '2018', '2021'], diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index 6e67587b27a0..76da0b67fcac 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -6,7 +6,7 @@ import copy -from . import mlog, mparser +from . import mlog, mparser, options import pickle, os, uuid import sys from itertools import chain @@ -15,12 +15,10 @@ from dataclasses import dataclass from .mesonlib import ( - HoldableObject, MesonBugException, + MesonBugException, MesonException, EnvironmentException, MachineChoice, PerMachine, - PerMachineDefaultable, default_libdir, default_libexecdir, - default_prefix, default_datadir, default_includedir, default_infodir, - default_localedir, default_mandir, default_sbindir, default_sysconfdir, - listify_array_value, OptionKey, OptionType, stringlistify, + PerMachineDefaultable, + OptionKey, OptionType, stringlistify, pickle_load ) import ast @@ -56,8 +54,8 @@ class SharedCMDOptions(Protocol): cross_file: T.List[str] native_file: T.List[str] - OptionDictType = T.Union[T.Dict[str, 'UserOption[T.Any]'], 'OptionsView'] - MutableKeyedOptionDictType = T.Dict['OptionKey', 'UserOption[T.Any]'] + OptionDictType = T.Union[T.Dict[str, 'options.UserOption[T.Any]'], 'OptionsView'] + MutableKeyedOptionDictType = T.Dict['OptionKey', 'options.UserOption[T.Any]'] KeyedOptionDictType = T.Union[MutableKeyedOptionDictType, 'OptionsView'] CompilerCheckCacheKey = T.Tuple[T.Tuple[str, ...], str, FileOrString, T.Tuple[str, ...], CompileCheckMode] # code, args @@ -82,20 +80,11 @@ class SharedCMDOptions(Protocol): stable_version_array[-2] = str(int(stable_version_array[-2]) + 1) stable_version = '.'.join(stable_version_array) -backendlist = ['ninja', 'vs', 'vs2010', 'vs2012', 'vs2013', 'vs2015', 'vs2017', 'vs2019', 'vs2022', 'xcode', 'none'] -genvslitelist = ['vs2022'] -buildtypelist = ['plain', 'debug', 'debugoptimized', 'release', 'minsize', 'custom'] - -DEFAULT_YIELDING = False - -# Can't bind this near the class method it seems, sadly. -_T = T.TypeVar('_T') - def get_genvs_default_buildtype_list() -> list[str]: # just debug, debugoptimized, and release for now # but this should probably be configurable through some extra option, alongside --genvslite. - return buildtypelist[1:-2] + return options.buildtypelist[1:-2] class MesonVersionMismatchException(MesonException): @@ -108,312 +97,6 @@ def __init__(self, old_version: str, current_version: str, extra_msg: str = '') self.current_version = current_version -class UserOption(T.Generic[_T], HoldableObject): - def __init__(self, name: str, description: str, choices: T.Optional[T.Union[str, T.List[_T]]], - yielding: bool, - deprecated: T.Union[bool, str, T.Dict[str, str], T.List[str]] = False): - super().__init__() - self.name = name - self.choices = choices - self.description = description - if not isinstance(yielding, bool): - raise MesonException('Value of "yielding" must be a boolean.') - self.yielding = yielding - self.deprecated = deprecated - self.readonly = False - - def listify(self, value: T.Any) -> T.List[T.Any]: - return [value] - - def printable_value(self) -> T.Union[str, int, bool, T.List[T.Union[str, int, bool]]]: - assert isinstance(self.value, (str, int, bool, list)) - return self.value - - # Check that the input is a valid value and return the - # "cleaned" or "native" version. For example the Boolean - # option could take the string "true" and return True. - def validate_value(self, value: T.Any) -> _T: - raise RuntimeError('Derived option class did not override validate_value.') - - def set_value(self, newvalue: T.Any) -> bool: - oldvalue = getattr(self, 'value', None) - self.value = self.validate_value(newvalue) - return self.value != oldvalue - -class UserStringOption(UserOption[str]): - def __init__(self, name: str, description: str, value: T.Any, yielding: bool = DEFAULT_YIELDING, - deprecated: T.Union[bool, str, T.Dict[str, str], T.List[str]] = False): - super().__init__(name, description, None, yielding, deprecated) - self.set_value(value) - - def validate_value(self, value: T.Any) -> str: - if not isinstance(value, str): - raise MesonException(f'The value of option "{self.name}" is "{value}", which is not a string.') - return value - -class UserBooleanOption(UserOption[bool]): - def __init__(self, name: str, description: str, value: bool, yielding: bool = DEFAULT_YIELDING, - deprecated: T.Union[bool, str, T.Dict[str, str], T.List[str]] = False): - super().__init__(name, description, [True, False], yielding, deprecated) - self.set_value(value) - - def __bool__(self) -> bool: - return self.value - - def validate_value(self, value: T.Any) -> bool: - if isinstance(value, bool): - return value - if not isinstance(value, str): - raise MesonException(f'Option "{self.name}" value {value} cannot be converted to a boolean') - if value.lower() == 'true': - return True - if value.lower() == 'false': - return False - raise MesonException(f'Option "{self.name}" value {value} is not boolean (true or false).') - -class UserIntegerOption(UserOption[int]): - def __init__(self, name: str, description: str, value: T.Any, yielding: bool = DEFAULT_YIELDING, - deprecated: T.Union[bool, str, T.Dict[str, str], T.List[str]] = False): - min_value, max_value, default_value = value - self.min_value = min_value - self.max_value = max_value - c: T.List[str] = [] - if min_value is not None: - c.append('>=' + str(min_value)) - if max_value is not None: - c.append('<=' + str(max_value)) - choices = ', '.join(c) - super().__init__(name, description, choices, yielding, deprecated) - self.set_value(default_value) - - def validate_value(self, value: T.Any) -> int: - if isinstance(value, str): - value = self.toint(value) - if not isinstance(value, int): - raise MesonException(f'Value {value!r} for option "{self.name}" is not an integer.') - if self.min_value is not None and value < self.min_value: - raise MesonException(f'Value {value} for option "{self.name}" is less than minimum value {self.min_value}.') - if self.max_value is not None and value > self.max_value: - raise MesonException(f'Value {value} for option "{self.name}" is more than maximum value {self.max_value}.') - return value - - def toint(self, valuestring: str) -> int: - try: - return int(valuestring) - except ValueError: - raise MesonException(f'Value string "{valuestring}" for option "{self.name}" is not convertible to an integer.') - -class OctalInt(int): - # NinjaBackend.get_user_option_args uses str() to converts it to a command line option - # UserUmaskOption.toint() uses int(str, 8) to convert it to an integer - # So we need to use oct instead of dec here if we do not want values to be misinterpreted. - def __str__(self) -> str: - return oct(int(self)) - -class UserUmaskOption(UserIntegerOption, UserOption[T.Union[str, OctalInt]]): - def __init__(self, name: str, description: str, value: T.Any, yielding: bool = DEFAULT_YIELDING, - deprecated: T.Union[bool, str, T.Dict[str, str], T.List[str]] = False): - super().__init__(name, description, (0, 0o777, value), yielding, deprecated) - self.choices = ['preserve', '0000-0777'] - - def printable_value(self) -> str: - if self.value == 'preserve': - return self.value - return format(self.value, '04o') - - def validate_value(self, value: T.Any) -> T.Union[str, OctalInt]: - if value == 'preserve': - return 'preserve' - return OctalInt(super().validate_value(value)) - - def toint(self, valuestring: T.Union[str, OctalInt]) -> int: - try: - return int(valuestring, 8) - except ValueError as e: - raise MesonException(f'Invalid mode for option "{self.name}" {e}') - -class UserComboOption(UserOption[str]): - def __init__(self, name: str, description: str, choices: T.List[str], value: T.Any, - yielding: bool = DEFAULT_YIELDING, - deprecated: T.Union[bool, str, T.Dict[str, str], T.List[str]] = False): - super().__init__(name, description, choices, yielding, deprecated) - if not isinstance(self.choices, list): - raise MesonException(f'Combo choices for option "{self.name}" must be an array.') - for i in self.choices: - if not isinstance(i, str): - raise MesonException(f'Combo choice elements for option "{self.name}" must be strings.') - self.set_value(value) - - def validate_value(self, value: T.Any) -> str: - if value not in self.choices: - if isinstance(value, bool): - _type = 'boolean' - elif isinstance(value, (int, float)): - _type = 'number' - else: - _type = 'string' - optionsstring = ', '.join([f'"{item}"' for item in self.choices]) - raise MesonException('Value "{}" (of type "{}") for option "{}" is not one of the choices.' - ' Possible choices are (as string): {}.'.format( - value, _type, self.name, optionsstring)) - return value - -class UserArrayOption(UserOption[T.List[str]]): - def __init__(self, name: str, description: str, value: T.Union[str, T.List[str]], - split_args: bool = False, - allow_dups: bool = False, yielding: bool = DEFAULT_YIELDING, - choices: T.Optional[T.List[str]] = None, - deprecated: T.Union[bool, str, T.Dict[str, str], T.List[str]] = False): - super().__init__(name, description, choices if choices is not None else [], yielding, deprecated) - self.split_args = split_args - self.allow_dups = allow_dups - self.set_value(value) - - def listify(self, value: T.Any) -> T.List[T.Any]: - try: - return listify_array_value(value, self.split_args) - except MesonException as e: - raise MesonException(f'error in option "{self.name}": {e!s}') - - def validate_value(self, value: T.Union[str, T.List[str]]) -> T.List[str]: - newvalue = self.listify(value) - - if not self.allow_dups and len(set(newvalue)) != len(newvalue): - msg = 'Duplicated values in array option is deprecated. ' \ - 'This will become a hard error in the future.' - mlog.deprecation(msg) - for i in newvalue: - if not isinstance(i, str): - raise MesonException(f'String array element "{newvalue!s}" for option "{self.name}" is not a string.') - if self.choices: - bad = [x for x in newvalue if x not in self.choices] - if bad: - raise MesonException('Value{} "{}" for option "{}" {} not in allowed choices: "{}"'.format( - '' if len(bad) == 1 else 's', - ', '.join(bad), - self.name, - 'is' if len(bad) == 1 else 'are', - ', '.join(self.choices)) - ) - return newvalue - - def extend_value(self, value: T.Union[str, T.List[str]]) -> None: - """Extend the value with an additional value.""" - new = self.validate_value(value) - self.set_value(self.value + new) - - -class UserFeatureOption(UserComboOption): - static_choices = ['enabled', 'disabled', 'auto'] - - def __init__(self, name: str, description: str, value: T.Any, yielding: bool = DEFAULT_YIELDING, - deprecated: T.Union[bool, str, T.Dict[str, str], T.List[str]] = False): - super().__init__(name, description, self.static_choices, value, yielding, deprecated) - self.name: T.Optional[str] = None # TODO: Refactor options to all store their name - - def is_enabled(self) -> bool: - return self.value == 'enabled' - - def is_disabled(self) -> bool: - return self.value == 'disabled' - - def is_auto(self) -> bool: - return self.value == 'auto' - -class UserStdOption(UserComboOption): - ''' - UserOption specific to c_std and cpp_std options. User can set a list of - STDs in preference order and it selects the first one supported by current - compiler. - - For historical reasons, some compilers (msvc) allowed setting a GNU std and - silently fell back to C std. This is now deprecated. Projects that support - both GNU and MSVC compilers should set e.g. c_std=gnu11,c11. - - This is not using self.deprecated mechanism we already have for project - options because we want to print a warning if ALL values are deprecated, not - if SOME values are deprecated. - ''' - def __init__(self, lang: str, all_stds: T.List[str]) -> None: - self.lang = lang.lower() - self.all_stds = ['none'] + all_stds - # Map a deprecated std to its replacement. e.g. gnu11 -> c11. - self.deprecated_stds: T.Dict[str, str] = {} - opt_name = 'cpp_std' if lang == 'c++' else f'{lang}_std' - super().__init__(opt_name, f'{lang} language standard to use', ['none'], 'none') - - def set_versions(self, versions: T.List[str], gnu: bool = False, gnu_deprecated: bool = False) -> None: - assert all(std in self.all_stds for std in versions) - self.choices += versions - if gnu: - gnu_stds_map = {f'gnu{std[1:]}': std for std in versions} - if gnu_deprecated: - self.deprecated_stds.update(gnu_stds_map) - else: - self.choices += gnu_stds_map.keys() - - def validate_value(self, value: T.Union[str, T.List[str]]) -> str: - try: - candidates = listify_array_value(value) - except MesonException as e: - raise MesonException(f'error in option "{self.name}": {e!s}') - unknown = ','.join(std for std in candidates if std not in self.all_stds) - if unknown: - raise MesonException(f'Unknown option "{self.name}" value {unknown}. Possible values are {self.all_stds}.') - # Check first if any of the candidates are not deprecated - for std in candidates: - if std in self.choices: - return std - # Fallback to a deprecated std if any - for std in candidates: - newstd = self.deprecated_stds.get(std) - if newstd is not None: - mlog.deprecation( - f'None of the values {candidates} are supported by the {self.lang} compiler.\n' + - f'However, the deprecated {std} std currently falls back to {newstd}.\n' + - 'This will be an error in the future.\n' + - 'If the project supports both GNU and MSVC compilers, a value such as\n' + - '"c_std=gnu11,c11" specifies that GNU is preferred but it can safely fallback to plain c11.') - return newstd - raise MesonException(f'None of values {candidates} are supported by the {self.lang.upper()} compiler. ' + - f'Possible values for option "{self.name}" are {self.choices}') - -@dataclass -class OptionsView(abc.Mapping): - '''A view on an options dictionary for a given subproject and with overrides. - ''' - - # TODO: the typing here could be made more explicit using a TypeDict from - # python 3.8 or typing_extensions - options: KeyedOptionDictType - subproject: T.Optional[str] = None - overrides: T.Optional[T.Mapping[OptionKey, T.Union[str, int, bool, T.List[str]]]] = None - - def __getitem__(self, key: OptionKey) -> UserOption: - # FIXME: This is fundamentally the same algorithm than interpreter.get_option_internal(). - # We should try to share the code somehow. - key = key.evolve(subproject=self.subproject) - if not key.is_project(): - opt = self.options.get(key) - if opt is None or opt.yielding: - opt = self.options[key.as_root()] - else: - opt = self.options[key] - if opt.yielding: - opt = self.options.get(key.as_root(), opt) - if self.overrides: - override_value = self.overrides.get(key.as_root()) - if override_value is not None: - opt = copy.copy(opt) - opt.set_value(override_value) - return opt - - def __iter__(self) -> T.Iterator[OptionKey]: - return iter(self.options) - - def __len__(self) -> int: - return len(self.options) - class DependencyCacheType(enum.Enum): OTHER = 0 @@ -664,7 +347,7 @@ def builtin_options_libdir_cross_fixup(self) -> None: # getting the "system default" is always wrong on multiarch # platforms as it gets a value like lib/x86_64-linux-gnu. if self.cross_files: - BUILTIN_OPTIONS[OptionKey('libdir')].default = 'lib' + options.BUILTIN_OPTIONS[OptionKey('libdir')].default = 'lib' def sanitize_prefix(self, prefix: str) -> str: prefix = os.path.expanduser(prefix) @@ -698,7 +381,7 @@ def sanitize_dir_option_value(self, prefix: str, option: OptionKey, value: T.Any except TypeError: return value if option.name.endswith('dir') and value.is_absolute() and \ - option not in BUILTIN_DIR_NOPREFIX_OPTIONS: + option not in options.BUILTIN_DIR_NOPREFIX_OPTIONS: try: # Try to relativize the path. value = value.relative_to(prefix) @@ -717,15 +400,15 @@ def sanitize_dir_option_value(self, prefix: str, option: OptionKey, value: T.Any def init_builtins(self, subproject: str) -> None: # Create builtin options with default values - for key, opt in BUILTIN_OPTIONS.items(): + for key, opt in options.BUILTIN_OPTIONS.items(): self.add_builtin_option(self.options, key.evolve(subproject=subproject), opt) for for_machine in iter(MachineChoice): - for key, opt in BUILTIN_OPTIONS_PER_MACHINE.items(): + for key, opt in options.BUILTIN_OPTIONS_PER_MACHINE.items(): self.add_builtin_option(self.options, key.evolve(subproject=subproject, machine=for_machine), opt) @staticmethod def add_builtin_option(opts_map: 'MutableKeyedOptionDictType', key: OptionKey, - opt: 'BuiltinOption') -> None: + opt: 'options.BuiltinOption') -> None: if key.subproject: if opt.yielding: # This option is global and not per-subproject @@ -733,17 +416,17 @@ def add_builtin_option(opts_map: 'MutableKeyedOptionDictType', key: OptionKey, value = opts_map[key.as_root()].value else: value = None - opts_map[key] = opt.init_option(key, value, default_prefix()) + opts_map[key] = opt.init_option(key, value, options.default_prefix()) def init_backend_options(self, backend_name: str) -> None: if backend_name == 'ninja': - self.options[OptionKey('backend_max_links')] = UserIntegerOption( + self.options[OptionKey('backend_max_links')] = options.UserIntegerOption( 'backend_max_links', 'Maximum number of linker processes to run or 0 for no ' 'limit', (0, None, 0)) elif backend_name.startswith('vs'): - self.options[OptionKey('backend_startup_project')] = UserStringOption( + self.options[OptionKey('backend_startup_project')] = options.UserStringOption( 'backend_startup_project', 'Default project to execute in Visual Studio', '') @@ -881,7 +564,7 @@ def _set_others_from_buildtype(self, value: str) -> bool: @staticmethod def is_per_machine_option(optname: OptionKey) -> bool: - if optname.as_host() in BUILTIN_OPTIONS_PER_MACHINE: + if optname.as_host() in options.BUILTIN_OPTIONS_PER_MACHINE: return True return optname.lang is not None @@ -930,7 +613,7 @@ def is_cross_build(self, when_building_for: MachineChoice = MachineChoice.HOST) def copy_build_options_from_regular_ones(self) -> bool: dirty = False assert not self.is_cross_build() - for k in BUILTIN_OPTIONS_PER_MACHINE: + for k in options.BUILTIN_OPTIONS_PER_MACHINE: o = self.options[k] dirty |= self.options[k.as_build()].set_value(o.value) for bk, bv in self.options.items(): @@ -944,21 +627,21 @@ def copy_build_options_from_regular_ones(self) -> bool: return dirty - def set_options(self, options: T.Dict[OptionKey, T.Any], subproject: str = '', first_invocation: bool = False) -> bool: + def set_options(self, opts_to_set: T.Dict[OptionKey, T.Any], subproject: str = '', first_invocation: bool = False) -> bool: dirty = False if not self.is_cross_build(): - options = {k: v for k, v in options.items() if k.machine is not MachineChoice.BUILD} + opts_to_set = {k: v for k, v in opts_to_set.items() if k.machine is not MachineChoice.BUILD} # Set prefix first because it's needed to sanitize other options pfk = OptionKey('prefix') - if pfk in options: - prefix = self.sanitize_prefix(options[pfk]) + if pfk in opts_to_set: + prefix = self.sanitize_prefix(opts_to_set[pfk]) dirty |= self.options[OptionKey('prefix')].set_value(prefix) - for key in BUILTIN_DIR_NOPREFIX_OPTIONS: - if key not in options: - dirty |= self.options[key].set_value(BUILTIN_OPTIONS[key].prefixed_default(key, prefix)) + for key in options.BUILTIN_DIR_NOPREFIX_OPTIONS: + if key not in opts_to_set: + dirty |= self.options[key].set_value(options.BUILTIN_OPTIONS[key].prefixed_default(key, prefix)) unknown_options: T.List[OptionKey] = [] - for k, v in options.items(): + for k, v in opts_to_set.items(): if k == pfk: continue elif k in self.options: @@ -1255,9 +938,9 @@ def save(obj: CoreData, build_dir: str) -> str: def register_builtin_arguments(parser: argparse.ArgumentParser) -> None: - for n, b in BUILTIN_OPTIONS.items(): + for n, b in options.BUILTIN_OPTIONS.items(): b.add_to_argparse(str(n), parser, '') - for n, b in BUILTIN_OPTIONS_PER_MACHINE.items(): + for n, b in options.BUILTIN_OPTIONS_PER_MACHINE.items(): b.add_to_argparse(str(n), parser, ' (just for host machine)') b.add_to_argparse(str(n.as_build()), parser, ' (just for build machine)') parser.add_argument('-D', action='append', dest='projectoptions', default=[], metavar="option", @@ -1281,185 +964,55 @@ def parse_cmd_line_options(args: SharedCMDOptions) -> None: # Merge builtin options set with --option into the dict. for key in chain( - BUILTIN_OPTIONS.keys(), - (k.as_build() for k in BUILTIN_OPTIONS_PER_MACHINE.keys()), - BUILTIN_OPTIONS_PER_MACHINE.keys(), + options.BUILTIN_OPTIONS.keys(), + (k.as_build() for k in options.BUILTIN_OPTIONS_PER_MACHINE.keys()), + options.BUILTIN_OPTIONS_PER_MACHINE.keys(), ): name = str(key) value = getattr(args, name, None) if value is not None: if key in args.cmd_line_options: - cmdline_name = BuiltinOption.argparse_name_to_arg(name) + cmdline_name = options.BuiltinOption.argparse_name_to_arg(name) raise MesonException( f'Got argument {name} as both -D{name} and {cmdline_name}. Pick one.') args.cmd_line_options[key] = value delattr(args, name) +@dataclass +class OptionsView(abc.Mapping): + '''A view on an options dictionary for a given subproject and with overrides. + ''' -_U = T.TypeVar('_U', bound=UserOption[_T]) - -class BuiltinOption(T.Generic[_T, _U]): - - """Class for a builtin option type. - - There are some cases that are not fully supported yet. - """ - - def __init__(self, opt_type: T.Type[_U], description: str, default: T.Any, yielding: bool = True, *, - choices: T.Any = None, readonly: bool = False): - self.opt_type = opt_type - self.description = description - self.default = default - self.choices = choices - self.yielding = yielding - self.readonly = readonly - - def init_option(self, name: 'OptionKey', value: T.Optional[T.Any], prefix: str) -> _U: - """Create an instance of opt_type and return it.""" - if value is None: - value = self.prefixed_default(name, prefix) - keywords = {'yielding': self.yielding, 'value': value} - if self.choices: - keywords['choices'] = self.choices - o = self.opt_type(name.name, self.description, **keywords) - o.readonly = self.readonly - return o - - def _argparse_action(self) -> T.Optional[str]: - # If the type is a boolean, the presence of the argument in --foo form - # is to enable it. Disabling happens by using -Dfoo=false, which is - # parsed under `args.projectoptions` and does not hit this codepath. - if isinstance(self.default, bool): - return 'store_true' - return None - - def _argparse_choices(self) -> T.Any: - if self.opt_type is UserBooleanOption: - return [True, False] - elif self.opt_type is UserFeatureOption: - return UserFeatureOption.static_choices - return self.choices + # TODO: the typing here could be made more explicit using a TypeDict from + # python 3.8 or typing_extensions + original_options: KeyedOptionDictType + subproject: T.Optional[str] = None + overrides: T.Optional[T.Mapping[OptionKey, T.Union[str, int, bool, T.List[str]]]] = None - @staticmethod - def argparse_name_to_arg(name: str) -> str: - if name == 'warning_level': - return '--warnlevel' + def __getitem__(self, key: OptionKey) -> options.UserOption: + # FIXME: This is fundamentally the same algorithm than interpreter.get_option_internal(). + # We should try to share the code somehow. + key = key.evolve(subproject=self.subproject) + if not key.is_project(): + opt = self.original_options.get(key) + if opt is None or opt.yielding: + opt = self.original_options[key.as_root()] else: - return '--' + name.replace('_', '-') - - def prefixed_default(self, name: 'OptionKey', prefix: str = '') -> T.Any: - if self.opt_type in [UserComboOption, UserIntegerOption]: - return self.default - try: - return BUILTIN_DIR_NOPREFIX_OPTIONS[name][prefix] - except KeyError: - pass - return self.default + opt = self.original_options[key] + if opt.yielding: + opt = self.original_options.get(key.as_root(), opt) + if self.overrides: + override_value = self.overrides.get(key.as_root()) + if override_value is not None: + opt = copy.copy(opt) + opt.set_value(override_value) + return opt - def add_to_argparse(self, name: str, parser: argparse.ArgumentParser, help_suffix: str) -> None: - kwargs = OrderedDict() + def __iter__(self) -> T.Iterator[OptionKey]: + return iter(self.original_options) - c = self._argparse_choices() - b = self._argparse_action() - h = self.description - if not b: - h = '{} (default: {}).'.format(h.rstrip('.'), self.prefixed_default(name)) - else: - kwargs['action'] = b - if c and not b: - kwargs['choices'] = c - kwargs['default'] = argparse.SUPPRESS - kwargs['dest'] = name - - cmdline_name = self.argparse_name_to_arg(name) - parser.add_argument(cmdline_name, help=h + help_suffix, **kwargs) - - -# Update `docs/markdown/Builtin-options.md` after changing the options below -# Also update mesonlib._BUILTIN_NAMES. See the comment there for why this is required. -# Please also update completion scripts in $MESONSRC/data/shell-completions/ -BUILTIN_DIR_OPTIONS: T.Dict['OptionKey', 'BuiltinOption'] = OrderedDict([ - (OptionKey('prefix'), BuiltinOption(UserStringOption, 'Installation prefix', default_prefix())), - (OptionKey('bindir'), BuiltinOption(UserStringOption, 'Executable directory', 'bin')), - (OptionKey('datadir'), BuiltinOption(UserStringOption, 'Data file directory', default_datadir())), - (OptionKey('includedir'), BuiltinOption(UserStringOption, 'Header file directory', default_includedir())), - (OptionKey('infodir'), BuiltinOption(UserStringOption, 'Info page directory', default_infodir())), - (OptionKey('libdir'), BuiltinOption(UserStringOption, 'Library directory', default_libdir())), - (OptionKey('licensedir'), BuiltinOption(UserStringOption, 'Licenses directory', '')), - (OptionKey('libexecdir'), BuiltinOption(UserStringOption, 'Library executable directory', default_libexecdir())), - (OptionKey('localedir'), BuiltinOption(UserStringOption, 'Locale data directory', default_localedir())), - (OptionKey('localstatedir'), BuiltinOption(UserStringOption, 'Localstate data directory', 'var')), - (OptionKey('mandir'), BuiltinOption(UserStringOption, 'Manual page directory', default_mandir())), - (OptionKey('sbindir'), BuiltinOption(UserStringOption, 'System executable directory', default_sbindir())), - (OptionKey('sharedstatedir'), BuiltinOption(UserStringOption, 'Architecture-independent data directory', 'com')), - (OptionKey('sysconfdir'), BuiltinOption(UserStringOption, 'Sysconf data directory', default_sysconfdir())), -]) - -BUILTIN_CORE_OPTIONS: T.Dict['OptionKey', 'BuiltinOption'] = OrderedDict([ - (OptionKey('auto_features'), BuiltinOption(UserFeatureOption, "Override value of all 'auto' features", 'auto')), - (OptionKey('backend'), BuiltinOption(UserComboOption, 'Backend to use', 'ninja', choices=backendlist, - readonly=True)), - (OptionKey('genvslite'), - BuiltinOption( - UserComboOption, - 'Setup multiple buildtype-suffixed ninja-backend build directories, ' - 'and a [builddir]_vs containing a Visual Studio meta-backend with multiple configurations that calls into them', - 'vs2022', - choices=genvslitelist) - ), - (OptionKey('buildtype'), BuiltinOption(UserComboOption, 'Build type to use', 'debug', - choices=buildtypelist)), - (OptionKey('debug'), BuiltinOption(UserBooleanOption, 'Enable debug symbols and other information', True)), - (OptionKey('default_library'), BuiltinOption(UserComboOption, 'Default library type', 'shared', choices=['shared', 'static', 'both'], - yielding=False)), - (OptionKey('errorlogs'), BuiltinOption(UserBooleanOption, "Whether to print the logs from failing tests", True)), - (OptionKey('install_umask'), BuiltinOption(UserUmaskOption, 'Default umask to apply on permissions of installed files', '022')), - (OptionKey('layout'), BuiltinOption(UserComboOption, 'Build directory layout', 'mirror', choices=['mirror', 'flat'])), - (OptionKey('optimization'), BuiltinOption(UserComboOption, 'Optimization level', '0', choices=['plain', '0', 'g', '1', '2', '3', 's'])), - (OptionKey('prefer_static'), BuiltinOption(UserBooleanOption, 'Whether to try static linking before shared linking', False)), - (OptionKey('stdsplit'), BuiltinOption(UserBooleanOption, 'Split stdout and stderr in test logs', True)), - (OptionKey('strip'), BuiltinOption(UserBooleanOption, 'Strip targets on install', False)), - (OptionKey('unity'), BuiltinOption(UserComboOption, 'Unity build', 'off', choices=['on', 'off', 'subprojects'])), - (OptionKey('unity_size'), BuiltinOption(UserIntegerOption, 'Unity block size', (2, None, 4))), - (OptionKey('warning_level'), BuiltinOption(UserComboOption, 'Compiler warning level to use', '1', choices=['0', '1', '2', '3', 'everything'], yielding=False)), - (OptionKey('werror'), BuiltinOption(UserBooleanOption, 'Treat warnings as errors', False, yielding=False)), - (OptionKey('wrap_mode'), BuiltinOption(UserComboOption, 'Wrap mode', 'default', choices=['default', 'nofallback', 'nodownload', 'forcefallback', 'nopromote'])), - (OptionKey('force_fallback_for'), BuiltinOption(UserArrayOption, 'Force fallback for those subprojects', [])), - (OptionKey('vsenv'), BuiltinOption(UserBooleanOption, 'Activate Visual Studio environment', False, readonly=True)), - - # Pkgconfig module - (OptionKey('relocatable', module='pkgconfig'), - BuiltinOption(UserBooleanOption, 'Generate pkgconfig files as relocatable', False)), - - # Python module - (OptionKey('bytecompile', module='python'), - BuiltinOption(UserIntegerOption, 'Whether to compile bytecode', (-1, 2, 0))), - (OptionKey('install_env', module='python'), - BuiltinOption(UserComboOption, 'Which python environment to install to', 'prefix', choices=['auto', 'prefix', 'system', 'venv'])), - (OptionKey('platlibdir', module='python'), - BuiltinOption(UserStringOption, 'Directory for site-specific, platform-specific files.', '')), - (OptionKey('purelibdir', module='python'), - BuiltinOption(UserStringOption, 'Directory for site-specific, non-platform-specific files.', '')), - (OptionKey('allow_limited_api', module='python'), - BuiltinOption(UserBooleanOption, 'Whether to allow use of the Python Limited API', True)), -]) - -BUILTIN_OPTIONS = OrderedDict(chain(BUILTIN_DIR_OPTIONS.items(), BUILTIN_CORE_OPTIONS.items())) - -BUILTIN_OPTIONS_PER_MACHINE: T.Dict['OptionKey', 'BuiltinOption'] = OrderedDict([ - (OptionKey('pkg_config_path'), BuiltinOption(UserArrayOption, 'List of additional paths for pkg-config to search', [])), - (OptionKey('cmake_prefix_path'), BuiltinOption(UserArrayOption, 'List of additional prefixes for cmake to search', [])), -]) - -# Special prefix-dependent defaults for installation directories that reside in -# a path outside of the prefix in FHS and common usage. -BUILTIN_DIR_NOPREFIX_OPTIONS: T.Dict[OptionKey, T.Dict[str, str]] = { - OptionKey('sysconfdir'): {'/usr': '/etc'}, - OptionKey('localstatedir'): {'/usr': '/var', '/usr/local': '/var/local'}, - OptionKey('sharedstatedir'): {'/usr': '/var/lib', '/usr/local': '/var/local/lib'}, - OptionKey('platlibdir', module='python'): {}, - OptionKey('purelibdir', module='python'): {}, -} + def __len__(self) -> int: + return len(self.original_options) FORBIDDEN_TARGET_NAMES = frozenset({ 'clean', diff --git a/mesonbuild/interpreter/compiler.py b/mesonbuild/interpreter/compiler.py index 50a850af4482..359e8e726378 100644 --- a/mesonbuild/interpreter/compiler.py +++ b/mesonbuild/interpreter/compiler.py @@ -13,6 +13,7 @@ from .. import build from .. import coredata from .. import dependencies +from .. import options from .. import mesonlib from .. import mlog from ..compilers import SUFFIX_TO_LANG, RunResult @@ -89,7 +90,7 @@ class FindLibraryKW(ExtractRequired, ExtractSearchDirs): header_include_directories: T.List[build.IncludeDirs] header_no_builtin_args: bool header_prefix: str - header_required: T.Union[bool, coredata.UserFeatureOption] + header_required: T.Union[bool, options.UserFeatureOption] class PreprocessKW(TypedDict): output: str @@ -685,7 +686,7 @@ def notfound_library(self, libname: str) -> 'dependencies.ExternalLibrary': @typed_pos_args('compiler.find_library', str) @typed_kwargs( 'compiler.find_library', - KwargInfo('required', (bool, coredata.UserFeatureOption), default=True), + KwargInfo('required', (bool, options.UserFeatureOption), default=True), KwargInfo('has_headers', ContainerTypeInfo(list, str), listify=True, default=[], since='0.50.0'), KwargInfo('static', (bool, NoneType), since='0.51.0'), KwargInfo('disabler', bool, default=False, since='0.49.0'), diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index 50780bab2149..eb6783c644b4 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -11,6 +11,7 @@ from .. import coredata from .. import dependencies from .. import mlog +from .. import options from .. import build from .. import optinterpreter from .. import compilers @@ -163,7 +164,7 @@ def add_section(self, section: str, values: T.Dict[str, T.Any], bool_yn: bool, elif isinstance(i, Disabler): FeatureNew.single_use('disabler in summary', '0.64.0', subproject) formatted_values.append(mlog.red('NO')) - elif isinstance(i, coredata.UserOption): + elif isinstance(i, options.UserOption): FeatureNew.single_use('feature option in summary', '0.58.0', subproject) formatted_values.append(i.printable_value()) else: @@ -450,7 +451,7 @@ def build_holder_map(self) -> None: build.StructuredSources: OBJ.StructuredSourcesHolder, compilers.RunResult: compilerOBJ.TryRunResultHolder, dependencies.ExternalLibrary: OBJ.ExternalLibraryHolder, - coredata.UserFeatureOption: OBJ.FeatureOptionHolder, + options.UserFeatureOption: OBJ.FeatureOptionHolder, envconfig.MachineInfo: OBJ.MachineHolder, build.ConfigurationData: OBJ.ConfigurationDataHolder, }) @@ -1047,7 +1048,7 @@ def _do_subproject_cargo(self, subp_name: str, subdir: str, # FIXME: Are there other files used by cargo interpreter? [os.path.join(subdir, 'Cargo.toml')]) - def get_option_internal(self, optname: str) -> coredata.UserOption: + def get_option_internal(self, optname: str) -> options.UserOption: key = OptionKey.from_string(optname).evolve(subproject=self.subproject) if not key.is_project(): @@ -1056,7 +1057,7 @@ def get_option_internal(self, optname: str) -> coredata.UserOption: if v is None or v.yielding: v = opts.get(key.as_root()) if v is not None: - assert isinstance(v, coredata.UserOption), 'for mypy' + assert isinstance(v, options.UserOption), 'for mypy' return v try: @@ -1085,7 +1086,7 @@ def get_option_internal(self, optname: str) -> coredata.UserOption: @typed_pos_args('get_option', str) @noKwargs def func_get_option(self, nodes: mparser.BaseNode, args: T.Tuple[str], - kwargs: 'TYPE_kwargs') -> T.Union[coredata.UserOption, 'TYPE_var']: + kwargs: 'TYPE_kwargs') -> T.Union[options.UserOption, 'TYPE_var']: optname = args[0] if ':' in optname: raise InterpreterException('Having a colon in option name is forbidden, ' @@ -1096,10 +1097,10 @@ def func_get_option(self, nodes: mparser.BaseNode, args: T.Tuple[str], raise InterpreterException(f'Invalid option name {optname!r}') opt = self.get_option_internal(optname) - if isinstance(opt, coredata.UserFeatureOption): + if isinstance(opt, options.UserFeatureOption): opt.name = optname return opt - elif isinstance(opt, coredata.UserOption): + elif isinstance(opt, options.UserOption): if isinstance(opt.value, str): return P_OBJ.OptionString(opt.value, f'{{{optname}}}') return opt.value diff --git a/mesonbuild/interpreter/interpreterobjects.py b/mesonbuild/interpreter/interpreterobjects.py index 8cd9a2be6286..adec0d589be8 100644 --- a/mesonbuild/interpreter/interpreterobjects.py +++ b/mesonbuild/interpreter/interpreterobjects.py @@ -8,7 +8,7 @@ from pathlib import Path, PurePath from .. import mesonlib -from .. import coredata +from .. import options from .. import build from .. import mlog @@ -52,7 +52,7 @@ def extract_required_kwarg(kwargs: 'kwargs.ExtractRequired', disabled = False required = False feature: T.Optional[str] = None - if isinstance(val, coredata.UserFeatureOption): + if isinstance(val, options.UserFeatureOption): if not feature_check: feature_check = FeatureNew('User option "feature"', '0.47.0') feature_check.use(subproject) @@ -85,12 +85,12 @@ def extract_search_dirs(kwargs: 'kwargs.ExtractSearchDirs') -> T.List[str]: raise InvalidCode(f'Search directory {d} is not an absolute path.') return [str(s) for s in search_dirs] -class FeatureOptionHolder(ObjectHolder[coredata.UserFeatureOption]): - def __init__(self, option: coredata.UserFeatureOption, interpreter: 'Interpreter'): +class FeatureOptionHolder(ObjectHolder[options.UserFeatureOption]): + def __init__(self, option: options.UserFeatureOption, interpreter: 'Interpreter'): super().__init__(option, interpreter) if option and option.is_auto(): # TODO: we need to cast here because options is not a TypedDict - auto = T.cast('coredata.UserFeatureOption', self.env.coredata.options[OptionKey('auto_features')]) + auto = T.cast('options.UserFeatureOption', self.env.coredata.options[OptionKey('auto_features')]) self.held_object = copy.copy(auto) self.held_object.name = option.name self.methods.update({'enabled': self.enabled_method, @@ -108,12 +108,12 @@ def __init__(self, option: coredata.UserFeatureOption, interpreter: 'Interpreter def value(self) -> str: return 'disabled' if not self.held_object else self.held_object.value - def as_disabled(self) -> coredata.UserFeatureOption: + def as_disabled(self) -> options.UserFeatureOption: disabled = copy.deepcopy(self.held_object) disabled.value = 'disabled' return disabled - def as_enabled(self) -> coredata.UserFeatureOption: + def as_enabled(self) -> options.UserFeatureOption: enabled = copy.deepcopy(self.held_object) enabled.value = 'enabled' return enabled @@ -139,7 +139,7 @@ def allowed_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: def auto_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: return self.value == 'auto' - def _disable_if(self, condition: bool, message: T.Optional[str]) -> coredata.UserFeatureOption: + def _disable_if(self, condition: bool, message: T.Optional[str]) -> options.UserFeatureOption: if not condition: return copy.deepcopy(self.held_object) @@ -156,7 +156,7 @@ def _disable_if(self, condition: bool, message: T.Optional[str]) -> coredata.Use 'feature_option.require', _ERROR_MSG_KW, ) - def require_method(self, args: T.Tuple[bool], kwargs: 'kwargs.FeatureOptionRequire') -> coredata.UserFeatureOption: + def require_method(self, args: T.Tuple[bool], kwargs: 'kwargs.FeatureOptionRequire') -> options.UserFeatureOption: return self._disable_if(not args[0], kwargs['error_message']) @FeatureNew('feature_option.disable_if()', '1.1.0') @@ -165,7 +165,7 @@ def require_method(self, args: T.Tuple[bool], kwargs: 'kwargs.FeatureOptionRequi 'feature_option.disable_if', _ERROR_MSG_KW, ) - def disable_if_method(self, args: T.Tuple[bool], kwargs: 'kwargs.FeatureOptionRequire') -> coredata.UserFeatureOption: + def disable_if_method(self, args: T.Tuple[bool], kwargs: 'kwargs.FeatureOptionRequire') -> options.UserFeatureOption: return self._disable_if(args[0], kwargs['error_message']) @FeatureNew('feature_option.enable_if()', '1.1.0') @@ -174,7 +174,7 @@ def disable_if_method(self, args: T.Tuple[bool], kwargs: 'kwargs.FeatureOptionRe 'feature_option.enable_if', _ERROR_MSG_KW, ) - def enable_if_method(self, args: T.Tuple[bool], kwargs: 'kwargs.FeatureOptionRequire') -> coredata.UserFeatureOption: + def enable_if_method(self, args: T.Tuple[bool], kwargs: 'kwargs.FeatureOptionRequire') -> options.UserFeatureOption: if not args[0]: return copy.deepcopy(self.held_object) @@ -188,13 +188,13 @@ def enable_if_method(self, args: T.Tuple[bool], kwargs: 'kwargs.FeatureOptionReq @FeatureNew('feature_option.disable_auto_if()', '0.59.0') @noKwargs @typed_pos_args('feature_option.disable_auto_if', bool) - def disable_auto_if_method(self, args: T.Tuple[bool], kwargs: TYPE_kwargs) -> coredata.UserFeatureOption: + def disable_auto_if_method(self, args: T.Tuple[bool], kwargs: TYPE_kwargs) -> options.UserFeatureOption: return copy.deepcopy(self.held_object) if self.value != 'auto' or not args[0] else self.as_disabled() @FeatureNew('feature_option.enable_auto_if()', '1.1.0') @noKwargs @typed_pos_args('feature_option.enable_auto_if', bool) - def enable_auto_if_method(self, args: T.Tuple[bool], kwargs: TYPE_kwargs) -> coredata.UserFeatureOption: + def enable_auto_if_method(self, args: T.Tuple[bool], kwargs: TYPE_kwargs) -> options.UserFeatureOption: return self.as_enabled() if self.value == 'auto' and args[0] else copy.deepcopy(self.held_object) diff --git a/mesonbuild/interpreter/kwargs.py b/mesonbuild/interpreter/kwargs.py index 17f7876a04d0..85779bc00b08 100644 --- a/mesonbuild/interpreter/kwargs.py +++ b/mesonbuild/interpreter/kwargs.py @@ -10,7 +10,7 @@ from typing_extensions import TypedDict, Literal, Protocol, NotRequired from .. import build -from .. import coredata +from .. import options from ..compilers import Compiler from ..dependencies.base import Dependency from ..mesonlib import EnvironmentVariables, MachineChoice, File, FileMode, FileOrString, OptionKey @@ -73,7 +73,7 @@ class ExtractRequired(TypedDict): a boolean or a feature option should inherit it's arguments from this class. """ - required: T.Union[bool, coredata.UserFeatureOption] + required: T.Union[bool, options.UserFeatureOption] class ExtractSearchDirs(TypedDict): diff --git a/mesonbuild/interpreter/type_checking.py b/mesonbuild/interpreter/type_checking.py index 9b7e35c637ef..2856136361f8 100644 --- a/mesonbuild/interpreter/type_checking.py +++ b/mesonbuild/interpreter/type_checking.py @@ -11,7 +11,7 @@ from ..build import (CustomTarget, BuildTarget, CustomTargetIndex, ExtractedObjects, GeneratedList, IncludeDirs, BothLibraries, SharedLibrary, StaticLibrary, Jar, Executable, StructuredSources) -from ..coredata import UserFeatureOption +from ..options import UserFeatureOption from ..dependencies import Dependency, InternalDependency from ..interpreterbase.decorators import KwargInfo, ContainerTypeInfo from ..mesonlib import (File, FileMode, MachineChoice, listify, has_path_sep, diff --git a/mesonbuild/interpreterbase/helpers.py b/mesonbuild/interpreterbase/helpers.py index 3942f2c9f4c4..0b0436209dbe 100644 --- a/mesonbuild/interpreterbase/helpers.py +++ b/mesonbuild/interpreterbase/helpers.py @@ -5,7 +5,7 @@ from .. import mesonlib, mparser from .exceptions import InterpreterException, InvalidArguments -from ..coredata import UserOption +from ..options import UserOption import collections.abc diff --git a/mesonbuild/mconf.py b/mesonbuild/mconf.py index 2cef24fd77e1..48359ded23fb 100644 --- a/mesonbuild/mconf.py +++ b/mesonbuild/mconf.py @@ -14,6 +14,7 @@ from . import build from . import coredata +from . import options from . import environment from . import mesonlib from . import mintro @@ -83,12 +84,12 @@ def __init__(self, build_dir: str): # if the option file has been updated, reload it # This cannot handle options for a new subproject that has not yet # been configured. - for sub, options in self.coredata.options_files.items(): - if options is not None and os.path.exists(options[0]): - opfile = options[0] + for sub, conf_options in self.coredata.options_files.items(): + if conf_options is not None and os.path.exists(conf_options[0]): + opfile = conf_options[0] with open(opfile, 'rb') as f: ophash = hashlib.sha1(f.read()).hexdigest() - if ophash != options[1]: + if ophash != conf_options[1]: oi = OptionInterpreter(sub) oi.process(opfile) self.coredata.update_project_options(oi.options, sub) @@ -223,18 +224,18 @@ def add_section(self, section: str) -> None: self._add_line(mlog.normal_yellow(section + ':'), '', '', '') self.print_margin = 2 - def print_options(self, title: str, options: 'coredata.KeyedOptionDictType') -> None: - if not options: + def print_options(self, title: str, opts: 'coredata.KeyedOptionDictType') -> None: + if not opts: return if title: self.add_title(title) - auto = T.cast('coredata.UserFeatureOption', self.coredata.options[OptionKey('auto_features')]) - for k, o in sorted(options.items()): + auto = T.cast('options.UserFeatureOption', self.coredata.options[OptionKey('auto_features')]) + for k, o in sorted(opts.items()): printable_value = o.printable_value() root = k.as_root() if o.yielding and k.subproject and root in self.coredata.options: printable_value = '' - if isinstance(o, coredata.UserFeatureOption) and o.is_auto(): + if isinstance(o, options.UserFeatureOption) and o.is_auto(): printable_value = auto.printable_value() self.add_option(str(root), o.description, printable_value, o.choices) @@ -255,7 +256,7 @@ def print_default_values_warning() -> None: if not self.default_values_only: mlog.log(' Build dir ', self.build_dir) - dir_option_names = set(coredata.BUILTIN_DIR_OPTIONS) + dir_option_names = set(options.BUILTIN_DIR_OPTIONS) test_option_names = {OptionKey('errorlogs'), OptionKey('stdsplit')} diff --git a/mesonbuild/mintro.py b/mesonbuild/mintro.py index bdbb59e3a18d..a5ce72a1f396 100644 --- a/mesonbuild/mintro.py +++ b/mesonbuild/mintro.py @@ -19,7 +19,7 @@ import sys import typing as T -from . import build, mesonlib, coredata as cdata +from . import build, mesonlib, options, coredata as cdata from .ast import IntrospectionInterpreter, BUILD_TARGET_FUNCTIONS, AstConditionLevel, AstIDGenerator, AstIndentationGenerator, AstJSONPrinter from .backend import backends from .dependencies import Dependency @@ -88,7 +88,7 @@ def add_arguments(parser: argparse.ArgumentParser) -> None: flag = '--' + key.replace('_', '-') parser.add_argument(flag, action='store_true', dest=key, default=False, help=val.desc) - parser.add_argument('--backend', choices=sorted(cdata.backendlist), dest='backend', default='ninja', + parser.add_argument('--backend', choices=sorted(options.backendlist), dest='backend', default='ninja', help='The backend to use for the --buildoptions introspection.') parser.add_argument('-a', '--all', action='store_true', dest='all', default=False, help='Print all available information.') @@ -284,7 +284,7 @@ def list_buildoptions(coredata: cdata.CoreData, subprojects: T.Optional[T.List[s optlist: T.List[T.Dict[str, T.Union[str, bool, int, T.List[str]]]] = [] subprojects = subprojects or [] - dir_option_names = set(cdata.BUILTIN_DIR_OPTIONS) + dir_option_names = set(options.BUILTIN_DIR_OPTIONS) test_option_names = {OptionKey('errorlogs'), OptionKey('stdsplit')} @@ -302,20 +302,20 @@ def list_buildoptions(coredata: cdata.CoreData, subprojects: T.Optional[T.List[s for s in subprojects: core_options[k.evolve(subproject=s)] = v - def add_keys(options: 'cdata.KeyedOptionDictType', section: str) -> None: - for key, opt in sorted(options.items()): + def add_keys(opts: 'cdata.KeyedOptionDictType', section: str) -> None: + for key, opt in sorted(opts.items()): optdict = {'name': str(key), 'value': opt.value, 'section': section, 'machine': key.machine.get_lower_case_name() if coredata.is_per_machine_option(key) else 'any'} - if isinstance(opt, cdata.UserStringOption): + if isinstance(opt, options.UserStringOption): typestr = 'string' - elif isinstance(opt, cdata.UserBooleanOption): + elif isinstance(opt, options.UserBooleanOption): typestr = 'boolean' - elif isinstance(opt, cdata.UserComboOption): + elif isinstance(opt, options.UserComboOption): optdict['choices'] = opt.choices typestr = 'combo' - elif isinstance(opt, cdata.UserIntegerOption): + elif isinstance(opt, options.UserIntegerOption): typestr = 'integer' - elif isinstance(opt, cdata.UserArrayOption): + elif isinstance(opt, options.UserArrayOption): typestr = 'array' if opt.choices: optdict['choices'] = opt.choices diff --git a/mesonbuild/modules/_qt.py b/mesonbuild/modules/_qt.py index 7effa1f58401..ebb8a3994097 100644 --- a/mesonbuild/modules/_qt.py +++ b/mesonbuild/modules/_qt.py @@ -11,7 +11,7 @@ from . import ModuleReturnValue, ExtensionModule from .. import build -from .. import coredata +from .. import options from .. import mlog from ..dependencies import find_external_dependency, Dependency, ExternalLibrary, InternalDependency from ..mesonlib import MesonException, File, version_compare, Popen_safe @@ -256,7 +256,7 @@ def _parse_qrc_deps(self, state: 'ModuleState', @noPosargs @typed_kwargs( 'qt.has_tools', - KwargInfo('required', (bool, coredata.UserFeatureOption), default=False), + KwargInfo('required', (bool, options.UserFeatureOption), default=False), KwargInfo('method', str, default='auto'), ) def has_tools(self, state: 'ModuleState', args: T.Tuple, kwargs: 'HasToolKwArgs') -> bool: diff --git a/mesonbuild/modules/pkgconfig.py b/mesonbuild/modules/pkgconfig.py index ebe0d92d5cb1..1a730707986f 100644 --- a/mesonbuild/modules/pkgconfig.py +++ b/mesonbuild/modules/pkgconfig.py @@ -14,7 +14,7 @@ from .. import dependencies from .. import mesonlib from .. import mlog -from ..coredata import BUILTIN_DIR_OPTIONS +from ..options import BUILTIN_DIR_OPTIONS from ..dependencies.pkgconfig import PkgConfigDependency, PkgConfigInterface from ..interpreter.type_checking import D_MODULE_VERSIONS_KW, INSTALL_DIR_KW, VARIABLES_KW, NoneType from ..interpreterbase import FeatureNew, FeatureDeprecated diff --git a/mesonbuild/modules/python.py b/mesonbuild/modules/python.py index 59b5050c07b8..d195a3fa5291 100644 --- a/mesonbuild/modules/python.py +++ b/mesonbuild/modules/python.py @@ -9,7 +9,7 @@ from . import ExtensionModule, ModuleInfo from .. import mesonlib from .. import mlog -from ..coredata import UserFeatureOption +from ..options import UserFeatureOption from ..build import known_shmod_kwargs, CustomTarget, CustomTargetIndex, BuildTarget, GeneratedList, StructuredSources, ExtractedObjects, SharedModule from ..dependencies import NotFoundDependency from ..dependencies.detect import get_dep_identifier, find_external_dependency diff --git a/mesonbuild/optinterpreter.py b/mesonbuild/optinterpreter.py index 599da65d3273..ffa46cda650e 100644 --- a/mesonbuild/optinterpreter.py +++ b/mesonbuild/optinterpreter.py @@ -7,6 +7,7 @@ import typing as T from . import coredata +from . import options from . import mesonlib from . import mparser from . import mlog @@ -66,7 +67,7 @@ class OptionInterpreter: def __init__(self, subproject: 'SubProject') -> None: self.options: 'coredata.MutableKeyedOptionDictType' = {} self.subproject = subproject - self.option_types: T.Dict[str, T.Callable[..., coredata.UserOption]] = { + self.option_types: T.Dict[str, T.Callable[..., options.UserOption]] = { 'string': self.string_parser, 'boolean': self.boolean_parser, 'combo': self.combo_parser, @@ -179,7 +180,7 @@ def evaluate_statement(self, node: mparser.BaseNode) -> None: since='0.60.0', since_values={str: '0.63.0'}, ), - KwargInfo('yield', bool, default=coredata.DEFAULT_YIELDING, since='0.45.0'), + KwargInfo('yield', bool, default=options.DEFAULT_YIELDING, since='0.45.0'), allow_unknown=True, ) @typed_pos_args('option', str) @@ -208,8 +209,8 @@ def func_option(self, args: T.Tuple[str], kwargs: 'FuncOptionArgs') -> None: 'string option', KwargInfo('value', str, default=''), ) - def string_parser(self, name: str, description: str, args: T.Tuple[bool, _DEPRECATED_ARGS], kwargs: StringArgs) -> coredata.UserOption: - return coredata.UserStringOption(name, description, kwargs['value'], *args) + def string_parser(self, name: str, description: str, args: T.Tuple[bool, _DEPRECATED_ARGS], kwargs: StringArgs) -> options.UserOption: + return options.UserStringOption(name, description, kwargs['value'], *args) @typed_kwargs( 'boolean option', @@ -221,20 +222,20 @@ def string_parser(self, name: str, description: str, args: T.Tuple[bool, _DEPREC deprecated_values={str: ('1.1.0', 'use a boolean, not a string')}, ), ) - def boolean_parser(self, name: str, description: str, args: T.Tuple[bool, _DEPRECATED_ARGS], kwargs: BooleanArgs) -> coredata.UserOption: - return coredata.UserBooleanOption(name, description, kwargs['value'], *args) + def boolean_parser(self, name: str, description: str, args: T.Tuple[bool, _DEPRECATED_ARGS], kwargs: BooleanArgs) -> options.UserOption: + return options.UserBooleanOption(name, description, kwargs['value'], *args) @typed_kwargs( 'combo option', KwargInfo('value', (str, NoneType)), KwargInfo('choices', ContainerTypeInfo(list, str, allow_empty=False), required=True), ) - def combo_parser(self, name: str, description: str, args: T.Tuple[bool, _DEPRECATED_ARGS], kwargs: ComboArgs) -> coredata.UserOption: + def combo_parser(self, name: str, description: str, args: T.Tuple[bool, _DEPRECATED_ARGS], kwargs: ComboArgs) -> options.UserOption: choices = kwargs['choices'] value = kwargs['value'] if value is None: value = kwargs['choices'][0] - return coredata.UserComboOption(name, description, choices, value, *args) + return options.UserComboOption(name, description, choices, value, *args) @typed_kwargs( 'integer option', @@ -248,17 +249,17 @@ def combo_parser(self, name: str, description: str, args: T.Tuple[bool, _DEPRECA KwargInfo('min', (int, NoneType)), KwargInfo('max', (int, NoneType)), ) - def integer_parser(self, name: str, description: str, args: T.Tuple[bool, _DEPRECATED_ARGS], kwargs: IntegerArgs) -> coredata.UserOption: + def integer_parser(self, name: str, description: str, args: T.Tuple[bool, _DEPRECATED_ARGS], kwargs: IntegerArgs) -> options.UserOption: value = kwargs['value'] inttuple = (kwargs['min'], kwargs['max'], value) - return coredata.UserIntegerOption(name, description, inttuple, *args) + return options.UserIntegerOption(name, description, inttuple, *args) @typed_kwargs( 'string array option', KwargInfo('value', (ContainerTypeInfo(list, str), str, NoneType)), KwargInfo('choices', ContainerTypeInfo(list, str), default=[]), ) - def string_array_parser(self, name: str, description: str, args: T.Tuple[bool, _DEPRECATED_ARGS], kwargs: StringArrayArgs) -> coredata.UserOption: + def string_array_parser(self, name: str, description: str, args: T.Tuple[bool, _DEPRECATED_ARGS], kwargs: StringArrayArgs) -> options.UserOption: choices = kwargs['choices'] value = kwargs['value'] if kwargs['value'] is not None else choices if isinstance(value, str): @@ -266,14 +267,14 @@ def string_array_parser(self, name: str, description: str, args: T.Tuple[bool, _ FeatureDeprecated('String value for array option', '1.3.0').use(self.subproject) else: raise mesonlib.MesonException('Value does not define an array: ' + value) - return coredata.UserArrayOption(name, description, value, - choices=choices, - yielding=args[0], - deprecated=args[1]) + return options.UserArrayOption(name, description, value, + choices=choices, + yielding=args[0], + deprecated=args[1]) @typed_kwargs( 'feature option', KwargInfo('value', str, default='auto', validator=in_set_validator({'auto', 'enabled', 'disabled'})), ) - def feature_parser(self, name: str, description: str, args: T.Tuple[bool, _DEPRECATED_ARGS], kwargs: FeatureArgs) -> coredata.UserOption: - return coredata.UserFeatureOption(name, description, kwargs['value'], *args) + def feature_parser(self, name: str, description: str, args: T.Tuple[bool, _DEPRECATED_ARGS], kwargs: FeatureArgs) -> options.UserOption: + return options.UserFeatureOption(name, description, kwargs['value'], *args) diff --git a/mesonbuild/options.py b/mesonbuild/options.py new file mode 100644 index 000000000000..2699dd5ce6ed --- /dev/null +++ b/mesonbuild/options.py @@ -0,0 +1,480 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2013-2024 Contributors to the The Meson project + +from collections import OrderedDict +from itertools import chain +import argparse + +from .mesonlib import ( + HoldableObject, + OptionKey, + default_prefix, + default_datadir, + default_includedir, + default_infodir, + default_libdir, + default_libexecdir, + default_localedir, + default_mandir, + default_sbindir, + default_sysconfdir, + MesonException, + listify_array_value, +) + +from . import mlog + +import typing as T + +DEFAULT_YIELDING = False + +# Can't bind this near the class method it seems, sadly. +_T = T.TypeVar('_T') + +backendlist = ['ninja', 'vs', 'vs2010', 'vs2012', 'vs2013', 'vs2015', 'vs2017', 'vs2019', 'vs2022', 'xcode', 'none'] +genvslitelist = ['vs2022'] +buildtypelist = ['plain', 'debug', 'debugoptimized', 'release', 'minsize', 'custom'] + + +class UserOption(T.Generic[_T], HoldableObject): + def __init__(self, name: str, description: str, choices: T.Optional[T.Union[str, T.List[_T]]], + yielding: bool, + deprecated: T.Union[bool, str, T.Dict[str, str], T.List[str]] = False): + super().__init__() + self.name = name + self.choices = choices + self.description = description + if not isinstance(yielding, bool): + raise MesonException('Value of "yielding" must be a boolean.') + self.yielding = yielding + self.deprecated = deprecated + self.readonly = False + + def listify(self, value: T.Any) -> T.List[T.Any]: + return [value] + + def printable_value(self) -> T.Union[str, int, bool, T.List[T.Union[str, int, bool]]]: + assert isinstance(self.value, (str, int, bool, list)) + return self.value + + # Check that the input is a valid value and return the + # "cleaned" or "native" version. For example the Boolean + # option could take the string "true" and return True. + def validate_value(self, value: T.Any) -> _T: + raise RuntimeError('Derived option class did not override validate_value.') + + def set_value(self, newvalue: T.Any) -> bool: + oldvalue = getattr(self, 'value', None) + self.value = self.validate_value(newvalue) + return self.value != oldvalue + +_U = T.TypeVar('_U', bound=UserOption[_T]) + + +class UserStringOption(UserOption[str]): + def __init__(self, name: str, description: str, value: T.Any, yielding: bool = DEFAULT_YIELDING, + deprecated: T.Union[bool, str, T.Dict[str, str], T.List[str]] = False): + super().__init__(name, description, None, yielding, deprecated) + self.set_value(value) + + def validate_value(self, value: T.Any) -> str: + if not isinstance(value, str): + raise MesonException(f'The value of option "{self.name}" is "{value}", which is not a string.') + return value + +class UserBooleanOption(UserOption[bool]): + def __init__(self, name: str, description: str, value: bool, yielding: bool = DEFAULT_YIELDING, + deprecated: T.Union[bool, str, T.Dict[str, str], T.List[str]] = False): + super().__init__(name, description, [True, False], yielding, deprecated) + self.set_value(value) + + def __bool__(self) -> bool: + return self.value + + def validate_value(self, value: T.Any) -> bool: + if isinstance(value, bool): + return value + if not isinstance(value, str): + raise MesonException(f'Option "{self.name}" value {value} cannot be converted to a boolean') + if value.lower() == 'true': + return True + if value.lower() == 'false': + return False + raise MesonException(f'Option "{self.name}" value {value} is not boolean (true or false).') + +class UserIntegerOption(UserOption[int]): + def __init__(self, name: str, description: str, value: T.Any, yielding: bool = DEFAULT_YIELDING, + deprecated: T.Union[bool, str, T.Dict[str, str], T.List[str]] = False): + min_value, max_value, default_value = value + self.min_value = min_value + self.max_value = max_value + c: T.List[str] = [] + if min_value is not None: + c.append('>=' + str(min_value)) + if max_value is not None: + c.append('<=' + str(max_value)) + choices = ', '.join(c) + super().__init__(name, description, choices, yielding, deprecated) + self.set_value(default_value) + + def validate_value(self, value: T.Any) -> int: + if isinstance(value, str): + value = self.toint(value) + if not isinstance(value, int): + raise MesonException(f'Value {value!r} for option "{self.name}" is not an integer.') + if self.min_value is not None and value < self.min_value: + raise MesonException(f'Value {value} for option "{self.name}" is less than minimum value {self.min_value}.') + if self.max_value is not None and value > self.max_value: + raise MesonException(f'Value {value} for option "{self.name}" is more than maximum value {self.max_value}.') + return value + + def toint(self, valuestring: str) -> int: + try: + return int(valuestring) + except ValueError: + raise MesonException(f'Value string "{valuestring}" for option "{self.name}" is not convertible to an integer.') + +class OctalInt(int): + # NinjaBackend.get_user_option_args uses str() to converts it to a command line option + # UserUmaskOption.toint() uses int(str, 8) to convert it to an integer + # So we need to use oct instead of dec here if we do not want values to be misinterpreted. + def __str__(self) -> str: + return oct(int(self)) + +class UserUmaskOption(UserIntegerOption, UserOption[T.Union[str, OctalInt]]): + def __init__(self, name: str, description: str, value: T.Any, yielding: bool = DEFAULT_YIELDING, + deprecated: T.Union[bool, str, T.Dict[str, str], T.List[str]] = False): + super().__init__(name, description, (0, 0o777, value), yielding, deprecated) + self.choices = ['preserve', '0000-0777'] + + def printable_value(self) -> str: + if self.value == 'preserve': + return self.value + return format(self.value, '04o') + + def validate_value(self, value: T.Any) -> T.Union[str, OctalInt]: + if value == 'preserve': + return 'preserve' + return OctalInt(super().validate_value(value)) + + def toint(self, valuestring: T.Union[str, OctalInt]) -> int: + try: + return int(valuestring, 8) + except ValueError as e: + raise MesonException(f'Invalid mode for option "{self.name}" {e}') + +class UserComboOption(UserOption[str]): + def __init__(self, name: str, description: str, choices: T.List[str], value: T.Any, + yielding: bool = DEFAULT_YIELDING, + deprecated: T.Union[bool, str, T.Dict[str, str], T.List[str]] = False): + super().__init__(name, description, choices, yielding, deprecated) + if not isinstance(self.choices, list): + raise MesonException(f'Combo choices for option "{self.name}" must be an array.') + for i in self.choices: + if not isinstance(i, str): + raise MesonException(f'Combo choice elements for option "{self.name}" must be strings.') + self.set_value(value) + + def validate_value(self, value: T.Any) -> str: + if value not in self.choices: + if isinstance(value, bool): + _type = 'boolean' + elif isinstance(value, (int, float)): + _type = 'number' + else: + _type = 'string' + optionsstring = ', '.join([f'"{item}"' for item in self.choices]) + raise MesonException('Value "{}" (of type "{}") for option "{}" is not one of the choices.' + ' Possible choices are (as string): {}.'.format( + value, _type, self.name, optionsstring)) + return value + +class UserArrayOption(UserOption[T.List[str]]): + def __init__(self, name: str, description: str, value: T.Union[str, T.List[str]], + split_args: bool = False, + allow_dups: bool = False, yielding: bool = DEFAULT_YIELDING, + choices: T.Optional[T.List[str]] = None, + deprecated: T.Union[bool, str, T.Dict[str, str], T.List[str]] = False): + super().__init__(name, description, choices if choices is not None else [], yielding, deprecated) + self.split_args = split_args + self.allow_dups = allow_dups + self.set_value(value) + + def listify(self, value: T.Any) -> T.List[T.Any]: + try: + return listify_array_value(value, self.split_args) + except MesonException as e: + raise MesonException(f'error in option "{self.name}": {e!s}') + + def validate_value(self, value: T.Union[str, T.List[str]]) -> T.List[str]: + newvalue = self.listify(value) + + if not self.allow_dups and len(set(newvalue)) != len(newvalue): + msg = 'Duplicated values in array option is deprecated. ' \ + 'This will become a hard error in the future.' + mlog.deprecation(msg) + for i in newvalue: + if not isinstance(i, str): + raise MesonException(f'String array element "{newvalue!s}" for option "{self.name}" is not a string.') + if self.choices: + bad = [x for x in newvalue if x not in self.choices] + if bad: + raise MesonException('Value{} "{}" for option "{}" {} not in allowed choices: "{}"'.format( + '' if len(bad) == 1 else 's', + ', '.join(bad), + self.name, + 'is' if len(bad) == 1 else 'are', + ', '.join(self.choices)) + ) + return newvalue + + def extend_value(self, value: T.Union[str, T.List[str]]) -> None: + """Extend the value with an additional value.""" + new = self.validate_value(value) + self.set_value(self.value + new) + + +class UserFeatureOption(UserComboOption): + static_choices = ['enabled', 'disabled', 'auto'] + + def __init__(self, name: str, description: str, value: T.Any, yielding: bool = DEFAULT_YIELDING, + deprecated: T.Union[bool, str, T.Dict[str, str], T.List[str]] = False): + super().__init__(name, description, self.static_choices, value, yielding, deprecated) + self.name: T.Optional[str] = None # TODO: Refactor options to all store their name + + def is_enabled(self) -> bool: + return self.value == 'enabled' + + def is_disabled(self) -> bool: + return self.value == 'disabled' + + def is_auto(self) -> bool: + return self.value == 'auto' + +class UserStdOption(UserComboOption): + ''' + UserOption specific to c_std and cpp_std options. User can set a list of + STDs in preference order and it selects the first one supported by current + compiler. + + For historical reasons, some compilers (msvc) allowed setting a GNU std and + silently fell back to C std. This is now deprecated. Projects that support + both GNU and MSVC compilers should set e.g. c_std=gnu11,c11. + + This is not using self.deprecated mechanism we already have for project + options because we want to print a warning if ALL values are deprecated, not + if SOME values are deprecated. + ''' + def __init__(self, lang: str, all_stds: T.List[str]) -> None: + self.lang = lang.lower() + self.all_stds = ['none'] + all_stds + # Map a deprecated std to its replacement. e.g. gnu11 -> c11. + self.deprecated_stds: T.Dict[str, str] = {} + opt_name = 'cpp_std' if lang == 'c++' else f'{lang}_std' + super().__init__(opt_name, f'{lang} language standard to use', ['none'], 'none') + + def set_versions(self, versions: T.List[str], gnu: bool = False, gnu_deprecated: bool = False) -> None: + assert all(std in self.all_stds for std in versions) + self.choices += versions + if gnu: + gnu_stds_map = {f'gnu{std[1:]}': std for std in versions} + if gnu_deprecated: + self.deprecated_stds.update(gnu_stds_map) + else: + self.choices += gnu_stds_map.keys() + + def validate_value(self, value: T.Union[str, T.List[str]]) -> str: + try: + candidates = listify_array_value(value) + except MesonException as e: + raise MesonException(f'error in option "{self.name}": {e!s}') + unknown = ','.join(std for std in candidates if std not in self.all_stds) + if unknown: + raise MesonException(f'Unknown option "{self.name}" value {unknown}. Possible values are {self.all_stds}.') + # Check first if any of the candidates are not deprecated + for std in candidates: + if std in self.choices: + return std + # Fallback to a deprecated std if any + for std in candidates: + newstd = self.deprecated_stds.get(std) + if newstd is not None: + mlog.deprecation( + f'None of the values {candidates} are supported by the {self.lang} compiler.\n' + + f'However, the deprecated {std} std currently falls back to {newstd}.\n' + + 'This will be an error in the future.\n' + + 'If the project supports both GNU and MSVC compilers, a value such as\n' + + '"c_std=gnu11,c11" specifies that GNU is preferred but it can safely fallback to plain c11.') + return newstd + raise MesonException(f'None of values {candidates} are supported by the {self.lang.upper()} compiler. ' + + f'Possible values for option "{self.name}" are {self.choices}') + + +class BuiltinOption(T.Generic[_T, _U]): + + """Class for a builtin option type. + + There are some cases that are not fully supported yet. + """ + + def __init__(self, opt_type: T.Type[_U], description: str, default: T.Any, yielding: bool = True, *, + choices: T.Any = None, readonly: bool = False): + self.opt_type = opt_type + self.description = description + self.default = default + self.choices = choices + self.yielding = yielding + self.readonly = readonly + + def init_option(self, name: 'OptionKey', value: T.Optional[T.Any], prefix: str) -> _U: + """Create an instance of opt_type and return it.""" + if value is None: + value = self.prefixed_default(name, prefix) + keywords = {'yielding': self.yielding, 'value': value} + if self.choices: + keywords['choices'] = self.choices + o = self.opt_type(name.name, self.description, **keywords) + o.readonly = self.readonly + return o + + def _argparse_action(self) -> T.Optional[str]: + # If the type is a boolean, the presence of the argument in --foo form + # is to enable it. Disabling happens by using -Dfoo=false, which is + # parsed under `args.projectoptions` and does not hit this codepath. + if isinstance(self.default, bool): + return 'store_true' + return None + + def _argparse_choices(self) -> T.Any: + if self.opt_type is UserBooleanOption: + return [True, False] + elif self.opt_type is UserFeatureOption: + return UserFeatureOption.static_choices + return self.choices + + @staticmethod + def argparse_name_to_arg(name: str) -> str: + if name == 'warning_level': + return '--warnlevel' + else: + return '--' + name.replace('_', '-') + + def prefixed_default(self, name: 'OptionKey', prefix: str = '') -> T.Any: + if self.opt_type in [UserComboOption, UserIntegerOption]: + return self.default + try: + return BUILTIN_DIR_NOPREFIX_OPTIONS[name][prefix] + except KeyError: + pass + return self.default + + def add_to_argparse(self, name: str, parser: argparse.ArgumentParser, help_suffix: str) -> None: + kwargs = OrderedDict() + + c = self._argparse_choices() + b = self._argparse_action() + h = self.description + if not b: + h = '{} (default: {}).'.format(h.rstrip('.'), self.prefixed_default(name)) + else: + kwargs['action'] = b + if c and not b: + kwargs['choices'] = c + kwargs['default'] = argparse.SUPPRESS + kwargs['dest'] = name + + cmdline_name = self.argparse_name_to_arg(name) + parser.add_argument(cmdline_name, help=h + help_suffix, **kwargs) + + +# Update `docs/markdown/Builtin-options.md` after changing the options below +# Also update mesonlib._BUILTIN_NAMES. See the comment there for why this is required. +# Please also update completion scripts in $MESONSRC/data/shell-completions/ +BUILTIN_DIR_OPTIONS: T.Dict['OptionKey', 'BuiltinOption'] = OrderedDict([ + (OptionKey('prefix'), BuiltinOption(UserStringOption, 'Installation prefix', default_prefix())), + (OptionKey('bindir'), BuiltinOption(UserStringOption, 'Executable directory', 'bin')), + (OptionKey('datadir'), BuiltinOption(UserStringOption, 'Data file directory', default_datadir())), + (OptionKey('includedir'), BuiltinOption(UserStringOption, 'Header file directory', default_includedir())), + (OptionKey('infodir'), BuiltinOption(UserStringOption, 'Info page directory', default_infodir())), + (OptionKey('libdir'), BuiltinOption(UserStringOption, 'Library directory', default_libdir())), + (OptionKey('licensedir'), BuiltinOption(UserStringOption, 'Licenses directory', '')), + (OptionKey('libexecdir'), BuiltinOption(UserStringOption, 'Library executable directory', default_libexecdir())), + (OptionKey('localedir'), BuiltinOption(UserStringOption, 'Locale data directory', default_localedir())), + (OptionKey('localstatedir'), BuiltinOption(UserStringOption, 'Localstate data directory', 'var')), + (OptionKey('mandir'), BuiltinOption(UserStringOption, 'Manual page directory', default_mandir())), + (OptionKey('sbindir'), BuiltinOption(UserStringOption, 'System executable directory', default_sbindir())), + (OptionKey('sharedstatedir'), BuiltinOption(UserStringOption, 'Architecture-independent data directory', 'com')), + (OptionKey('sysconfdir'), BuiltinOption(UserStringOption, 'Sysconf data directory', default_sysconfdir())), +]) + +BUILTIN_CORE_OPTIONS: T.Dict['OptionKey', 'BuiltinOption'] = OrderedDict([ + (OptionKey('auto_features'), BuiltinOption(UserFeatureOption, "Override value of all 'auto' features", 'auto')), + (OptionKey('backend'), BuiltinOption(UserComboOption, 'Backend to use', 'ninja', choices=backendlist, + readonly=True)), + (OptionKey('genvslite'), + BuiltinOption( + UserComboOption, + 'Setup multiple buildtype-suffixed ninja-backend build directories, ' + 'and a [builddir]_vs containing a Visual Studio meta-backend with multiple configurations that calls into them', + 'vs2022', + choices=genvslitelist) + ), + (OptionKey('buildtype'), BuiltinOption(UserComboOption, 'Build type to use', 'debug', + choices=buildtypelist)), + (OptionKey('debug'), BuiltinOption(UserBooleanOption, 'Enable debug symbols and other information', True)), + (OptionKey('default_library'), BuiltinOption(UserComboOption, 'Default library type', 'shared', choices=['shared', 'static', 'both'], + yielding=False)), + (OptionKey('errorlogs'), BuiltinOption(UserBooleanOption, "Whether to print the logs from failing tests", True)), + (OptionKey('install_umask'), BuiltinOption(UserUmaskOption, 'Default umask to apply on permissions of installed files', '022')), + (OptionKey('layout'), BuiltinOption(UserComboOption, 'Build directory layout', 'mirror', choices=['mirror', 'flat'])), + (OptionKey('optimization'), BuiltinOption(UserComboOption, 'Optimization level', '0', choices=['plain', '0', 'g', '1', '2', '3', 's'])), + (OptionKey('prefer_static'), BuiltinOption(UserBooleanOption, 'Whether to try static linking before shared linking', False)), + (OptionKey('stdsplit'), BuiltinOption(UserBooleanOption, 'Split stdout and stderr in test logs', True)), + (OptionKey('strip'), BuiltinOption(UserBooleanOption, 'Strip targets on install', False)), + (OptionKey('unity'), BuiltinOption(UserComboOption, 'Unity build', 'off', choices=['on', 'off', 'subprojects'])), + (OptionKey('unity_size'), BuiltinOption(UserIntegerOption, 'Unity block size', (2, None, 4))), + (OptionKey('warning_level'), BuiltinOption(UserComboOption, 'Compiler warning level to use', '1', choices=['0', '1', '2', '3', 'everything'], yielding=False)), + (OptionKey('werror'), BuiltinOption(UserBooleanOption, 'Treat warnings as errors', False, yielding=False)), + (OptionKey('wrap_mode'), BuiltinOption(UserComboOption, 'Wrap mode', 'default', choices=['default', 'nofallback', 'nodownload', 'forcefallback', 'nopromote'])), + (OptionKey('force_fallback_for'), BuiltinOption(UserArrayOption, 'Force fallback for those subprojects', [])), + (OptionKey('vsenv'), BuiltinOption(UserBooleanOption, 'Activate Visual Studio environment', False, readonly=True)), + + # Pkgconfig module + (OptionKey('relocatable', module='pkgconfig'), + BuiltinOption(UserBooleanOption, 'Generate pkgconfig files as relocatable', False)), + + # Python module + (OptionKey('bytecompile', module='python'), + BuiltinOption(UserIntegerOption, 'Whether to compile bytecode', (-1, 2, 0))), + (OptionKey('install_env', module='python'), + BuiltinOption(UserComboOption, 'Which python environment to install to', 'prefix', choices=['auto', 'prefix', 'system', 'venv'])), + (OptionKey('platlibdir', module='python'), + BuiltinOption(UserStringOption, 'Directory for site-specific, platform-specific files.', '')), + (OptionKey('purelibdir', module='python'), + BuiltinOption(UserStringOption, 'Directory for site-specific, non-platform-specific files.', '')), + (OptionKey('allow_limited_api', module='python'), + BuiltinOption(UserBooleanOption, 'Whether to allow use of the Python Limited API', True)), +]) + +BUILTIN_OPTIONS = OrderedDict(chain(BUILTIN_DIR_OPTIONS.items(), BUILTIN_CORE_OPTIONS.items())) + +BUILTIN_OPTIONS_PER_MACHINE: T.Dict['OptionKey', 'BuiltinOption'] = OrderedDict([ + (OptionKey('pkg_config_path'), BuiltinOption(UserArrayOption, 'List of additional paths for pkg-config to search', [])), + (OptionKey('cmake_prefix_path'), BuiltinOption(UserArrayOption, 'List of additional prefixes for cmake to search', [])), +]) + +# Special prefix-dependent defaults for installation directories that reside in +# a path outside of the prefix in FHS and common usage. +BUILTIN_DIR_NOPREFIX_OPTIONS: T.Dict[OptionKey, T.Dict[str, str]] = { + OptionKey('sysconfdir'): {'/usr': '/etc'}, + OptionKey('localstatedir'): {'/usr': '/var', '/usr/local': '/var/local'}, + OptionKey('sharedstatedir'): {'/usr': '/var/lib', '/usr/local': '/var/local/lib'}, + OptionKey('platlibdir', module='python'): {}, + OptionKey('purelibdir', module='python'): {}, +} + + +class OptionStore: + def __init__(self): + # This class will hold all options for a given build directory + self.dummy = None diff --git a/run_project_tests.py b/run_project_tests.py index 23561d97357a..974273fc790e 100755 --- a/run_project_tests.py +++ b/run_project_tests.py @@ -41,7 +41,8 @@ from mesonbuild.build import ConfigurationData from mesonbuild.mesonlib import MachineChoice, Popen_safe, TemporaryDirectoryWinProof, setup_vsenv from mesonbuild.mlog import blue, bold, cyan, green, red, yellow, normal_green -from mesonbuild.coredata import backendlist, version as meson_version +from mesonbuild.coredata import version as meson_version +from mesonbuild.options import backendlist from mesonbuild.modules.python import PythonExternalProgram from run_tests import ( get_fake_options, run_configure, get_meson_script, get_backend_commands, diff --git a/run_tests.py b/run_tests.py index 6d33dd99e145..63eb62c19b60 100755 --- a/run_tests.py +++ b/run_tests.py @@ -33,7 +33,8 @@ from mesonbuild import mtest from mesonbuild import mlog from mesonbuild.environment import Environment, detect_ninja, detect_machine_info -from mesonbuild.coredata import backendlist, version as meson_version +from mesonbuild.coredata import version as meson_version +from mesonbuild.options import backendlist from mesonbuild.mesonlib import OptionKey, setup_vsenv if T.TYPE_CHECKING: diff --git a/test cases/unit/116 empty project/expected_mods.json b/test cases/unit/116 empty project/expected_mods.json index 7463bcb1275a..19f56a5f71b3 100644 --- a/test cases/unit/116 empty project/expected_mods.json +++ b/test cases/unit/116 empty project/expected_mods.json @@ -225,6 +225,7 @@ "mesonbuild.mparser", "mesonbuild.msetup", "mesonbuild.optinterpreter", + "mesonbuild.options", "mesonbuild.programs", "mesonbuild.scripts", "mesonbuild.scripts.meson_exe", @@ -237,6 +238,6 @@ "mesonbuild.wrap", "mesonbuild.wrap.wrap" ], - "count": 68 + "count": 69 } } diff --git a/unittests/datatests.py b/unittests/datatests.py index b14bbac5a29a..19664e378817 100644 --- a/unittests/datatests.py +++ b/unittests/datatests.py @@ -14,6 +14,7 @@ import mesonbuild.envconfig import mesonbuild.environment import mesonbuild.coredata +import mesonbuild.options import mesonbuild.modules.gnome from mesonbuild.interpreter import Interpreter from mesonbuild.ast import AstInterpreter @@ -139,8 +140,8 @@ def test_builtin_options_documented(self): found_entries |= options self.assertEqual(found_entries, { - *(str(k.evolve(module=None)) for k in mesonbuild.coredata.BUILTIN_OPTIONS), - *(str(k.evolve(module=None)) for k in mesonbuild.coredata.BUILTIN_OPTIONS_PER_MACHINE), + *(str(k.evolve(module=None)) for k in mesonbuild.options.BUILTIN_OPTIONS), + *(str(k.evolve(module=None)) for k in mesonbuild.options.BUILTIN_OPTIONS_PER_MACHINE), }) # Check that `buildtype` table inside `Core options` matches how diff --git a/unittests/platformagnostictests.py b/unittests/platformagnostictests.py index ffc4b47ad2d3..33a789b4bf9c 100644 --- a/unittests/platformagnostictests.py +++ b/unittests/platformagnostictests.py @@ -274,7 +274,7 @@ def test_setup_loaded_modules(self): expected = json.load(f)['meson']['modules'] self.assertEqual(data['modules'], expected) - self.assertEqual(data['count'], 68) + self.assertEqual(data['count'], 69) def test_meson_package_cache_dir(self): # Copy testdir into temporary directory to not pollute meson source tree. From a66cb97e8cd29e6d8c89232b8e3763677ad3825f Mon Sep 17 00:00:00 2001 From: Sam James Date: Fri, 24 May 2024 05:27:57 +0100 Subject: [PATCH 13/91] Revert "rust: recursively pull proc-macro dependencies as well" This reverts commit aee941559c4b88a062e88186819a820c69c200ae. The commit being reverted breaks compilation of a major Meson consumer (Mesa). As a result, various distros are either pinning to <1.4.0 (before the commit) or performing this same revert downstream. Fixing a regression takes priority, so let's revert. Fixes: https://github.com/mesonbuild/meson/issues/12973 --- mesonbuild/build.py | 2 ++ test cases/rust/18 proc-macro/lib.rs | 8 -------- test cases/rust/18 proc-macro/meson.build | 11 ----------- test cases/rust/18 proc-macro/subdir/meson.build | 1 - .../rust/18 proc-macro/transitive-proc-macro.rs | 7 ------- 5 files changed, 2 insertions(+), 27 deletions(-) delete mode 100644 test cases/rust/18 proc-macro/lib.rs delete mode 100644 test cases/rust/18 proc-macro/subdir/meson.build delete mode 100644 test cases/rust/18 proc-macro/transitive-proc-macro.rs diff --git a/mesonbuild/build.py b/mesonbuild/build.py index d8afd1bc6157..313f73e00200 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -1292,6 +1292,8 @@ def get_dependencies_recurse(self, result: OrderedSet[BuildTargetTypes], include for t in self.link_targets: if t in result: continue + if t.rust_crate_type == 'proc-macro': + continue if include_internals or not t.is_internal(): result.add(t) if isinstance(t, StaticLibrary): diff --git a/test cases/rust/18 proc-macro/lib.rs b/test cases/rust/18 proc-macro/lib.rs deleted file mode 100644 index 5242886cc5e4..000000000000 --- a/test cases/rust/18 proc-macro/lib.rs +++ /dev/null @@ -1,8 +0,0 @@ -extern crate proc_macro_examples; -use proc_macro_examples::make_answer; - -make_answer!(); - -pub fn func() -> u32 { - answer() -} diff --git a/test cases/rust/18 proc-macro/meson.build b/test cases/rust/18 proc-macro/meson.build index e8b28eda144e..c5f0dfc82aee 100644 --- a/test cases/rust/18 proc-macro/meson.build +++ b/test cases/rust/18 proc-macro/meson.build @@ -31,14 +31,3 @@ main = executable( ) test('main_test2', main) - -subdir('subdir') - -staticlib = static_library('staticlib', 'lib.rs', - link_with: pm_in_subdir, - rust_dependency_map : {'proc_macro_examples3' : 'proc_macro_examples'} -) - -executable('transitive-proc-macro', 'transitive-proc-macro.rs', - link_with: staticlib, -) diff --git a/test cases/rust/18 proc-macro/subdir/meson.build b/test cases/rust/18 proc-macro/subdir/meson.build deleted file mode 100644 index 04842c431e78..000000000000 --- a/test cases/rust/18 proc-macro/subdir/meson.build +++ /dev/null @@ -1 +0,0 @@ -pm_in_subdir = rust.proc_macro('proc_macro_examples3', '../proc.rs') diff --git a/test cases/rust/18 proc-macro/transitive-proc-macro.rs b/test cases/rust/18 proc-macro/transitive-proc-macro.rs deleted file mode 100644 index 4c804b3b6f4d..000000000000 --- a/test cases/rust/18 proc-macro/transitive-proc-macro.rs +++ /dev/null @@ -1,7 +0,0 @@ -extern crate staticlib; -use staticlib::func; - - -fn main() { - assert_eq!(42, func()); -} From d57ca7d2a21b70bc5a9659c51bc345f539ccc6c9 Mon Sep 17 00:00:00 2001 From: Eli Schwartz Date: Tue, 21 May 2024 23:56:02 -0400 Subject: [PATCH 14/91] compilers: improve a comment describing why we add a silly clang workaround Clang is such a great compiler! Not. Compilers have enhanced diagnostics for some kinds of "well known" undeclared identifiers, telling you exactly which header you might have forgotten to include. The reason why clang needs an option GCC doesn't need is because clang's fixit suggestions, unlike GCC's actually *changes the type of the error*, as a result of a fixit of all things. After the fixit suggestion grants this error the right to be ignored, we start having to add clang-specific options. Follow-up to https://github.com/mesonbuild/meson/issues/9140 Upstream clang bug, which appears to be going nowhere: https://github.com/llvm/llvm-project/issues/33905 --- mesonbuild/compilers/mixins/clang.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/mesonbuild/compilers/mixins/clang.py b/mesonbuild/compilers/mixins/clang.py index f982509f436b..a9bf56609d8d 100644 --- a/mesonbuild/compilers/mixins/clang.py +++ b/mesonbuild/compilers/mixins/clang.py @@ -77,9 +77,23 @@ def get_pch_use_args(self, pch_dir: str, header: str) -> T.List[str]: def get_compiler_check_args(self, mode: CompileCheckMode) -> T.List[str]: # Clang is different than GCC, it will return True when a symbol isn't - # defined in a header. Specifically this seems to have something to do - # with functions that may be in a header on some systems, but not all of - # them. `strlcat` specifically with can trigger this. + # defined in a header. Specifically this is caused by a functionality + # both GCC and clang have: for some "well known" functions, arbitrarily + # chosen, they provide fixit suggestions for the header you should try + # including. + # + # - With GCC, this is a note appended to the prexisting diagnostic + # "error: undeclared identifier" + # + # - With clang, the error is converted to a c89'ish implicit function + # declaration instead, which can be disabled with -Wno-error and on + # clang < 16, simply passes compilation by default. + # + # One example of a clang fixit suggestion is for `strlcat`, which + # triggers this. + # + # This was reported in 2017 and promptly fixed. Just kidding! + # https://github.com/llvm/llvm-project/issues/33905 myargs: T.List[str] = ['-Werror=implicit-function-declaration'] if mode is CompileCheckMode.COMPILE: myargs.extend(['-Werror=unknown-warning-option', '-Werror=unused-command-line-argument']) From 0c802d260c98f990a32ad22f55a055ab65779ccb Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 24 May 2024 11:38:23 +1000 Subject: [PATCH 15/91] remove deprecation warning for `configure_file` kwarg 'copy' `configure_file` kwarg `copy` runs at configure time, whereas `fs.copyfile` runs at build time. Both have use cases, so this undeprecates the `configure_file` version. Fixes: #12792 --- mesonbuild/interpreter/interpreter.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index eb6783c644b4..67c000c2c377 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -2585,7 +2585,6 @@ def func_install_subdir(self, node: mparser.BaseNode, args: T.Tuple[str], ), KwargInfo( 'copy', bool, default=False, since='0.47.0', - deprecated='0.64.0', deprecated_message='Use fs.copyfile instead', ), KwargInfo('encoding', str, default='utf-8', since='0.47.0'), KwargInfo('format', str, default='meson', since='0.46.0', From 8d9248716cdc6cfc3ddcda7b825b2e470fdc767f Mon Sep 17 00:00:00 2001 From: Bruno <156703114+brunvonlope@users.noreply.github.com> Date: Sun, 26 May 2024 10:28:24 -0300 Subject: [PATCH 16/91] docs: Update GIMP meson adoption The master branch isn't in experimental state anymore. --- docs/markdown/Users.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/markdown/Users.md b/docs/markdown/Users.md index 90892fac52be..19c4ea6438e4 100644 --- a/docs/markdown/Users.md +++ b/docs/markdown/Users.md @@ -52,7 +52,7 @@ topic](https://github.com/topics/meson). - [fwupd](https://github.com/hughsie/fwupd), a simple daemon to allow session software to update firmware - [GameMode](https://github.com/FeralInteractive/gamemode), a daemon/lib combo for Linux that allows games to request a set of optimisations be temporarily applied to the host OS - [Geary](https://wiki.gnome.org/Apps/Geary), an email application built around conversations, for the GNOME 3 desktop. - - [GIMP](https://gitlab.gnome.org/GNOME/gimp), an image manipulation program (experimental replacing autoconf) + - [GIMP](https://gitlab.gnome.org/GNOME/gimp), an image manipulation program (master branch) - [GLib](https://gitlab.gnome.org/GNOME/glib), cross-platform C library used by GTK+ and GStreamer - [Glorytun](https://github.com/angt/glorytun), a multipath UDP tunnel - [GNOME Boxes](https://gitlab.gnome.org/GNOME/gnome-boxes), a GNOME hypervisor From 7f2c6f644b83f27d1f46aacf8ac59722498529ff Mon Sep 17 00:00:00 2001 From: David Seifert Date: Wed, 29 May 2024 22:19:02 +0200 Subject: [PATCH 17/91] cuda: disable thin archives when cuda is added through `add_languages('cuda')` later --- mesonbuild/backend/ninjabackend.py | 15 +++++++-------- mesonbuild/interpreter/interpreter.py | 4 ++++ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 28f5d532fca8..4d56f7d1bfa5 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -502,13 +502,12 @@ def __init__(self, build: T.Optional[build.Build], interpreter: T.Optional[Inter # hence we disable them if 'cuda' is enabled globally. See also # - https://github.com/mesonbuild/meson/pull/9453 # - https://github.com/mesonbuild/meson/issues/9479#issuecomment-953485040 - self._allow_thin_archives = PerMachine[bool]( - 'cuda' not in self.environment.coredata.compilers.build, - 'cuda' not in self.environment.coredata.compilers.host) if self.environment else PerMachine[bool](True, True) - if not self._allow_thin_archives.build: - mlog.debug('cuda enabled globally, disabling thin archives for build machine, since nvcc/nvlink cannot handle thin archives natively') - if not self._allow_thin_archives.host: - mlog.debug('cuda enabled globally, disabling thin archives for host machine, since nvcc/nvlink cannot handle thin archives natively') + self.allow_thin_archives = PerMachine[bool](True, True) + if self.environment: + for for_machine in MachineChoice: + if 'cuda' in self.environment.coredata.compilers[for_machine]: + mlog.debug('cuda enabled globally, disabling thin archives for {}, since nvcc/nvlink cannot handle thin archives natively'.format(for_machine)) + self.allow_thin_archives[for_machine] = False def create_phony_target(self, dummy_outfile: str, rulename: str, phony_infilename: str) -> NinjaBuildElement: ''' @@ -3268,7 +3267,7 @@ def get_target_type_link_args(self, target, linker): if target.import_filename: commands += linker.gen_import_library_args(self.get_import_filename(target)) elif isinstance(target, build.StaticLibrary): - produce_thin_archive = self._allow_thin_archives[target.for_machine] and not target.should_install() + produce_thin_archive = self.allow_thin_archives[target.for_machine] and not target.should_install() commands += linker.get_std_link_args(self.environment, produce_thin_archive) else: raise RuntimeError('Unknown build target type.') diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index 67c000c2c377..6474e60844e8 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -1531,6 +1531,10 @@ def add_languages_for(self, args: T.List[str], required: bool, for_machine: Mach continue else: raise + if lang == 'cuda' and hasattr(self.backend, 'allow_thin_archives'): + # see NinjaBackend.__init__() why we need to disable thin archives for cuda + mlog.debug('added cuda as language, disabling thin archives for {}, since nvcc/nvlink cannot handle thin archives natively'.format(for_machine)) + self.backend.allow_thin_archives[for_machine] = False else: # update new values from commandline, if it applies self.coredata.process_compiler_options(lang, comp, self.environment, self.subproject) From 6f3841e98636b8460b8dc4e57a14a36fb7ddb815 Mon Sep 17 00:00:00 2001 From: David Seifert Date: Wed, 29 May 2024 22:19:03 +0200 Subject: [PATCH 18/91] cuda: add test for late `add_languages('cuda')` --- test cases/cuda/17 separate compilation linking/meson.build | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test cases/cuda/17 separate compilation linking/meson.build b/test cases/cuda/17 separate compilation linking/meson.build index ee86123eb40d..8e90ddd7f6fd 100644 --- a/test cases/cuda/17 separate compilation linking/meson.build +++ b/test cases/cuda/17 separate compilation linking/meson.build @@ -3,7 +3,10 @@ # code: # https://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/index.html#examples -project('device linking', ['cpp', 'cuda'], version : '1.0.0') +project('device linking', ['cpp'], version : '1.0.0') + +# test that optional initialization of cuda works to disable thin archives +add_languages('cuda') nvcc = meson.get_compiler('cuda') cuda = import('unstable-cuda') From f4577911b4c608f11e01e6fca26c1c78f46e5797 Mon Sep 17 00:00:00 2001 From: David Seifert Date: Fri, 31 May 2024 00:21:43 +0200 Subject: [PATCH 19/91] cuda: avoid test failure without GPU available Fixes #13269 --- .../cuda/17 separate compilation linking/meson.build | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test cases/cuda/17 separate compilation linking/meson.build b/test cases/cuda/17 separate compilation linking/meson.build index 8e90ddd7f6fd..7ba7e220870d 100644 --- a/test cases/cuda/17 separate compilation linking/meson.build +++ b/test cases/cuda/17 separate compilation linking/meson.build @@ -11,7 +11,7 @@ add_languages('cuda') nvcc = meson.get_compiler('cuda') cuda = import('unstable-cuda') -arch_flags = cuda.nvcc_arch_flags(nvcc.version(), 'Auto', detected : ['8.0']) +arch_flags = cuda.nvcc_arch_flags(nvcc.version(), 'Common') message('NVCC version: ' + nvcc.version()) message('NVCC flags: ' + ' '.join(arch_flags)) @@ -19,4 +19,8 @@ message('NVCC flags: ' + ' '.join(arch_flags)) # test device linking with -dc (which is equivalent to `--relocatable-device-code true`) lib = static_library('devicefuncs', ['b.cu'], cuda_args : ['-dc'] + arch_flags) exe = executable('app', 'main.cu', cuda_args : ['-dc'] + arch_flags, link_with : lib, link_args : arch_flags) -test('cudatest', exe) + +# if we don't have a CUDA-capable GPU available, avoid creating the test +if run_command('__nvcc_device_query', check : false).returncode() == 0 + test('cudatest', exe) +endif From d9e3a3f09ff82fafab85f1d8631c9e8689850510 Mon Sep 17 00:00:00 2001 From: Eli Schwartz Date: Thu, 30 May 2024 17:00:30 -0400 Subject: [PATCH 20/91] CI: make cuda tests even run to begin with Arch profile.d scripts were converted to use an appending function that disappears when /etc/profile exits, and overall are simply not suitable -- any more -- for sourcing individually. (I will freely admit I'm not really sure what the overall goal of refraining from sourcing /etc/profile itself is. Arguably it's kind of misuse of the profile...) This silently broke the cuda tests, which never ran because the cuda compiler was not detected as available. While we are at it, I guess we can convert gentoo to use the same trick of appending it in install.sh --- ci/ciimage/build.py | 5 ----- ci/ciimage/cuda/install.sh | 2 +- ci/ciimage/gentoo/install.sh | 2 ++ 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/ci/ciimage/build.py b/ci/ciimage/build.py index b355c47a78a9..b9d318158411 100755 --- a/ci/ciimage/build.py +++ b/ci/ciimage/build.py @@ -80,11 +80,6 @@ def gen_bashrc(self) -> None: fi ''' - if self.data_dir.name == 'gentoo': - out_data += ''' - source /etc/profile - ''' - out_file.write_text(out_data, encoding='utf-8') # make it executable diff --git a/ci/ciimage/cuda/install.sh b/ci/ciimage/cuda/install.sh index 7c79d28ec807..6c4fd3b096a6 100755 --- a/ci/ciimage/cuda/install.sh +++ b/ci/ciimage/cuda/install.sh @@ -18,4 +18,4 @@ install_minimal_python_packages # Manually remove cache to avoid GitHub space restrictions rm -rf /var/cache/pacman -echo "source /etc/profile.d/cuda.sh" >> /ci/env_vars.sh +echo "source /etc/profile" >> /ci/env_vars.sh diff --git a/ci/ciimage/gentoo/install.sh b/ci/ciimage/gentoo/install.sh index b2a697fcbc1e..8f7aa33f5d17 100755 --- a/ci/ciimage/gentoo/install.sh +++ b/ci/ciimage/gentoo/install.sh @@ -156,3 +156,5 @@ rm /usr/lib/python/EXTERNALLY-MANAGED python3 -m ensurepip install_python_packages python3 -m pip install "${base_python_pkgs[@]}" + +echo "source /etc/profile" >> /ci/env_vars.sh From 8603bb491925d9cb74f63725e02fab2a0c6fafe1 Mon Sep 17 00:00:00 2001 From: Emil Melnikov Date: Wed, 29 May 2024 16:04:44 +0200 Subject: [PATCH 21/91] Explain how to add preprocessor defines in FAQ.md See https://github.com/mesonbuild/meson/issues/6269 for the discussion. Co-authored-by: Eli Schwartz --- docs/markdown/FAQ.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/markdown/FAQ.md b/docs/markdown/FAQ.md index ffc9e1718eb6..810e9da23e95 100644 --- a/docs/markdown/FAQ.md +++ b/docs/markdown/FAQ.md @@ -695,3 +695,16 @@ directory. It glob ignores ```"*"```, since all generated files should not be checked into git. Users of older versions of Meson may need to set up ignore files themselves. + +## How to add preprocessor defines to a target? + +Just add `-DFOO` to `c_args` or `cpp_args`. This works for all known compilers. + +```meson +mylib = library('mylib', 'mysource.c', c_args: ['-DFOO']) +``` + +Even though [MSVC documentation](https://learn.microsoft.com/en-us/cpp/build/reference/d-preprocessor-definitions) +uses `/D` for preprocessor defines, its [command-line syntax](https://learn.microsoft.com/en-us/cpp/build/reference/compiler-command-line-syntax) +accepts `-` instead of `/`. +It's not necessary to treat preprocessor defines specially in Meson ([GH-6269](https://github.com/mesonbuild/meson/issues/6269#issuecomment-560003922)). From e5aed6ac8f4758f636d81e0374c0658238b90eb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20La=C3=ADns?= Date: Wed, 29 May 2024 14:17:55 +0100 Subject: [PATCH 22/91] mintro: write humman-readable JSON MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Filipe Laíns --- mesonbuild/mintro.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mesonbuild/mintro.py b/mesonbuild/mintro.py index a5ce72a1f396..c678cf750ee5 100644 --- a/mesonbuild/mintro.py +++ b/mesonbuild/mintro.py @@ -592,7 +592,7 @@ def write_intro_info(intro_info: T.Sequence[T.Tuple[str, T.Union[dict, T.List[T. out_file = os.path.join(info_dir, f'intro-{kind}.json') tmp_file = os.path.join(info_dir, 'tmp_dump.json') with open(tmp_file, 'w', encoding='utf-8') as fp: - json.dump(data, fp) + json.dump(data, fp, indent=2) fp.flush() # Not sure if this is needed os.replace(tmp_file, out_file) updated_introspection_files.append(kind) From daa058e90793e6ff8c8e1a3fa14a88c0167d8561 Mon Sep 17 00:00:00 2001 From: Jussi Pakkanen Date: Sun, 2 Jun 2024 16:29:59 +0300 Subject: [PATCH 23/91] Start moving machine files to their own store. --- mesonbuild/machinefile.py | 15 +++++++++++++++ unittests/machinefiletests.py | 13 ++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 mesonbuild/machinefile.py diff --git a/mesonbuild/machinefile.py b/mesonbuild/machinefile.py new file mode 100644 index 000000000000..18162eff52b6 --- /dev/null +++ b/mesonbuild/machinefile.py @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2013-2024 Contributors to the The Meson project + +from . import mlog + +class MachineFile: + def __init__(self, fname): + with open(fname, encoding='utf-8') as f: + pass + self.stuff = None + +class MachineFileStore: + def __init__(self, native_files, cross_files): + self.native = [MachineFile(x) for x in native_files] + self.cross = [MachineFile(x) for x in cross_files] diff --git a/unittests/machinefiletests.py b/unittests/machinefiletests.py index 22341cb9a000..3899ea942faa 100644 --- a/unittests/machinefiletests.py +++ b/unittests/machinefiletests.py @@ -12,7 +12,7 @@ import threading import sys from itertools import chain -from unittest import mock, skipIf, SkipTest +from unittest import mock, skipIf, SkipTest, TestCase from pathlib import Path import typing as T @@ -23,6 +23,9 @@ import mesonbuild.environment import mesonbuild.coredata import mesonbuild.modules.gnome + +from mesonbuild import machinefile + from mesonbuild.mesonlib import ( MachineChoice, is_windows, is_osx, is_cygwin, is_haiku, is_sunos ) @@ -50,6 +53,14 @@ def is_real_gnu_compiler(path): out = subprocess.check_output([path, '--version'], universal_newlines=True, stderr=subprocess.STDOUT) return 'Free Software Foundation' in out +cross_dir = Path(__file__).parent.parent / 'cross' + +class MachineFileStoreTests(TestCase): + + def test_loading(self): + store = machinefile.MachineFileStore([cross_dir / 'ubuntu-armhf.txt'], [], str(cross_dir)) + self.assertTrue(True) + class NativeFileTests(BasePlatformTests): def setUp(self): From 4cc2e2171a7a6452da6ee0ec336ecb0e77f19791 Mon Sep 17 00:00:00 2001 From: Jussi Pakkanen Date: Sun, 2 Jun 2024 16:45:42 +0300 Subject: [PATCH 24/91] Create a directory for machine files used in unit tests. --- unittests/allplatformstests.py | 50 +++++++++------------------- unittests/machinefiles/constant1.txt | 2 ++ unittests/machinefiles/constant2.txt | 13 ++++++++ 3 files changed, 31 insertions(+), 34 deletions(-) create mode 100644 unittests/machinefiles/constant1.txt create mode 100644 unittests/machinefiles/constant2.txt diff --git a/unittests/allplatformstests.py b/unittests/allplatformstests.py index 9c9f61678609..63445ec2b25c 100644 --- a/unittests/allplatformstests.py +++ b/unittests/allplatformstests.py @@ -25,6 +25,7 @@ import mesonbuild.envconfig import mesonbuild.environment import mesonbuild.coredata +import mesonbuild.machinefile import mesonbuild.modules.gnome from mesonbuild.mesonlib import ( BuildDirLock, MachineChoice, is_windows, is_osx, is_cygwin, is_dragonflybsd, @@ -61,6 +62,8 @@ from .baseplatformtests import BasePlatformTests from .helpers import * +UNIT_MACHINEFILE_DIR = Path(__file__).parent / 'machinefiles' + @contextmanager def temp_filename(): '''A context manager which provides a filename to an empty temporary file. @@ -4111,40 +4114,19 @@ def test_coverage_escaping(self): self._check_coverage_files() def test_cross_file_constants(self): - with temp_filename() as crossfile1, temp_filename() as crossfile2: - with open(crossfile1, 'w', encoding='utf-8') as f: - f.write(textwrap.dedent( - ''' - [constants] - compiler = 'gcc' - ''')) - with open(crossfile2, 'w', encoding='utf-8') as f: - f.write(textwrap.dedent( - ''' - [constants] - toolchain = '/toolchain/' - common_flags = ['--sysroot=' + toolchain / 'sysroot'] - - [properties] - c_args = common_flags + ['-DSOMETHING'] - cpp_args = c_args + ['-DSOMETHING_ELSE'] - rel_to_src = '@GLOBAL_SOURCE_ROOT@' / 'tool' - rel_to_file = '@DIRNAME@' / 'tool' - no_escaping = '@@DIRNAME@@' / 'tool' - - [binaries] - c = toolchain / compiler - ''')) - - values = mesonbuild.coredata.parse_machine_files([crossfile1, crossfile2], self.builddir) - self.assertEqual(values['binaries']['c'], '/toolchain/gcc') - self.assertEqual(values['properties']['c_args'], - ['--sysroot=/toolchain/sysroot', '-DSOMETHING']) - self.assertEqual(values['properties']['cpp_args'], - ['--sysroot=/toolchain/sysroot', '-DSOMETHING', '-DSOMETHING_ELSE']) - self.assertEqual(values['properties']['rel_to_src'], os.path.join(self.builddir, 'tool')) - self.assertEqual(values['properties']['rel_to_file'], os.path.join(os.path.dirname(crossfile2), 'tool')) - self.assertEqual(values['properties']['no_escaping'], os.path.join(f'@{os.path.dirname(crossfile2)}@', 'tool')) + crossfile1 = UNIT_MACHINEFILE_DIR / 'constant1.txt' + crossfile2 = UNIT_MACHINEFILE_DIR / 'constant2.txt' + values = mesonbuild.machinefile.parse_machine_files([crossfile1, + crossfile2], + self.builddir) + self.assertEqual(values['binaries']['c'], '/toolchain/gcc') + self.assertEqual(values['properties']['c_args'], + ['--sysroot=/toolchain/sysroot', '-DSOMETHING']) + self.assertEqual(values['properties']['cpp_args'], + ['--sysroot=/toolchain/sysroot', '-DSOMETHING', '-DSOMETHING_ELSE']) + self.assertEqual(values['properties']['rel_to_src'], os.path.join(self.builddir, 'tool')) + self.assertEqual(values['properties']['rel_to_file'], os.path.join(os.path.dirname(crossfile2), 'tool')) + self.assertEqual(values['properties']['no_escaping'], os.path.join(f'@{os.path.dirname(crossfile2)}@', 'tool')) @skipIf(is_windows(), 'Directory cleanup fails for some reason') def test_wrap_git(self): diff --git a/unittests/machinefiles/constant1.txt b/unittests/machinefiles/constant1.txt new file mode 100644 index 000000000000..eeba7cb10cda --- /dev/null +++ b/unittests/machinefiles/constant1.txt @@ -0,0 +1,2 @@ +[constants] +compiler = 'gcc' diff --git a/unittests/machinefiles/constant2.txt b/unittests/machinefiles/constant2.txt new file mode 100644 index 000000000000..226dcc8529c5 --- /dev/null +++ b/unittests/machinefiles/constant2.txt @@ -0,0 +1,13 @@ +[constants] +toolchain = '/toolchain/' +common_flags = ['--sysroot=' + toolchain / 'sysroot'] + +[properties] +c_args = common_flags + ['-DSOMETHING'] +cpp_args = c_args + ['-DSOMETHING_ELSE'] +rel_to_src = '@GLOBAL_SOURCE_ROOT@' / 'tool' +rel_to_file = '@DIRNAME@' / 'tool' +no_escaping = '@@DIRNAME@@' / 'tool' + +[binaries] +c = toolchain / compiler From 8499e4535b196c0497fbae92b59350dd8824d347 Mon Sep 17 00:00:00 2001 From: Albert Tang Date: Fri, 8 Sep 2023 10:04:03 -0500 Subject: [PATCH 25/91] xcode: Detect installed version of Xcode This will allow for generating project files more specific to certain versions of Xcode without breaking compatibility. --- mesonbuild/backend/xcodebackend.py | 32 +++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/mesonbuild/backend/xcodebackend.py b/mesonbuild/backend/xcodebackend.py index c801b5aae260..629a222572b7 100644 --- a/mesonbuild/backend/xcodebackend.py +++ b/mesonbuild/backend/xcodebackend.py @@ -3,7 +3,7 @@ from __future__ import annotations -import functools, uuid, os, operator +import functools, uuid, os, operator, re import typing as T from . import backends @@ -58,6 +58,35 @@ } BOOL2XCODEBOOL = {True: 'YES', False: 'NO'} LINKABLE_EXTENSIONS = {'.o', '.a', '.obj', '.so', '.dylib'} +XCODEVERSIONS = {'1500': ('Xcode 15.0', 60), + '1400': ('Xcode 14.0', 56), + '1300': ('Xcode 13.0', 55), + '1200': ('Xcode 12.0', 54), + '1140': ('Xcode 11.4', 53), + '1100': ('Xcode 11.0', 52), + '1000': ('Xcode 10.0', 51), + '930': ('Xcode 9.3', 50), + '800': ('Xcode 8.0', 48), + '630': ('Xcode 6.3', 47), + '320': ('Xcode 3.2', 46), + '310': ('Xcode 3.1', 45) + } + +def autodetect_xcode_version() -> T.Tuple[str, int]: + try: + pc, stdout, stderr = mesonlib.Popen_safe(['xcodebuild', '-version']) + except FileNotFoundError: + raise MesonException('Could not detect Xcode. Please install it if you wish to use the Xcode backend.') + if pc.returncode != 0: + raise MesonException(f'An error occurred while detecting Xcode: {stderr}') + version = int(''.join(re.search(r'\d*\.\d*\.*\d*', stdout).group(0).split('.'))) + # If the version number does not have two decimal points, pretend it does. + if stdout.count('.') < 2: + version *= 10 + for v, r in XCODEVERSIONS.items(): + if int(v) <= version: + return r + raise MesonException('Your Xcode installation is too old and is not supported.') class FileTreeEntry: @@ -203,6 +232,7 @@ def __init__(self, build: T.Optional[build.Build], interpreter: T.Optional[Inter self.arch = self.build.environment.machines.host.cpu if self.arch == 'aarch64': self.arch = 'arm64' + self.xcodeversion, self.objversion = autodetect_xcode_version() # In Xcode files are not accessed via their file names, but rather every one of them # gets an unique id. More precisely they get one unique id per target they are used # in. If you generate only one id per file and use them, compilation will work but the From da20ea01ca257987216b0798966070fecc40bfc9 Mon Sep 17 00:00:00 2001 From: Albert Tang Date: Fri, 22 Sep 2023 09:53:18 -0500 Subject: [PATCH 26/91] xcode: Generate files for highest detected version Some settings require "objectVersion" to be set to a certain value or Xcode will not use it. To fix this, we set it to the highest possible value, determined by the detected version of Xcode. We also set "compatibilityVersion", but mainly so it lines up with "objectVersion". At the same time, we should not be generating Xcode 3.2-compatible projects by default anyway. --- mesonbuild/backend/xcodebackend.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mesonbuild/backend/xcodebackend.py b/mesonbuild/backend/xcodebackend.py index 629a222572b7..f1b5c5172ef7 100644 --- a/mesonbuild/backend/xcodebackend.py +++ b/mesonbuild/backend/xcodebackend.py @@ -1271,7 +1271,7 @@ def generate_pbx_project(self, objects_dict: PbxDict) -> None: project_dict.add_item('buildStyles', style_arr) for name, idval in self.buildstylemap.items(): style_arr.add_item(idval, name) - project_dict.add_item('compatibilityVersion', '"Xcode 3.2"') + project_dict.add_item('compatibilityVersion', f'"{self.xcodeversion}"') project_dict.add_item('hasScannedForEncodings', 0) project_dict.add_item('mainGroup', self.maingroup_id) project_dict.add_item('projectDirPath', '"' + self.environment.get_source_dir() + '"') @@ -1870,7 +1870,7 @@ def generate_xc_configurationList(self, objects_dict: PbxDict) -> None: def generate_prefix(self, pbxdict: PbxDict) -> PbxDict: pbxdict.add_item('archiveVersion', '1') pbxdict.add_item('classes', PbxDict()) - pbxdict.add_item('objectVersion', '46') + pbxdict.add_item('objectVersion', self.objversion) objects_dict = PbxDict() pbxdict.add_item('objects', objects_dict) From f70de5885cc7ff331f0bd4aa04e5f2d964f2a030 Mon Sep 17 00:00:00 2001 From: Albert Tang Date: Mon, 18 Sep 2023 14:40:16 -0500 Subject: [PATCH 27/91] xcode: Skip generating PBXBuildStyle on Xcode 9 and above This was removed on Xcode 9, so only generate it for Xcode 8.3.3 and lower. --- mesonbuild/backend/xcodebackend.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/mesonbuild/backend/xcodebackend.py b/mesonbuild/backend/xcodebackend.py index f1b5c5172ef7..69a544b240a0 100644 --- a/mesonbuild/backend/xcodebackend.py +++ b/mesonbuild/backend/xcodebackend.py @@ -296,7 +296,8 @@ def generate(self, capture: bool = False, vslite_ctx: T.Optional[T.Dict] = None) self.build_targets = self.build.get_build_targets() self.custom_targets = self.build.get_custom_targets() self.generate_filemap() - self.generate_buildstylemap() + if self.objversion < 50: + self.generate_buildstylemap() self.generate_build_phase_map() self.generate_build_configuration_map() self.generate_build_configurationlist_map() @@ -328,9 +329,10 @@ def generate(self, capture: bool = False, vslite_ctx: T.Optional[T.Dict] = None) self.generate_pbx_build_rule(objects_dict) objects_dict.add_comment(PbxComment('End PBXBuildRule section')) objects_dict.add_comment(PbxComment('Begin PBXBuildStyle section')) - self.generate_pbx_build_style(objects_dict) - objects_dict.add_comment(PbxComment('End PBXBuildStyle section')) - objects_dict.add_comment(PbxComment('Begin PBXContainerItemProxy section')) + if self.objversion < 50: + self.generate_pbx_build_style(objects_dict) + objects_dict.add_comment(PbxComment('End PBXBuildStyle section')) + objects_dict.add_comment(PbxComment('Begin PBXContainerItemProxy section')) self.generate_pbx_container_item_proxy(objects_dict) objects_dict.add_comment(PbxComment('End PBXContainerItemProxy section')) objects_dict.add_comment(PbxComment('Begin PBXFileReference section')) @@ -758,8 +760,8 @@ def create_generator_shellphase(self, objects_dict, tname, generator_id) -> None odict.add_item('isa', 'PBXBuildFile') odict.add_item('fileRef', ref_id) + # This is skipped if Xcode 9 or above is installed, as PBXBuildStyle was removed on that version. def generate_pbx_build_style(self, objects_dict: PbxDict) -> None: - # FIXME: Xcode 9 and later does not uses PBXBuildStyle and it gets removed. Maybe we can remove this part. for name, idval in self.buildstylemap.items(): styledict = PbxDict() objects_dict.add_item(idval, styledict, name) @@ -1267,10 +1269,11 @@ def generate_pbx_project(self, objects_dict: PbxDict) -> None: attr_dict.add_item('BuildIndependentTargetsInParallel', 'YES') project_dict.add_item('buildConfigurationList', self.project_conflist, f'Build configuration list for PBXProject "{self.build.project_name}"') project_dict.add_item('buildSettings', PbxDict()) - style_arr = PbxArray() - project_dict.add_item('buildStyles', style_arr) - for name, idval in self.buildstylemap.items(): - style_arr.add_item(idval, name) + if self.objversion < 50: + style_arr = PbxArray() + project_dict.add_item('buildStyles', style_arr) + for name, idval in self.buildstylemap.items(): + style_arr.add_item(idval, name) project_dict.add_item('compatibilityVersion', f'"{self.xcodeversion}"') project_dict.add_item('hasScannedForEncodings', 0) project_dict.add_item('mainGroup', self.maingroup_id) From 1da230c9e0f66cf7ecf5a9f5fed70e74e15418ac Mon Sep 17 00:00:00 2001 From: Eli Schwartz Date: Wed, 29 May 2024 17:43:44 -0400 Subject: [PATCH 28/91] CI: Windows: downgrade rust to 1.77 It started failing CI as soon as the default shifted to 1.78. Something is broken and it prevents running stable CI. Tracking issue opened. We pin the version because that is the same way we handle CI for linux -- with the exception that Linux CI can upgrade itself as soon as we fix issues causing the CI Image Builder to jam itself, whereas unfortunately Windows will need to be manually unpinned, but such is life as a Windows supporter. Bug: #13236 --- ci/run.ps1 | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/ci/run.ps1 b/ci/run.ps1 index 05bb6b69e216..596253fe2d62 100644 --- a/ci/run.ps1 +++ b/ci/run.ps1 @@ -8,21 +8,22 @@ if ($LastExitCode -ne 0) { $env:Path = ($env:Path.Split(';') | Where-Object { $_ -notmatch 'mingw|Strawberry|Chocolatey|PostgreSQL' }) -join ';' if ($env:arch -eq 'x64') { + rustup default 1.77 # Rust puts its shared stdlib in a secret place, but it is needed to run tests. - $env:Path += ";$HOME/.rustup/toolchains/stable-x86_64-pc-windows-msvc/bin" + $env:Path += ";$HOME/.rustup/toolchains/1.77-x86_64-pc-windows-msvc/bin" } elseif ($env:arch -eq 'x86') { # Switch to the x86 Rust toolchain - rustup default stable-i686-pc-windows-msvc - - # Also install clippy - rustup component add clippy + rustup default 1.77-i686-pc-windows-msvc # Rust puts its shared stdlib in a secret place, but it is needed to run tests. - $env:Path += ";$HOME/.rustup/toolchains/stable-i686-pc-windows-msvc/bin" + $env:Path += ";$HOME/.rustup/toolchains/1.77-i686-pc-windows-msvc/bin" # Need 32-bit Python for tests that need the Python dependency $env:Path = "C:\hostedtoolcache\windows\Python\3.7.9\x86;C:\hostedtoolcache\windows\Python\3.7.9\x86\Scripts;$env:Path" } +# Also install clippy +rustup component add clippy + # Set the CI env var for the meson test framework $env:CI = '1' From 3efec185acd5d536d8fd71d8dffb128bb631ef70 Mon Sep 17 00:00:00 2001 From: Eli Schwartz Date: Wed, 29 May 2024 17:45:35 -0400 Subject: [PATCH 29/91] CI: clean up azure triggers --- azure-pipelines.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 76440de7a8a7..ea511f33f94e 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -26,7 +26,7 @@ trigger: - 'test cases' - 'unittests' - 'azure-pipelines.yml' - - 'ci/azure-steps.yml' + - 'ci/run.ps1' - 'run_project_tests.py' - 'run_tests.py' - 'run_unittests.py' @@ -41,7 +41,7 @@ pr: - 'test cases' - 'unittests' - 'azure-pipelines.yml' - - 'ci/azure-steps.yml' + - 'ci/run.ps1' - 'run_project_tests.py' - 'run_tests.py' - 'run_unittests.py' From 41a445c2284dcdd1c209fe618ea6b7ac1f117378 Mon Sep 17 00:00:00 2001 From: Jussi Pakkanen Date: Sun, 2 Jun 2024 17:19:34 +0300 Subject: [PATCH 30/91] Extract native file parser to machinefile source file. --- mesonbuild/coredata.py | 101 +-------------- mesonbuild/environment.py | 8 +- mesonbuild/machinefile.py | 120 ++++++++++++++++-- mesonbuild/scripts/scanbuild.py | 3 +- .../unit/116 empty project/expected_mods.json | 1 + unittests/machinefiletests.py | 2 +- unittests/platformagnostictests.py | 2 +- 7 files changed, 127 insertions(+), 110 deletions(-) diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index 76da0b67fcac..782e77081f98 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -6,7 +6,7 @@ import copy -from . import mlog, mparser, options +from . import mlog, options import pickle, os, uuid import sys from itertools import chain @@ -16,14 +16,16 @@ from .mesonlib import ( MesonBugException, - MesonException, EnvironmentException, MachineChoice, PerMachine, + MesonException, MachineChoice, PerMachine, PerMachineDefaultable, OptionKey, OptionType, stringlistify, pickle_load ) + +from .machinefile import CmdLineFileParser + import ast import argparse -import configparser import enum import shlex import typing as T @@ -760,99 +762,6 @@ def emit_base_options_warnings(self, enabled_opts: T.List[OptionKey]) -> None: mlog.warning('Base option \'b_bitcode\' is enabled, which is incompatible with many linker options. Incompatible options such as \'b_asneeded\' have been disabled.', fatal=False) mlog.warning('Please see https://mesonbuild.com/Builtin-options.html#Notes_about_Apple_Bitcode_support for more details.', fatal=False) -class CmdLineFileParser(configparser.ConfigParser): - def __init__(self) -> None: - # We don't want ':' as key delimiter, otherwise it would break when - # storing subproject options like "subproject:option=value" - super().__init__(delimiters=['='], interpolation=None) - - def read(self, filenames: T.Union['StrOrBytesPath', T.Iterable['StrOrBytesPath']], encoding: T.Optional[str] = 'utf-8') -> T.List[str]: - return super().read(filenames, encoding) - - def optionxform(self, optionstr: str) -> str: - # Don't call str.lower() on keys - return optionstr - -class MachineFileParser(): - def __init__(self, filenames: T.List[str], sourcedir: str) -> None: - self.parser = CmdLineFileParser() - self.constants: T.Dict[str, T.Union[str, bool, int, T.List[str]]] = {'True': True, 'False': False} - self.sections: T.Dict[str, T.Dict[str, T.Union[str, bool, int, T.List[str]]]] = {} - - for fname in filenames: - try: - with open(fname, encoding='utf-8') as f: - content = f.read() - except UnicodeDecodeError as e: - raise EnvironmentException(f'Malformed machine file {fname!r} failed to parse as unicode: {e}') - - content = content.replace('@GLOBAL_SOURCE_ROOT@', sourcedir) - content = content.replace('@DIRNAME@', os.path.dirname(fname)) - try: - self.parser.read_string(content, fname) - except configparser.Error as e: - raise EnvironmentException(f'Malformed machine file: {e}') - - # Parse [constants] first so they can be used in other sections - if self.parser.has_section('constants'): - self.constants.update(self._parse_section('constants')) - - for s in self.parser.sections(): - if s == 'constants': - continue - self.sections[s] = self._parse_section(s) - - def _parse_section(self, s: str) -> T.Dict[str, T.Union[str, bool, int, T.List[str]]]: - self.scope = self.constants.copy() - section: T.Dict[str, T.Union[str, bool, int, T.List[str]]] = {} - for entry, value in self.parser.items(s): - if ' ' in entry or '\t' in entry or "'" in entry or '"' in entry: - raise EnvironmentException(f'Malformed variable name {entry!r} in machine file.') - # Windows paths... - value = value.replace('\\', '\\\\') - try: - ast = mparser.Parser(value, 'machinefile').parse() - if not ast.lines: - raise EnvironmentException('value cannot be empty') - res = self._evaluate_statement(ast.lines[0]) - except MesonException as e: - raise EnvironmentException(f'Malformed value in machine file variable {entry!r}: {str(e)}.') - except KeyError as e: - raise EnvironmentException(f'Undefined constant {e.args[0]!r} in machine file variable {entry!r}.') - section[entry] = res - self.scope[entry] = res - return section - - def _evaluate_statement(self, node: mparser.BaseNode) -> T.Union[str, bool, int, T.List[str]]: - if isinstance(node, (mparser.StringNode)): - return node.value - elif isinstance(node, mparser.BooleanNode): - return node.value - elif isinstance(node, mparser.NumberNode): - return node.value - elif isinstance(node, mparser.ParenthesizedNode): - return self._evaluate_statement(node.inner) - elif isinstance(node, mparser.ArrayNode): - # TODO: This is where recursive types would come in handy - return [self._evaluate_statement(arg) for arg in node.args.arguments] - elif isinstance(node, mparser.IdNode): - return self.scope[node.value] - elif isinstance(node, mparser.ArithmeticNode): - l = self._evaluate_statement(node.left) - r = self._evaluate_statement(node.right) - if node.operation == 'add': - if (isinstance(l, str) and isinstance(r, str)) or \ - (isinstance(l, list) and isinstance(r, list)): - return l + r - elif node.operation == 'div': - if isinstance(l, str) and isinstance(r, str): - return os.path.join(l, r) - raise EnvironmentException('Unsupported node type') - -def parse_machine_files(filenames: T.List[str], sourcedir: str): - parser = MachineFileParser(filenames, sourcedir) - return parser.sections - def get_cmd_line_file(build_dir: str) -> str: return os.path.join(build_dir, 'meson-private', 'cmd_line.txt') diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index 19b9e81b53b5..1607c32edd90 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -11,6 +11,10 @@ from . import coredata from . import mesonlib +from . import machinefile + +CmdLineFileParser = machinefile.CmdLineFileParser + from .mesonlib import ( MesonException, MachineChoice, Popen_safe, PerMachine, PerMachineDefaultable, PerThreeMachineDefaultable, split_args, quote_arg, OptionKey, @@ -589,7 +593,7 @@ def __init__(self, source_dir: str, build_dir: str, options: coredata.SharedCMDO ## Read in native file(s) to override build machine configuration if self.coredata.config_files is not None: - config = coredata.parse_machine_files(self.coredata.config_files, self.source_dir) + config = machinefile.parse_machine_files(self.coredata.config_files, self.source_dir) binaries.build = BinaryTable(config.get('binaries', {})) properties.build = Properties(config.get('properties', {})) cmakevars.build = CMakeVariables(config.get('cmake', {})) @@ -600,7 +604,7 @@ def __init__(self, source_dir: str, build_dir: str, options: coredata.SharedCMDO ## Read in cross file(s) to override host machine configuration if self.coredata.cross_files: - config = coredata.parse_machine_files(self.coredata.cross_files, self.source_dir) + config = machinefile.parse_machine_files(self.coredata.cross_files, self.source_dir) properties.host = Properties(config.get('properties', {})) binaries.host = BinaryTable(config.get('binaries', {})) cmakevars.host = CMakeVariables(config.get('cmake', {})) diff --git a/mesonbuild/machinefile.py b/mesonbuild/machinefile.py index 18162eff52b6..afeb4d05637c 100644 --- a/mesonbuild/machinefile.py +++ b/mesonbuild/machinefile.py @@ -1,15 +1,117 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright 2013-2024 Contributors to the The Meson project -from . import mlog +import typing as T +import configparser +import os + +from . import mparser + +from .mesonlib import MesonException + +if T.TYPE_CHECKING: + from .compilers import Compiler + from .coredata import StrOrBytesPath + + CompilersDict = T.Dict[str, Compiler] + + +class CmdLineFileParser(configparser.ConfigParser): + def __init__(self) -> None: + # We don't want ':' as key delimiter, otherwise it would break when + # storing subproject options like "subproject:option=value" + super().__init__(delimiters=['='], interpolation=None) + + def read(self, filenames: T.Union['StrOrBytesPath', T.Iterable['StrOrBytesPath']], encoding: T.Optional[str] = 'utf-8') -> T.List[str]: + return super().read(filenames, encoding) + + def optionxform(self, optionstr: str) -> str: + # Don't call str.lower() on keys + return optionstr + + +class MachineFileParser(): + def __init__(self, filenames: T.List[str], sourcedir: str) -> None: + self.parser = CmdLineFileParser() + self.constants: T.Dict[str, T.Union[str, bool, int, T.List[str]]] = {'True': True, 'False': False} + self.sections: T.Dict[str, T.Dict[str, T.Union[str, bool, int, T.List[str]]]] = {} + + for fname in filenames: + try: + with open(fname, encoding='utf-8') as f: + content = f.read() + except UnicodeDecodeError as e: + raise MesonException(f'Malformed machine file {fname!r} failed to parse as unicode: {e}') + + content = content.replace('@GLOBAL_SOURCE_ROOT@', sourcedir) + content = content.replace('@DIRNAME@', os.path.dirname(fname)) + try: + self.parser.read_string(content, fname) + except configparser.Error as e: + raise MesonException(f'Malformed machine file: {e}') + + # Parse [constants] first so they can be used in other sections + if self.parser.has_section('constants'): + self.constants.update(self._parse_section('constants')) + + for s in self.parser.sections(): + if s == 'constants': + continue + self.sections[s] = self._parse_section(s) + + def _parse_section(self, s: str) -> T.Dict[str, T.Union[str, bool, int, T.List[str]]]: + self.scope = self.constants.copy() + section: T.Dict[str, T.Union[str, bool, int, T.List[str]]] = {} + for entry, value in self.parser.items(s): + if ' ' in entry or '\t' in entry or "'" in entry or '"' in entry: + raise MesonException(f'Malformed variable name {entry!r} in machine file.') + # Windows paths... + value = value.replace('\\', '\\\\') + try: + ast = mparser.Parser(value, 'machinefile').parse() + if not ast.lines: + raise MesonException('value cannot be empty') + res = self._evaluate_statement(ast.lines[0]) + except MesonException as e: + raise MesonException(f'Malformed value in machine file variable {entry!r}: {str(e)}.') + except KeyError as e: + raise MesonException(f'Undefined constant {e.args[0]!r} in machine file variable {entry!r}.') + section[entry] = res + self.scope[entry] = res + return section + + def _evaluate_statement(self, node: mparser.BaseNode) -> T.Union[str, bool, int, T.List[str]]: + if isinstance(node, (mparser.StringNode)): + return node.value + elif isinstance(node, mparser.BooleanNode): + return node.value + elif isinstance(node, mparser.NumberNode): + return node.value + elif isinstance(node, mparser.ParenthesizedNode): + return self._evaluate_statement(node.inner) + elif isinstance(node, mparser.ArrayNode): + # TODO: This is where recursive types would come in handy + return [self._evaluate_statement(arg) for arg in node.args.arguments] + elif isinstance(node, mparser.IdNode): + return self.scope[node.value] + elif isinstance(node, mparser.ArithmeticNode): + l = self._evaluate_statement(node.left) + r = self._evaluate_statement(node.right) + if node.operation == 'add': + if (isinstance(l, str) and isinstance(r, str)) or \ + (isinstance(l, list) and isinstance(r, list)): + return l + r + elif node.operation == 'div': + if isinstance(l, str) and isinstance(r, str): + return os.path.join(l, r) + raise MesonException('Unsupported node type') + +def parse_machine_files(filenames: T.List[str], sourcedir: str): + parser = MachineFileParser(filenames, sourcedir) + return parser.sections -class MachineFile: - def __init__(self, fname): - with open(fname, encoding='utf-8') as f: - pass - self.stuff = None class MachineFileStore: - def __init__(self, native_files, cross_files): - self.native = [MachineFile(x) for x in native_files] - self.cross = [MachineFile(x) for x in cross_files] + def __init__(self, native_files, cross_files, source_dir): + self.native = MachineFileParser(native_files if native_files is not None else [], source_dir).sections + self.cross = MachineFileParser(cross_files if cross_files is not None else [], source_dir).sections diff --git a/mesonbuild/scripts/scanbuild.py b/mesonbuild/scripts/scanbuild.py index d7fbcf4fee33..b738aeee1b6e 100644 --- a/mesonbuild/scripts/scanbuild.py +++ b/mesonbuild/scripts/scanbuild.py @@ -7,7 +7,8 @@ import shutil import tempfile from ..environment import detect_ninja, detect_scanbuild -from ..coredata import get_cmd_line_file, CmdLineFileParser +from ..coredata import get_cmd_line_file +from ..machinefile import CmdLineFileParser from ..mesonlib import windows_proof_rmtree from pathlib import Path import typing as T diff --git a/test cases/unit/116 empty project/expected_mods.json b/test cases/unit/116 empty project/expected_mods.json index 19f56a5f71b3..fa5e0ec6c606 100644 --- a/test cases/unit/116 empty project/expected_mods.json +++ b/test cases/unit/116 empty project/expected_mods.json @@ -217,6 +217,7 @@ "mesonbuild.linkers", "mesonbuild.linkers.base", "mesonbuild.linkers.detect", + "mesonbuild.machinefile", "mesonbuild.mesonlib", "mesonbuild.mesonmain", "mesonbuild.mintro", diff --git a/unittests/machinefiletests.py b/unittests/machinefiletests.py index 3899ea942faa..5ff862cdcfb1 100644 --- a/unittests/machinefiletests.py +++ b/unittests/machinefiletests.py @@ -59,7 +59,7 @@ class MachineFileStoreTests(TestCase): def test_loading(self): store = machinefile.MachineFileStore([cross_dir / 'ubuntu-armhf.txt'], [], str(cross_dir)) - self.assertTrue(True) + self.assertIsNotNone(store) class NativeFileTests(BasePlatformTests): diff --git a/unittests/platformagnostictests.py b/unittests/platformagnostictests.py index 33a789b4bf9c..fe598a72bb80 100644 --- a/unittests/platformagnostictests.py +++ b/unittests/platformagnostictests.py @@ -274,7 +274,7 @@ def test_setup_loaded_modules(self): expected = json.load(f)['meson']['modules'] self.assertEqual(data['modules'], expected) - self.assertEqual(data['count'], 69) + self.assertEqual(data['count'], 70) def test_meson_package_cache_dir(self): # Copy testdir into temporary directory to not pollute meson source tree. From dfd22db4be6bfc0e64272479b51bbf314db04ac2 Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Mon, 3 Jun 2024 18:08:58 +0200 Subject: [PATCH 31/91] Add -export_dynamic flag for AppleDynamicLinker The apple linker uses -export_dynamic instead of --export-dynamic [1]. This should be set when setting export_dynamic: true. Resolves #13290 [1]: https://opensource.apple.com/source/ld64/ld64-609/doc/man/man1/ld.1.auto.html --- mesonbuild/linkers/linkers.py | 3 +++ unittests/darwintests.py | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/mesonbuild/linkers/linkers.py b/mesonbuild/linkers/linkers.py index de08e0f443e9..7507f5ab7b6c 100644 --- a/mesonbuild/linkers/linkers.py +++ b/mesonbuild/linkers/linkers.py @@ -834,6 +834,9 @@ def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, def get_thinlto_cache_args(self, path: str) -> T.List[str]: return ["-Wl,-cache_path_lto," + path] + def export_dynamic_args(self, env: 'Environment') -> T.List[str]: + return self._apply_prefix('-export_dynamic') + class LLVMLD64DynamicLinker(AppleDynamicLinker): diff --git a/unittests/darwintests.py b/unittests/darwintests.py index 5528bbc9fdc7..7403104771b7 100644 --- a/unittests/darwintests.py +++ b/unittests/darwintests.py @@ -81,6 +81,18 @@ def test_apple_bitcode_modules(self): self.build() self.run_tests() + def test_apple_lto_export_dynamic(self): + ''' + Tests that -Wl,-export_dynamic is correctly added, when export_dynamic: true is set. + On macOS, this is relevant for LTO builds only. + ''' + testdir = os.path.join(self.common_test_dir, '148 shared module resolving symbol in executable') + # Ensure that it builds even with LTO enabled + env = {'CFLAGS': '-flto'} + self.init(testdir, override_envvars=env) + self.build() + self.run_tests() + def _get_darwin_versions(self, fname): fname = os.path.join(self.builddir, fname) out = subprocess.check_output(['otool', '-L', fname], universal_newlines=True) From 31d8074727ec8ac4186f67ee5e24a6f5f80aaf13 Mon Sep 17 00:00:00 2001 From: John Wiele Date: Wed, 29 May 2024 16:45:20 -0400 Subject: [PATCH 32/91] Add help for meson dist to the man page. The added help text was derived from the output of the meson help command much like the markdown online help is derived. --- man/meson.1 | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/man/meson.1 b/man/meson.1 index 0221faad13df..5e63c811dcd6 100644 --- a/man/meson.1 +++ b/man/meson.1 @@ -83,6 +83,41 @@ To set values, use the \-D command line argument like this. .B meson configure \-Dopt1=value1 \-Dopt2=value2 +.SH The dist command + +.B meson dist +generates a release archive. + +.B meson dist [ +.I options +.B ] + +.SS "options:" +.TP +\fB\-h, \-\-help\fR +show this help message and exit + +.TP +\fB\-C WD\fR +directory to cd into before running + +.TP +\fB\-\-allow-dirty\fR +Allow even when repository contains uncommitted changes. + +.TP +\fB\-\-formats FORMATS\fR +Comma separated list of archive types to create. Supports xztar +(default), gztar, and zip. + +.TP +\fB\-\-include\-subprojects\fR +Include source code of subprojects that have been used for the build. + +.TP +\fB\-\-no\-tests\fR +Do not build and test generated packages. + .SH The introspect command Meson introspect is a command designed to make it simple to integrate with From 762262856a95c0e706629933d9e84050a57aa45f Mon Sep 17 00:00:00 2001 From: John Wiele Date: Wed, 29 May 2024 17:09:36 -0400 Subject: [PATCH 33/91] Add help for meson install to the man page. The added help text was derived from the output of the meson help command much like the markdown online help is derived. --- man/meson.1 | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/man/meson.1 b/man/meson.1 index 5e63c811dcd6..919f4545ee46 100644 --- a/man/meson.1 +++ b/man/meson.1 @@ -118,6 +118,58 @@ Include source code of subprojects that have been used for the build. \fB\-\-no\-tests\fR Do not build and test generated packages. +.SH The install command + +.B meson install +installs the project. + +.B meson install [ +.I options +.B ] + +.SS "options:" + +.TP +\fB\-h, \-\-help\fR +show this help message and exit + +.TP +\fB\-C WD\fR +directory to cd into before running + +.TP +\fB\-\-no-rebuild\fR +Do not rebuild before installing. + +.TP +\fB\-\-only\-changed\fR +Only overwrite files that are older than the copied file. + +.TP +\fB\-\-quiet\fR +Do not print every file that was installed. + +.TP +\fB\-\-destdir DESTDIR\fR +Sets or overrides DESTDIR environment. (Since 0.57.0) + +.TP +\fB\-\-dry\-run, \-n\fR +Doesn't actually install, but print logs. (Since 0.57.0) + +.TP +\fB\-\-skip\-subprojects [SKIP_SUBPROJECTS]\fR +Do not install files from given subprojects. (Since 0.58.0) + +.TP +\fB\-\-tags TAGS\fR +Install only targets having one of the given tags. (Since 0.60.0) + +.TP +\fB\-\-strip\fR +Strip targets even if strip option was not set during +configure. (Since 0.62.0) + .SH The introspect command Meson introspect is a command designed to make it simple to integrate with From 80a4b4a2ecd33bdbd73ca405a4f0221692bae57b Mon Sep 17 00:00:00 2001 From: John Wiele Date: Wed, 29 May 2024 17:33:28 -0400 Subject: [PATCH 34/91] Add help for meson init to the man page. The added help text was derived from the output of the meson help command much like the markdown online help is derived. --- man/meson.1 | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/man/meson.1 b/man/meson.1 index 919f4545ee46..15b2b9357e70 100644 --- a/man/meson.1 +++ b/man/meson.1 @@ -200,6 +200,68 @@ print all unit tests \fB\-\-help\fR print command line help +.SH The init command + +.B meson init +creates a new project + +.B meson init [ +.I options +.B ] [ +.I sourcefile... +.B ] + +.SS "positional arguments:" +.TP +sourcefile... +source files. default: all recognized files in current directory + +.SS "options:" +.TP +\fB\-h, \-\-help\fR +show this help message and exit + +.TP +\fB\-C WD\fR +directory to cd into before running + +.TP +\fB\-n NAME, \-\-name NAME\fR +project name. default: name of current directory + +.TP +\fB\-e EXECUTABLE, \-\-executable EXECUTABLE\fR +executable name. default: project name + +.TP +\fB\-d DEPS, \-\-deps DEPS\fR +dependencies, comma-separated + +.TP +\fB\-l {c,cpp,cs,cuda,d,fortran,java,objc,objcpp,rust,vala}, \ +\-\-language {c,cpp,cs,cuda,d,fortran,java,objc,objcpp,rust,vala}\fR +project language. default: autodetected based on source files + +.TP +\fB\-b, \-\-build +build after generation + +.TP +\fB\-\-builddir BUILDDIR\fR +directory for build + +.TP +\fB\-f, \-\-force\fR +force overwrite of existing files and directories. + +.TP +\fB\-\-type {executable,library}\fR +project type. default: executable based project + +.TP +\fB\-\-version VERSION\fR +project version. default: 0.1 + .SH The test command .B meson test From 559341bb2fec4c1b9a08993f4cb4165c03cfa08c Mon Sep 17 00:00:00 2001 From: John Wiele Date: Wed, 29 May 2024 17:47:49 -0400 Subject: [PATCH 35/91] Add help for meson subprojects to the man page. The added help text was derived from the output of the meson help command much like the markdown online help is derived. --- man/meson.1 | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/man/meson.1 b/man/meson.1 index 15b2b9357e70..f7a342b3371e 100644 --- a/man/meson.1 +++ b/man/meson.1 @@ -363,6 +363,49 @@ show available versions of the specified project \fBstatus\fR show installed and available versions of currently used subprojects +.SH The subprojects command + +.B meson subprojects +is used to manage subprojects. + +.B meson subprojects [ +.I options +.B ] [ +.I command +.B ] + +.SS "options:" +.TP +\fB\-h, \-\-help\fR +show this help message and exit + +.SS "commands:" +.TP +\fBupdate\fR +Update all subprojects from wrap files + +.TP +\fBcheckout\fR +Checkout a branch (git only) + +.TP +\fBdownload\fR +Ensure subprojects are fetched, even if not in use. Already downloaded +subprojects are not modified. This can be used to pre-fetch all +subprojects and avoid downloads during configure. + +.TP +\fBforeach\fR +Execute a command in each subproject directory. + +.TP +\fBpurge\fR +Remove all wrap-based subproject artifacts + +.TP +\fBpackagefiles\fR +Manage the packagefiles overlay + .SH EXIT STATUS .TP From caa32abf45146941a80ce99bc97fb35ce7c59bfd Mon Sep 17 00:00:00 2001 From: John Wiele Date: Wed, 29 May 2024 20:11:24 -0400 Subject: [PATCH 36/91] Add help for meson rewrite to the man page. The added help text was derived from the output of the meson help command much like the markdown online help is derived. --- man/meson.1 | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/man/meson.1 b/man/meson.1 index f7a342b3371e..37e329952b71 100644 --- a/man/meson.1 +++ b/man/meson.1 @@ -406,6 +406,53 @@ Remove all wrap-based subproject artifacts \fBpackagefiles\fR Manage the packagefiles overlay +.SH The rewrite command + +.B meson rewrite +modifies the project definition. + +.B meson rewrite [ +.I options +.B ] [ +.I command +.B ] + +.SS "options:" + +.TP +\fB\-h, \-\-help\fR +show this help message and exit + +.TP +\fB\-s SRCDIR, \-\-sourcedir SRCDIR\fR +Path to source directory. + +.TP +\fB\-V, \-\-verbose\fR +Enable verbose output + +.TP +\fB\-S, \-\-skip\-errors\fR +Skip errors instead of aborting + +.SS "commands:" + +.TP +\fBtarget (tgt)\fR +Modify a target + +.TP +\fBkwargs\fR +Modify keyword arguments + +.TP +\fBdefault-options (def)\fR +Modify the project default options + +.TP +\fBcommand (cmd)\fR +Execute a JSON array of commands + .SH EXIT STATUS .TP From 2e6f5c8bf296b26cd6af5de40b634b19ff216919 Mon Sep 17 00:00:00 2001 From: John Wiele Date: Thu, 30 May 2024 10:33:53 -0400 Subject: [PATCH 37/91] Add help for meson compile to the man page. The added help text was derived from the output of the meson help command much like the markdown online help is derived. --- man/meson.1 | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/man/meson.1 b/man/meson.1 index 37e329952b71..0467f66fd5ad 100644 --- a/man/meson.1 +++ b/man/meson.1 @@ -453,6 +453,62 @@ Modify the project default options \fBcommand (cmd)\fR Execute a JSON array of commands +.SH The compile command + +.B meson compile +builds the project. + +.B meson compile [ +.I options +.B ] [ +.I TARGET... +.B ] + +.SS "positional arguments:" +.TP +\fBTARGET\fR +Targets to build. Target has the following format: +[PATH_TO_TARGET/]TARGET_NAME.TARGET_SUFFIX[:TARGET_TYPE]. + +.SS "options:" + +.TP +\fB\-h, \-\-help\fR +show this help message and exit + +.TP +\fB\-\-clean\fR +Clean the build directory. + +.TP +\fB\-C WD\fR +directory to cd into before running + +.TP +\fB\-j JOBS, \-\-jobs JOBS\fR +The number of worker jobs to run (if supported). If the value is less +than 1 the build program will guess. + +.TP +\fB\-l LOAD_AVERAGE, \-\-load-average LOAD_AVERAGE\fR +The system load average to try to maintain (if supported). + +.TP +\fB\-v, \-\-verbose\fR +Show more verbose output. + +.TP +\fB\-\-ninja\-args NINJA_ARGS\fR +Arguments to pass to `ninja` (applied only on `ninja` backend). + +.TP +\fB\-\-vs\-args VS_ARGS\fR +Arguments to pass to `msbuild` (applied only on `vs` backend). + +.TP +\fB\-\-xcode\-args XCODE_ARGS\fR +Arguments to pass to `xcodebuild` (applied only on `xcode` backend). + .SH EXIT STATUS .TP From bce9b44c0bbdb8b4c4ccc30d8554350d7dbfc938 Mon Sep 17 00:00:00 2001 From: John Wiele Date: Thu, 30 May 2024 12:36:57 -0400 Subject: [PATCH 38/91] Add help for meson devenv to the man page. The added help text was derived from the output of the meson help command much like the markdown online help is derived. --- man/meson.1 | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/man/meson.1 b/man/meson.1 index 0467f66fd5ad..594fc1a7091d 100644 --- a/man/meson.1 +++ b/man/meson.1 @@ -509,6 +509,46 @@ Arguments to pass to `msbuild` (applied only on `vs` backend). \fB\-\-xcode\-args XCODE_ARGS\fR Arguments to pass to `xcodebuild` (applied only on `xcode` backend). +.SH The devenv command + +.B meson devenv +runs commands in the developer environment. + +.B meson devenv [ +.I options +.B ] [ +.I command +.B ] + +.SS "positional arguments:" + +.TP +\fBcommand\fR +Command to run in developer environment (default: interactive shell) + +.SS "options:" + +.TP +\fB\-h, \-\-help\fR +show this help message and exit + +.TP +\fB\-C BUILDDIR\fR +Path to build directory + +.TP +\fB\-\-workdir WORKDIR, \-w WORKDIR\fR +Directory to cd into before running (default: builddir, Since 1.0.0) + +.TP +\fB\-\-dump [DUMP]\fR +Only print required environment (Since 0.62.0) Takes an optional file +path (Since 1.1.0) + +.TP +\fB\-\-dump-format {sh,export,vscode}\fR +Format used with --dump (Since 1.1.0) + .SH EXIT STATUS .TP From fd6a6e8b90b33e358fb3facbeb20378cb6032a08 Mon Sep 17 00:00:00 2001 From: John Wiele Date: Thu, 30 May 2024 13:31:26 -0400 Subject: [PATCH 39/91] Add help for meson env2mfile to the man page. The added help text was derived from the output of the meson help command much like the markdown online help is derived. --- man/meson.1 | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/man/meson.1 b/man/meson.1 index 594fc1a7091d..885eeb33a467 100644 --- a/man/meson.1 +++ b/man/meson.1 @@ -549,6 +549,65 @@ path (Since 1.1.0) \fB\-\-dump-format {sh,export,vscode}\fR Format used with --dump (Since 1.1.0) +.SH The env2mfile command + +.B meson env2mfile +converts the current environment to a cross or native file. + +.B meson env2mfile [ +.I options +.B ] + +.SS "options:" + +.TP +\fB\-h, \-\-help\fR +show this help message and exit + +.TP +\fB\-\-debarch DEBARCH\fR +The dpkg architecture to generate. + +.TP +\fB\-\-gccsuffix GCCSUFFIX\fR +A particular gcc version suffix if necessary. + +.TP +\fB\-o OUTFILE\fR +The output file. + +.TP +\fB\-\-cross\fR +Generate a cross compilation file. + +.TP +\fB\-\-native\fR +Generate a native compilation file. + +.TP +\fB\-\-system SYSTEM\fR +Define system for cross compilation. + +.TP +\fB\-\-subsystem SUBSYSTEM\fR +Define subsystem for cross compilation. + +.TP +\fB\-\-kernel KERNEL\fR +Define kernel for cross compilation. + +.TP +\fB\-\-cpu CPU\fR +Define cpu for cross compilation. + +.TP +\fB\-\-cpu-family CPU_FAMILY\fR +Define cpu family for cross compilation. + +.TP +\fB\-\-endian {big,little}\fR +Define endianness for cross compilation. + .SH EXIT STATUS .TP From ee479ded3f13107deb1ea86f2090745f6ffbca51 Mon Sep 17 00:00:00 2001 From: Ruben Gonzalez Date: Thu, 1 Feb 2024 15:38:32 +0100 Subject: [PATCH 40/91] wrap: default values for netrc are empty string from python 3.11 From python 3.11 [1]: > The entry in the netrc file no longer needs to contain all tokens. The missing > tokens' value default to an empty string. All the tokens and their values now > can contain arbitrary characters, like whitespace and non-ASCII characters. > If the login name is anonymous, it won't trigger the security check. [1] https://github.com/python/cpython/commit/15409c720be0503131713e3d3abc1acd0da07378 --- mesonbuild/wrap/wrap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mesonbuild/wrap/wrap.py b/mesonbuild/wrap/wrap.py index a8efd163a5d6..a260e0ce681c 100644 --- a/mesonbuild/wrap/wrap.py +++ b/mesonbuild/wrap/wrap.py @@ -654,7 +654,7 @@ def get_netrc_credentials(self, netloc: str) -> T.Optional[T.Tuple[str, str]]: return None login, account, password = self.netrc.authenticators(netloc) - if account is not None: + if account: login = account return login, password From 03f055109121d69ec7990fde874d0d523bc744f6 Mon Sep 17 00:00:00 2001 From: Jussi Pakkanen Date: Sat, 8 Jun 2024 18:01:49 +0300 Subject: [PATCH 41/91] Create helper method for lang opts and use in C compiler classes. --- mesonbuild/compilers/c.py | 90 +++++++++++++++++++------------ mesonbuild/compilers/compilers.py | 3 ++ 2 files changed, 59 insertions(+), 34 deletions(-) diff --git a/mesonbuild/compilers/c.py b/mesonbuild/compilers/c.py index d0326d795016..7f2efc991e9c 100644 --- a/mesonbuild/compilers/c.py +++ b/mesonbuild/compilers/c.py @@ -8,7 +8,7 @@ from .. import options from .. import mlog -from ..mesonlib import MesonException, version_compare, OptionKey +from ..mesonlib import MesonException, version_compare from .c_function_attributes import C_FUNC_ATTRIBUTES from .mixins.clike import CLikeCompiler from .mixins.ccrx import CcrxCompiler @@ -94,7 +94,7 @@ def has_header_symbol(self, hname: str, symbol: str, prefix: str, def get_options(self) -> 'MutableKeyedOptionDictType': opts = super().get_options() - key = OptionKey('std', machine=self.for_machine, lang=self.language) + key = self.form_langopt_key('std') opts.update({ key: options.UserStdOption('C', _ALL_STDS), }) @@ -127,7 +127,8 @@ def get_options(self) -> 'MutableKeyedOptionDictType': stds += ['c2x'] if version_compare(self.version, self._C23_VERSION): stds += ['c23'] - std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] + key = self.form_langopt_key('std') + std_opt = opts[key] assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(stds, gnu=True) return opts @@ -155,7 +156,7 @@ def get_options(self) -> 'MutableKeyedOptionDictType': self.update_options( opts, self.create_option(options.UserArrayOption, - OptionKey('winlibs', machine=self.for_machine, lang=self.language), + self.form_langopt_key('winlibs'), 'Standard Win libraries to link against', gnu_winlibs), ) @@ -163,7 +164,8 @@ def get_options(self) -> 'MutableKeyedOptionDictType': def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] - std = options[OptionKey('std', machine=self.for_machine, lang=self.language)] + key = self.form_langopt_key('std') + std = options[key] if std.value != 'none': args.append('-std=' + std.value) return args @@ -171,7 +173,8 @@ def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str] def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: if self.info.is_windows() or self.info.is_cygwin(): # without a typedict mypy can't understand this. - libs = options[OptionKey('winlibs', machine=self.for_machine, lang=self.language)].value.copy() + key = self.form_langopt_key('winlibs') + libs = options[key].value.copy() assert isinstance(libs, list) for l in libs: assert isinstance(l, str) @@ -246,14 +249,16 @@ def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_ def get_options(self) -> 'MutableKeyedOptionDictType': opts = CCompiler.get_options(self) - std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] + key = self.form_langopt_key('std') + std_opt = opts[key] assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(['c90', 'c99', 'c11'], gnu=True) return opts def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] - std = options[OptionKey('std', machine=self.for_machine, lang=self.language)] + key = self.form_langopt_key('std') + std = options[key] if std.value != 'none': args.append('-std=' + std.value) return args @@ -296,7 +301,7 @@ def get_options(self) -> 'MutableKeyedOptionDictType': stds += ['c2x'] if version_compare(self.version, self._C23_VERSION): stds += ['c23'] - key = OptionKey('std', machine=self.for_machine, lang=self.language) + key = self.form_langopt_key('std') std_opt = opts[key] assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(stds, gnu=True) @@ -312,7 +317,8 @@ def get_options(self) -> 'MutableKeyedOptionDictType': def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] - std = options[OptionKey('std', lang=self.language, machine=self.for_machine)] + key = self.form_langopt_key('std') + std = options[key] if std.value != 'none': args.append('-std=' + std.value) return args @@ -320,7 +326,8 @@ def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str] def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: if self.info.is_windows() or self.info.is_cygwin(): # without a typeddict mypy can't figure this out - libs: T.List[str] = options[OptionKey('winlibs', lang=self.language, machine=self.for_machine)].value.copy() + key = self.form_langopt_key('winlibs') + libs: T.List[str] = options[key].value.copy() assert isinstance(libs, list) for l in libs: assert isinstance(l, str) @@ -376,7 +383,8 @@ def get_options(self) -> 'MutableKeyedOptionDictType': stds += ['c90', 'c1x', 'gnu90', 'gnu1x', 'iso9899:2011'] if version_compare(self.version, '>=1.26.00'): stds += ['c17', 'c18', 'iso9899:2017', 'iso9899:2018', 'gnu17', 'gnu18'] - std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] + key = self.form_langopt_key('std') + std_opt = opts[key] assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(stds) return opts @@ -415,14 +423,16 @@ def get_options(self) -> 'MutableKeyedOptionDictType': stds = ['c89', 'c99'] if version_compare(self.version, '>=16.0.0'): stds += ['c11'] - std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] + key = self.form_langopt_key('std') + std_opt = opts[key] assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(stds, gnu=True) return opts def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] - std = options[OptionKey('std', machine=self.for_machine, lang=self.language)] + key = self.form_langopt_key('std') + std = options[key] if std.value != 'none': args.append('-std=' + std.value) return args @@ -442,7 +452,7 @@ def get_options(self) -> MutableKeyedOptionDictType: super().get_options(), self.create_option( options.UserArrayOption, - OptionKey('winlibs', machine=self.for_machine, lang=self.language), + self.form_langopt_key('winlibs'), 'Windows libs to link against.', msvc_winlibs, ), @@ -450,7 +460,7 @@ def get_options(self) -> MutableKeyedOptionDictType: def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: # need a TypeDict to make this work - key = OptionKey('winlibs', machine=self.for_machine, lang=self.language) + key = self.form_langopt_key('winlibs') libs = options[key].value.copy() assert isinstance(libs, list) for l in libs: @@ -479,14 +489,16 @@ def get_options(self) -> 'MutableKeyedOptionDictType': stds += ['c11'] if version_compare(self.version, self._C17_VERSION): stds += ['c17', 'c18'] - std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] + key = self.form_langopt_key('std') + std_opt = opts[key] assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(stds, gnu=True, gnu_deprecated=True) return opts def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] - std = options[OptionKey('std', machine=self.for_machine, lang=self.language)] + key = self.form_langopt_key('std') + std = options[key] # As of MVSC 16.8, /std:c11 and /std:c17 are the only valid C standard options. if std.value in {'c11'}: args.append('/std:c11') @@ -506,7 +518,7 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic ClangClCompiler.__init__(self, target) def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: - key = OptionKey('std', machine=self.for_machine, lang=self.language) + key = self.form_langopt_key('std') std = options[key].value if std != "none": return [f'/clang:-std={std}'] @@ -528,14 +540,15 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic def get_options(self) -> 'MutableKeyedOptionDictType': opts = super().get_options() - std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] + key = self.form_langopt_key('std') + std_opt = opts[key] assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(['c89', 'c99', 'c11']) return opts def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] - key = OptionKey('std', machine=self.for_machine, lang=self.language) + key = self.form_langopt_key('std') std = options[key] if std.value == 'c89': mlog.log("ICL doesn't explicitly implement c89, setting the standard to 'none', which is close.", once=True) @@ -561,14 +574,15 @@ def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_ def get_options(self) -> 'MutableKeyedOptionDictType': opts = CCompiler.get_options(self) - std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] + key = self.form_langopt_key('std') + std_opt = opts[key] assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(['c89', 'c99', 'c11']) return opts def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] - key = OptionKey('std', machine=self.for_machine, lang=self.language) + key = self.form_langopt_key('std') std = options[key] if std.value != 'none': args.append('--' + std.value) @@ -590,7 +604,8 @@ def get_always_args(self) -> T.List[str]: def get_options(self) -> 'MutableKeyedOptionDictType': opts = CCompiler.get_options(self) - std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] + key = self.form_langopt_key('std') + std_opt = opts[key] assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(['c89', 'c99']) return opts @@ -600,7 +615,7 @@ def get_no_stdinc_args(self) -> T.List[str]: def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] - key = OptionKey('std', machine=self.for_machine, lang=self.language) + key = self.form_langopt_key('std') std = options[key] if std.value == 'c89': args.append('-lang=c') @@ -637,7 +652,8 @@ def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_ def get_options(self) -> 'MutableKeyedOptionDictType': opts = CCompiler.get_options(self) - std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] + key = self.form_langopt_key('std') + std_opt = opts[key] assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(['c89', 'c99'], gnu=True) return opts @@ -647,7 +663,7 @@ def get_no_stdinc_args(self) -> T.List[str]: def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] - key = OptionKey('std', machine=self.for_machine, lang=self.language) + key = self.form_langopt_key('std') std = options[key] if std.value != 'none': args.append('-ansi') @@ -682,7 +698,8 @@ def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_ def get_options(self) -> 'MutableKeyedOptionDictType': opts = CCompiler.get_options(self) - std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] + key = self.form_langopt_key('std') + std_opt = opts[key] assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(['c89', 'c99']) return opts @@ -719,7 +736,8 @@ def get_always_args(self) -> T.List[str]: def get_options(self) -> 'MutableKeyedOptionDictType': opts = CCompiler.get_options(self) - std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] + key = self.form_langopt_key('std') + std_opt = opts[key] assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(['c89', 'c99', 'c11']) return opts @@ -729,7 +747,7 @@ def get_no_stdinc_args(self) -> T.List[str]: def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] - key = OptionKey('std', machine=self.for_machine, lang=self.language) + key = self.form_langopt_key('std') std = options[key] if std.value != 'none': args.append('--' + std.value) @@ -759,12 +777,14 @@ def get_instruction_set_args(self, instruction_set: str) -> T.Optional[T.List[st def get_options(self) -> 'MutableKeyedOptionDictType': opts = CCompiler.get_options(self) c_stds = ['c99'] - opts[OptionKey('std', machine=self.for_machine, lang=self.language)].choices = ['none'] + c_stds + key = self.form_langopt_key('std') + opts[key].choices = ['none'] + c_stds return opts def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] - std = options[OptionKey('std', machine=self.for_machine, lang=self.language)] + key = self.form_langopt_key('std') + std = options[key] if std.value != 'none': args.append('-lang') args.append(std.value) @@ -787,12 +807,14 @@ def get_instruction_set_args(self, instruction_set: str) -> T.Optional[T.List[st def get_options(self) -> 'MutableKeyedOptionDictType': opts = CCompiler.get_options(self) c_stds = ['c99'] - opts[OptionKey('std', machine=self.for_machine, lang=self.language)].choices = ['none'] + c_stds + key = self.form_langopt_key('std') + opts[key].choices = ['none'] + c_stds return opts def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] - std = options[OptionKey('std', machine=self.for_machine, lang=self.language)] + key = self.form_langopt_key('std') + std = options[key] if std.value != 'none': args.append('-lang ' + std.value) return args diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index 4267384c5232..ef0ea70f72d4 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -1351,6 +1351,9 @@ def get_preprocessor(self) -> Compiler: """ raise EnvironmentException(f'{self.get_id()} does not support preprocessor') + def form_langopt_key(self, basename: str) -> OptionKey: + return OptionKey(basename, machine=self.for_machine, lang=self.language) + def get_global_options(lang: str, comp: T.Type[Compiler], for_machine: MachineChoice, From fbb8b09379ce3c5e9f19a0c7c4f56fce0ce6ed0e Mon Sep 17 00:00:00 2001 From: Jussi Pakkanen Date: Sat, 8 Jun 2024 18:25:03 +0300 Subject: [PATCH 42/91] Use helper method in C++ compiler classes. --- mesonbuild/compilers/cpp.py | 112 +++++++++++++++++++----------------- 1 file changed, 60 insertions(+), 52 deletions(-) diff --git a/mesonbuild/compilers/cpp.py b/mesonbuild/compilers/cpp.py index ea5182370fe3..12d7201002f9 100644 --- a/mesonbuild/compilers/cpp.py +++ b/mesonbuild/compilers/cpp.py @@ -10,7 +10,7 @@ from .. import options from .. import mlog -from ..mesonlib import MesonException, version_compare, OptionKey +from ..mesonlib import MesonException, version_compare from .compilers import ( gnu_winlibs, @@ -172,7 +172,7 @@ def _find_best_cpp_std(self, cpp_std: str) -> str: def get_options(self) -> 'MutableKeyedOptionDictType': opts = super().get_options() - key = OptionKey('std', machine=self.for_machine, lang=self.language) + key = self.form_langopt_key('std') opts.update({ key: options.UserStdOption('C++', _ALL_STDS), }) @@ -239,20 +239,19 @@ def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_ def get_options(self) -> 'MutableKeyedOptionDictType': opts = CPPCompiler.get_options(self) - key = OptionKey('key', machine=self.for_machine, lang=self.language) self.update_options( opts, self.create_option(options.UserComboOption, - key.evolve('eh'), + self.form_langopt_key('eh'), 'C++ exception handling type.', ['none', 'default', 'a', 's', 'sc'], 'default'), self.create_option(options.UserBooleanOption, - key.evolve('rtti'), + self.form_langopt_key('rtti'), 'Enable RTTI', True), self.create_option(options.UserBooleanOption, - key.evolve('debugstl'), + self.form_langopt_key('debugstl'), 'STL debug mode', False), ) @@ -263,14 +262,14 @@ def get_options(self) -> 'MutableKeyedOptionDictType': cppstd_choices.append('c++23') if version_compare(self.version, self._CPP26_VERSION): cppstd_choices.append('c++26') - std_opt = opts[key.evolve('std')] + std_opt = opts[self.form_langopt_key('std')] assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(cppstd_choices, gnu=True) if self.info.is_windows() or self.info.is_cygwin(): self.update_options( opts, self.create_option(options.UserArrayOption, - key.evolve('winlibs'), + self.form_langopt_key('winlibs'), 'Standard Win libraries to link against', gnu_winlibs), ) @@ -278,14 +277,16 @@ def get_options(self) -> 'MutableKeyedOptionDictType': def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args: T.List[str] = [] - key = OptionKey('std', machine=self.for_machine, lang=self.language) + key = self.form_langopt_key('std') std = options[key] if std.value != 'none': args.append(self._find_best_cpp_std(std.value)) - non_msvc_eh_options(options[key.evolve('eh')].value, args) + key = self.form_langopt_key('eh') + non_msvc_eh_options(options[key].value, args) - if options[key.evolve('debugstl')].value: + key = self.form_langopt_key('debugstl') + if options[key].value: args.append('-D_GLIBCXX_DEBUG=1') # We can't do _LIBCPP_DEBUG because it's unreliable unless libc++ was built with it too: @@ -294,7 +295,8 @@ def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str] if version_compare(self.version, '>=18'): args.append('-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG') - if not options[key.evolve('rtti')].value: + key = self.form_langopt_key('rtti') + if not options[key].value: args.append('-fno-rtti') return args @@ -302,7 +304,7 @@ def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str] def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: if self.info.is_windows() or self.info.is_cygwin(): # without a typedict mypy can't understand this. - key = OptionKey('winlibs', machine=self.for_machine, lang=self.language) + key = self.form_langopt_key('winlibs') libs = options[key].value.copy() assert isinstance(libs, list) for l in libs: @@ -362,7 +364,7 @@ def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_ def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args: T.List[str] = [] - key = OptionKey('std', machine=self.for_machine, lang=self.language) + key = self.form_langopt_key('std') std = options[key] if std.value != 'none': args.append(self._find_best_cpp_std(std.value)) @@ -390,7 +392,7 @@ def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_ def get_options(self) -> 'MutableKeyedOptionDictType': opts = CPPCompiler.get_options(self) - key = OptionKey('std', machine=self.for_machine, lang=self.language) + key = self.form_langopt_key('std') self.update_options( opts, self.create_option(options.UserComboOption, @@ -406,12 +408,13 @@ def get_options(self) -> 'MutableKeyedOptionDictType': def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args: T.List[str] = [] - key = OptionKey('std', machine=self.for_machine, lang=self.language) + key = self.form_langopt_key('std') std = options[key] if std.value != 'none': args.append('-std=' + std.value) - non_msvc_eh_options(options[key.evolve('eh')].value, args) + key = self.form_langopt_key('eh') + non_msvc_eh_options(options[key].value, args) return args @@ -438,21 +441,21 @@ def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_ self.supported_warn_args(gnu_cpp_warning_args))} def get_options(self) -> 'MutableKeyedOptionDictType': - key = OptionKey('std', machine=self.for_machine, lang=self.language) + key = self.form_langopt_key('std') opts = CPPCompiler.get_options(self) self.update_options( opts, self.create_option(options.UserComboOption, - key.evolve('eh'), + self.form_langopt_key('eh'), 'C++ exception handling type.', ['none', 'default', 'a', 's', 'sc'], 'default'), self.create_option(options.UserBooleanOption, - key.evolve('rtti'), + self.form_langopt_key('rtti'), 'Enable RTTI', True), self.create_option(options.UserBooleanOption, - key.evolve('debugstl'), + self.form_langopt_key('debugstl'), 'STL debug mode', False), ) @@ -479,7 +482,7 @@ def get_options(self) -> 'MutableKeyedOptionDictType': def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args: T.List[str] = [] - key = OptionKey('std', machine=self.for_machine, lang=self.language) + key = self.form_langopt_key('std') std = options[key] if std.value != 'none': args.append(self._find_best_cpp_std(std.value)) @@ -496,7 +499,7 @@ def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str] def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: if self.info.is_windows() or self.info.is_cygwin(): # without a typedict mypy can't understand this. - key = OptionKey('winlibs', machine=self.for_machine, lang=self.language) + key = self.form_langopt_key('winlibs') libs = options[key].value.copy() assert isinstance(libs, list) for l in libs: @@ -579,16 +582,16 @@ def get_options(self) -> 'MutableKeyedOptionDictType': if version_compare(self.version, '>=1.26.00'): cpp_stds += ['c++20'] - key = OptionKey('std', machine=self.for_machine, lang=self.language) + key = self.form_langopt_key('std') self.update_options( opts, self.create_option(options.UserComboOption, - key.evolve('eh'), + self.form_langopt_key('eh'), 'C++ exception handling type.', ['none', 'default', 'a', 's', 'sc'], 'default'), self.create_option(options.UserBooleanOption, - key.evolve('debugstl'), + self.form_langopt_key('debugstl'), 'STL debug mode', False), ) @@ -612,14 +615,16 @@ def has_function(self, funcname: str, prefix: str, env: 'Environment', *, # Elbrus C++ compiler does not support RTTI, so don't check for it. def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args: T.List[str] = [] - key = OptionKey('std', machine=self.for_machine, lang=self.language) + key = self.form_langopt_key('std') std = options[key] if std.value != 'none': args.append(self._find_best_cpp_std(std.value)) - non_msvc_eh_options(options[key.evolve('eh')].value, args) + key = self.form_langopt_key('eh') + non_msvc_eh_options(options[key].value, args) - if options[key.evolve('debugstl')].value: + key = self.form_langopt_key('debugstl') + if options[key].value: args.append('-D_GLIBCXX_DEBUG=1') return args @@ -658,20 +663,20 @@ def get_options(self) -> 'MutableKeyedOptionDictType': c_stds += ['c++2a'] g_stds += ['gnu++2a'] - key = OptionKey('std', machine=self.for_machine, lang=self.language) + key = self.form_langopt_key('std') self.update_options( opts, self.create_option(options.UserComboOption, - key.evolve('eh'), + self.form_langopt_key('eh'), 'C++ exception handling type.', ['none', 'default', 'a', 's', 'sc'], 'default'), self.create_option(options.UserBooleanOption, - key.evolve('rtti'), + self.form_langopt_key('rtti'), 'Enable RTTI', True), self.create_option(options.UserBooleanOption, - key.evolve('debugstl'), + self.form_langopt_key('debugstl'), 'STL debug mode', False), ) @@ -682,7 +687,7 @@ def get_options(self) -> 'MutableKeyedOptionDictType': def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args: T.List[str] = [] - key = OptionKey('std', machine=self.for_machine, lang=self.language) + key = self.form_langopt_key('std') std = options[key] if std.value != 'none': remap_cpp03 = { @@ -727,24 +732,24 @@ class VisualStudioLikeCPPCompilerMixin(CompilerMixinBase): def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: # need a typeddict for this - key = OptionKey('winlibs', machine=self.for_machine, lang=self.language) + key = self.form_langopt_key('winlibs') return T.cast('T.List[str]', options[key].value[:]) def _get_options_impl(self, opts: 'MutableKeyedOptionDictType', cpp_stds: T.List[str]) -> 'MutableKeyedOptionDictType': - key = OptionKey('std', machine=self.for_machine, lang=self.language) + key = self.form_langopt_key('std') self.update_options( opts, self.create_option(options.UserComboOption, - key.evolve('eh'), + self.form_langopt_key('eh'), 'C++ exception handling type.', ['none', 'default', 'a', 's', 'sc'], 'default'), self.create_option(options.UserBooleanOption, - key.evolve('rtti'), + self.form_langopt_key('rtti'), 'Enable RTTI', True), self.create_option(options.UserArrayOption, - key.evolve('winlibs'), + self.form_langopt_key('winlibs'), 'Windows libs to link against.', msvc_winlibs), ) @@ -755,9 +760,9 @@ def _get_options_impl(self, opts: 'MutableKeyedOptionDictType', cpp_stds: T.List def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args: T.List[str] = [] - key = OptionKey('std', machine=self.for_machine, lang=self.language) + key = self.form_langopt_key('std') - eh = options[key.evolve('eh')] + eh = options[self.form_langopt_key('eh')] if eh.value == 'default': args.append('/EHsc') elif eh.value == 'none': @@ -765,7 +770,7 @@ def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str] else: args.append('/EH' + eh.value) - if not options[key.evolve('rtti')].value: + if not options[self.form_langopt_key('rtti')].value: args.append('/GR-') permissive, ver = self.VC_VERSION_MAP[options[key].value] @@ -795,7 +800,7 @@ def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str] # which means setting the C++ standard version to C++14, in compilers that support it # (i.e., after VS2015U3) # if one is using anything before that point, one cannot set the standard. - key = OptionKey('std', machine=self.for_machine, lang=self.language) + key = self.form_langopt_key('std') if options[key].value in {'vc++11', 'c++11'}: mlog.warning(self.id, 'does not support C++11;', 'attempting best effort; setting the standard to C++14', @@ -842,7 +847,7 @@ def get_options(self) -> 'MutableKeyedOptionDictType': return self._get_options_impl(super().get_options(), cpp_stds) def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: - key = OptionKey('std', machine=self.for_machine, lang=self.language) + key = self.form_langopt_key('std') if options[key].value != 'none' and version_compare(self.version, '<19.00.24210'): mlog.warning('This version of MSVC does not support cpp_std arguments', fatal=False) options = copy.copy(options) @@ -911,14 +916,14 @@ def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_ def get_options(self) -> 'MutableKeyedOptionDictType': opts = CPPCompiler.get_options(self) - std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] + std_opt = self.form_langopt_key('std') assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(['c++03', 'c++11']) return opts def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args: T.List[str] = [] - key = OptionKey('std', machine=self.for_machine, lang=self.language) + key = self.form_langopt_key('std') std = options[key] if std.value == 'c++11': args.append('--cpp11') @@ -972,14 +977,15 @@ def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_ def get_options(self) -> 'MutableKeyedOptionDictType': opts = CPPCompiler.get_options(self) - std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] + key = self.form_langopt_key('std') + std_opt = opts[key] assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(['c++03']) return opts def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args: T.List[str] = [] - key = OptionKey('std', machine=self.for_machine, lang=self.language) + key = self.form_langopt_key('std') std = options[key] if std.value != 'none': args.append('--' + std.value) @@ -1014,13 +1020,14 @@ def get_instruction_set_args(self, instruction_set: str) -> T.Optional[T.List[st def get_options(self) -> 'MutableKeyedOptionDictType': opts = CPPCompiler.get_options(self) - key = OptionKey('std', machine=self.for_machine, lang=self.language) + key = self.form_langopt_key('std') opts[key].choices = ['none'] return opts def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args: T.List[str] = [] - std = options[OptionKey('std', machine=self.for_machine, lang=self.language)] + key = self.form_langopt_key('std') + std = options[key] if std.value != 'none': args.append('-lang') args.append(std.value) @@ -1042,13 +1049,14 @@ def get_instruction_set_args(self, instruction_set: str) -> T.Optional[T.List[st def get_options(self) -> 'MutableKeyedOptionDictType': opts = CPPCompiler.get_options(self) - key = OptionKey('std', machine=self.for_machine, lang=self.language) + key = self.form_langopt_key('std') opts[key].choices = ['none'] return opts def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args: T.List[str] = [] - std = options[OptionKey('std', machine=self.for_machine, lang=self.language)] + key = self.form_langopt_key('std') + std = options[key] if std.value != 'none': args.append('-lang ' + std.value) return args From 53acb50052af117907bdf1dba019ba0467ebf0e2 Mon Sep 17 00:00:00 2001 From: Jussi Pakkanen Date: Sat, 8 Jun 2024 18:28:04 +0300 Subject: [PATCH 43/91] Use helper method in Fortran compiler classes. --- mesonbuild/compilers/fortran.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/mesonbuild/compilers/fortran.py b/mesonbuild/compilers/fortran.py index 7b2f5d9d49e2..66fb466a5485 100644 --- a/mesonbuild/compilers/fortran.py +++ b/mesonbuild/compilers/fortran.py @@ -21,7 +21,7 @@ from mesonbuild.mesonlib import ( version_compare, MesonException, - LibType, OptionKey, + LibType, ) if T.TYPE_CHECKING: @@ -115,7 +115,7 @@ def get_options(self) -> 'MutableKeyedOptionDictType': return self.update_options( super().get_options(), self.create_option(options.UserComboOption, - OptionKey('std', machine=self.for_machine, lang=self.language), + self.form_langopt_key('std'), 'Fortran language standard to use', ['none'], 'none'), @@ -147,13 +147,13 @@ def get_options(self) -> 'MutableKeyedOptionDictType': fortran_stds += ['f2008'] if version_compare(self.version, '>=8.0.0'): fortran_stds += ['f2018'] - key = OptionKey('std', machine=self.for_machine, lang=self.language) + key = self.form_langopt_key('std') opts[key].choices = ['none'] + fortran_stds return opts def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args: T.List[str] = [] - key = OptionKey('std', machine=self.for_machine, lang=self.language) + key = self.form_langopt_key('std') std = options[key] if std.value != 'none': args.append('-std=' + std.value) @@ -205,7 +205,7 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic def get_options(self) -> 'MutableKeyedOptionDictType': opts = FortranCompiler.get_options(self) fortran_stds = ['f95', 'f2003', 'f2008', 'gnu', 'legacy', 'f2008ts'] - key = OptionKey('std', machine=self.for_machine, lang=self.language) + key = self.form_langopt_key('std') opts[key].choices = ['none'] + fortran_stds return opts @@ -284,13 +284,13 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic def get_options(self) -> 'MutableKeyedOptionDictType': opts = FortranCompiler.get_options(self) - key = OptionKey('std', machine=self.for_machine, lang=self.language) + key = self.form_langopt_key('std') opts[key].choices = ['none', 'legacy', 'f95', 'f2003', 'f2008', 'f2018'] return opts def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args: T.List[str] = [] - key = OptionKey('std', machine=self.for_machine, lang=self.language) + key = self.form_langopt_key('std') std = options[key] stds = {'legacy': 'none', 'f95': 'f95', 'f2003': 'f03', 'f2008': 'f08', 'f2018': 'f18'} if std.value != 'none': @@ -339,13 +339,13 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic def get_options(self) -> 'MutableKeyedOptionDictType': opts = FortranCompiler.get_options(self) - key = OptionKey('std', machine=self.for_machine, lang=self.language) + key = self.form_langopt_key('std') opts[key].choices = ['none', 'legacy', 'f95', 'f2003', 'f2008', 'f2018'] return opts def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args: T.List[str] = [] - key = OptionKey('std', machine=self.for_machine, lang=self.language) + key = self.form_langopt_key('std') std = options[key] stds = {'legacy': 'none', 'f95': 'f95', 'f2003': 'f03', 'f2008': 'f08', 'f2018': 'f18'} if std.value != 'none': From 4eb1eb3c4a4d1d3328128d85c490d7384753f004 Mon Sep 17 00:00:00 2001 From: Jussi Pakkanen Date: Sat, 8 Jun 2024 18:31:11 +0300 Subject: [PATCH 44/91] Use helper method in Rust compiler class. --- mesonbuild/compilers/rust.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mesonbuild/compilers/rust.py b/mesonbuild/compilers/rust.py index 0f52dbb5ebe5..346a3dd69460 100644 --- a/mesonbuild/compilers/rust.py +++ b/mesonbuild/compilers/rust.py @@ -159,7 +159,7 @@ def use_linker_args(cls, linker: str, version: str) -> T.List[str]: def get_options(self) -> MutableKeyedOptionDictType: return dict((self.create_option(options.UserComboOption, - OptionKey('std', machine=self.for_machine, lang=self.language), + self.form_langopt_key('std'), 'Rust edition to use', ['none', '2015', '2018', '2021'], 'none'),)) @@ -172,7 +172,7 @@ def get_dependency_compile_args(self, dep: 'Dependency') -> T.List[str]: def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] - key = OptionKey('std', machine=self.for_machine, lang=self.language) + key = self.form_langopt_key('std') std = options[key] if std.value != 'none': args.append('--edition=' + std.value) From 374fa7f0da278d46a4c3adb587f4b43089f5d7db Mon Sep 17 00:00:00 2001 From: Jussi Pakkanen Date: Sat, 8 Jun 2024 18:37:17 +0300 Subject: [PATCH 45/91] Use helper method in remaining compiler classes. --- mesonbuild/compilers/cuda.py | 12 ++++++------ mesonbuild/compilers/cython.py | 14 ++++++++------ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/mesonbuild/compilers/cuda.py b/mesonbuild/compilers/cuda.py index 8680cf076401..954040e1e037 100644 --- a/mesonbuild/compilers/cuda.py +++ b/mesonbuild/compilers/cuda.py @@ -13,7 +13,7 @@ from .. import mlog from ..mesonlib import ( EnvironmentException, Popen_safe, - is_windows, LibType, OptionKey, version_compare, + is_windows, LibType, version_compare, ) from .compilers import Compiler @@ -645,12 +645,12 @@ def get_options(self) -> 'MutableKeyedOptionDictType': return self.update_options( super().get_options(), self.create_option(options.UserComboOption, - OptionKey('std', machine=self.for_machine, lang=self.language), + self.form_langopt_key('std'), 'C++ language standard to use with CUDA', cpp_stds, 'none'), self.create_option(options.UserStringOption, - OptionKey('ccbindir', machine=self.for_machine, lang=self.language), + self.form_langopt_key('ccbindir'), 'CUDA non-default toolchain directory to use (-ccbin)', ''), ) @@ -663,7 +663,7 @@ def _to_host_compiler_options(self, options: 'KeyedOptionDictType') -> 'KeyedOpt # We must strip the -std option from the host compiler option set, as NVCC has # its own -std flag that may not agree with the host compiler's. host_options = {key: options.get(key, opt) for key, opt in self.host_compiler.get_options().items()} - std_key = OptionKey('std', machine=self.for_machine, lang=self.host_compiler.language) + std_key = self.form_langopt_key('std') overrides = {std_key: 'none'} return coredata.OptionsView(host_options, overrides=overrides) @@ -673,7 +673,7 @@ def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str] # the combination of CUDA version and MSVC version; the --std= is thus ignored # and attempting to use it will result in a warning: https://stackoverflow.com/a/51272091/741027 if not is_windows(): - key = OptionKey('std', machine=self.for_machine, lang=self.language) + key = self.form_langopt_key('std') std = options[key] if std.value != 'none': args.append('--std=' + std.value) @@ -793,7 +793,7 @@ def get_dependency_link_args(self, dep: 'Dependency') -> T.List[str]: return self._to_host_flags(super().get_dependency_link_args(dep), _Phase.LINKER) def get_ccbin_args(self, options: 'KeyedOptionDictType') -> T.List[str]: - key = OptionKey('ccbindir', machine=self.for_machine, lang=self.language) + key = self.form_langopt_key('ccbindir') ccbindir = options[key].value if isinstance(ccbindir, str) and ccbindir != '': return [self._shield_nvcc_list_arg('-ccbin='+ccbindir, False)] diff --git a/mesonbuild/compilers/cython.py b/mesonbuild/compilers/cython.py index 61e339bdda82..76e66c0b7ae1 100644 --- a/mesonbuild/compilers/cython.py +++ b/mesonbuild/compilers/cython.py @@ -7,7 +7,7 @@ import typing as T from .. import options -from ..mesonlib import EnvironmentException, OptionKey, version_compare +from ..mesonlib import EnvironmentException, version_compare from .compilers import Compiler if T.TYPE_CHECKING: @@ -70,12 +70,12 @@ def get_options(self) -> 'MutableKeyedOptionDictType': return self.update_options( super().get_options(), self.create_option(options.UserComboOption, - OptionKey('version', machine=self.for_machine, lang=self.language), + self.form_langopt_key('version'), 'Python version to target', ['2', '3'], '3'), self.create_option(options.UserComboOption, - OptionKey('language', machine=self.for_machine, lang=self.language), + self.form_langopt_key('language'), 'Output C or C++ files', ['c', 'cpp'], 'c'), @@ -83,9 +83,11 @@ def get_options(self) -> 'MutableKeyedOptionDictType': def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args: T.List[str] = [] - key = options[OptionKey('version', machine=self.for_machine, lang=self.language)] - args.append(f'-{key.value}') - lang = options[OptionKey('language', machine=self.for_machine, lang=self.language)] + key = self.form_langopt_key('version') + version = options[key] + args.append(f'-{version.value}') + key = self.form_langopt_key('language') + lang = options[key] if lang.value == 'cpp': args.append('--cplus') return args From 2b1510d7068747541f8d49ba876570d62fde5d0e Mon Sep 17 00:00:00 2001 From: Jussi Pakkanen Date: Sat, 8 Jun 2024 21:31:10 +0300 Subject: [PATCH 46/91] Convert option from a plain dictionary into a named class. --- mesonbuild/coredata.py | 16 +++++++-------- mesonbuild/environment.py | 16 +++++++-------- mesonbuild/options.py | 41 ++++++++++++++++++++++++++++++++++++--- 3 files changed, 54 insertions(+), 19 deletions(-) diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index 782e77081f98..e4081e30dbc8 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -58,7 +58,7 @@ class SharedCMDOptions(Protocol): OptionDictType = T.Union[T.Dict[str, 'options.UserOption[T.Any]'], 'OptionsView'] MutableKeyedOptionDictType = T.Dict['OptionKey', 'options.UserOption[T.Any]'] - KeyedOptionDictType = T.Union[MutableKeyedOptionDictType, 'OptionsView'] + KeyedOptionDictType = T.Union['options.OptionStore', MutableKeyedOptionDictType, 'OptionsView'] CompilerCheckCacheKey = T.Tuple[T.Tuple[str, ...], str, FileOrString, T.Tuple[str, ...], CompileCheckMode] # code, args RunCheckCacheKey = T.Tuple[str, T.Tuple[str, ...]] @@ -241,7 +241,7 @@ def languages(self) -> T.Set[str]: class CoreData: - def __init__(self, options: SharedCMDOptions, scratch_dir: str, meson_command: T.List[str]): + def __init__(self, cmd_options: SharedCMDOptions, scratch_dir: str, meson_command: T.List[str]): self.lang_guids = { 'default': '8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942', 'c': '8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942', @@ -255,8 +255,8 @@ def __init__(self, options: SharedCMDOptions, scratch_dir: str, meson_command: T self.meson_command = meson_command self.target_guids = {} self.version = version - self.options: 'MutableKeyedOptionDictType' = {} - self.cross_files = self.__load_config_files(options, scratch_dir, 'cross') + self.options = options.OptionStore() + self.cross_files = self.__load_config_files(cmd_options, scratch_dir, 'cross') self.compilers: PerMachine[T.Dict[str, Compiler]] = PerMachine(OrderedDict(), OrderedDict()) # Stores the (name, hash) of the options file, The name will be either @@ -282,18 +282,18 @@ def __init__(self, options: SharedCMDOptions, scratch_dir: str, meson_command: T self.cmake_cache: PerMachine[CMakeStateCache] = PerMachine(CMakeStateCache(), CMakeStateCache()) # Only to print a warning if it changes between Meson invocations. - self.config_files = self.__load_config_files(options, scratch_dir, 'native') + self.config_files = self.__load_config_files(cmd_options, scratch_dir, 'native') self.builtin_options_libdir_cross_fixup() self.init_builtins('') @staticmethod - def __load_config_files(options: SharedCMDOptions, scratch_dir: str, ftype: str) -> T.List[str]: + def __load_config_files(cmd_options: SharedCMDOptions, scratch_dir: str, ftype: str) -> T.List[str]: # Need to try and make the passed filenames absolute because when the # files are parsed later we'll have chdir()d. if ftype == 'cross': - filenames = options.cross_file + filenames = cmd_options.cross_file else: - filenames = options.native_file + filenames = cmd_options.native_file if not filenames: return [] diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index 1607c32edd90..be40dbcfd4be 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -520,7 +520,7 @@ class Environment: log_dir = 'meson-logs' info_dir = 'meson-info' - def __init__(self, source_dir: str, build_dir: str, options: coredata.SharedCMDOptions) -> None: + def __init__(self, source_dir: str, build_dir: str, cmd_options: coredata.SharedCMDOptions) -> None: self.source_dir = source_dir self.build_dir = build_dir # Do not try to create build directories when build_dir is none. @@ -536,26 +536,26 @@ def __init__(self, source_dir: str, build_dir: str, options: coredata.SharedCMDO self.coredata: coredata.CoreData = coredata.load(self.get_build_dir(), suggest_reconfigure=False) self.first_invocation = False except FileNotFoundError: - self.create_new_coredata(options) + self.create_new_coredata(cmd_options) except coredata.MesonVersionMismatchException as e: # This is routine, but tell the user the update happened mlog.log('Regenerating configuration from scratch:', str(e)) - coredata.read_cmd_line_file(self.build_dir, options) - self.create_new_coredata(options) + coredata.read_cmd_line_file(self.build_dir, cmd_options) + self.create_new_coredata(cmd_options) except MesonException as e: # If we stored previous command line options, we can recover from # a broken/outdated coredata. if os.path.isfile(coredata.get_cmd_line_file(self.build_dir)): mlog.warning('Regenerating configuration from scratch.', fatal=False) mlog.log('Reason:', mlog.red(str(e))) - coredata.read_cmd_line_file(self.build_dir, options) - self.create_new_coredata(options) + coredata.read_cmd_line_file(self.build_dir, cmd_options) + self.create_new_coredata(cmd_options) else: raise MesonException(f'{str(e)} Try regenerating using "meson setup --wipe".') else: # Just create a fresh coredata in this case self.scratch_dir = '' - self.create_new_coredata(options) + self.create_new_coredata(cmd_options) ## locally bind some unfrozen configuration @@ -627,7 +627,7 @@ def __init__(self, source_dir: str, build_dir: str, options: coredata.SharedCMDO self.cmakevars = cmakevars.default_missing() # Command line options override those from cross/native files - self.options.update(options.cmd_line_options) + self.options.update(cmd_options.cmd_line_options) # Take default value from env if not set in cross/native files or command line. self._set_default_options_from_env() diff --git a/mesonbuild/options.py b/mesonbuild/options.py index 2699dd5ce6ed..6ffcf1a278c0 100644 --- a/mesonbuild/options.py +++ b/mesonbuild/options.py @@ -25,6 +25,7 @@ from . import mlog import typing as T +from typing import ItemsView DEFAULT_YIELDING = False @@ -473,8 +474,42 @@ def add_to_argparse(self, name: str, parser: argparse.ArgumentParser, help_suffi OptionKey('purelibdir', module='python'): {}, } - class OptionStore: def __init__(self): - # This class will hold all options for a given build directory - self.dummy = None + self.d: T.Dict['OptionKey', 'UserOption[T.Any]'] = {} + + def __len__(self): + return len(self.d) + + def __setitem__(self, key, value): + self.d[key] = value + + def __getitem__(self, key): + return self.d[key] + + def __delitem__(self, key): + del self.d[key] + + def __contains__(self, key): + return key in self.d + + def __repr__(self): + return repr(self.d) + + def keys(self): + return self.d.keys() + + def values(self): + return self.d.values() + + def items(self) -> ItemsView['OptionKey', 'USerOption[T.Any]']: + return self.d.items() + + def update(self, *args, **kwargs): + return self.d.update(*args, **kwargs) + + def setdefault(self, k, o): + return self.d.setdefault(k, o) + + def get(self, *args, **kwargs)-> UserOption: + return self.d.get(*args, **kwargs) From 9694f9fefeee6ac7c729c3a520c43902e6fb76f1 Mon Sep 17 00:00:00 2001 From: Mis012 Date: Thu, 15 Feb 2024 20:21:56 +0100 Subject: [PATCH 47/91] java: use single javac invocation per jar Instead of invoking javac for every .java file, pass all of the sources for a jar target to a single javac invocation. This massively improves first compilation time and doesn't meaningfully affect incremental builds (it can even be faster in some cases). The old approach also had issues where files would not always get recompiled even though they should, necessitating a clean rebuild in order to see changes reflected in the build output. Multiple invocations seem to only make sense if: - issues with files not getting flagged for rebuild are investigated and fixed - something like the javaserver buildtool from openjdk sources is used instead of directly spawning javac processes - the amount of java files per jar is so large that it is faster to compile several files one by one than to compile all the files at once (batching may still make sense to get a reasonable balance) --- mesonbuild/backend/ninjabackend.py | 34 ++++++++++++++++++------------ 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 4d56f7d1bfa5..9d43c5ec3cd4 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -1412,7 +1412,6 @@ def generate_jar_target(self, target: build.Jar): outname_rel = os.path.join(self.get_target_dir(target), fname) src_list = target.get_sources() resources = target.get_java_resources() - class_list = [] compiler = target.compilers['java'] c = 'c' m = 'm' @@ -1430,10 +1429,8 @@ def generate_jar_target(self, target: build.Jar): if rel_src.endswith('.java'): gen_src_list.append(raw_src) - compile_args = self.determine_single_java_compile_args(target, compiler) - for src in src_list + gen_src_list: - plain_class_path = self.generate_single_java_compile(src, target, compiler, compile_args) - class_list.append(plain_class_path) + compile_args = self.determine_java_compile_args(target, compiler) + class_list = self.generate_java_compile(src_list + gen_src_list, target, compiler, compile_args) class_dep_list = [os.path.join(self.get_target_private_dir(target), i) for i in class_list] manifest_path = os.path.join(self.get_target_private_dir(target), 'META-INF', 'MANIFEST.MF') manifest_fullpath = os.path.join(self.environment.get_build_dir(), manifest_path) @@ -1530,7 +1527,8 @@ def generate_cs_target(self, target: build.BuildTarget): self.generate_generator_list_rules(target) self.create_target_source_introspection(target, compiler, commands, rel_srcs, generated_rel_srcs) - def determine_single_java_compile_args(self, target, compiler): + def determine_java_compile_args(self, target, compiler): + args = [] args = self.generate_basic_compiler_args(target, compiler) args += target.get_java_args() args += compiler.get_output_args(self.get_target_private_dir(target)) @@ -1544,20 +1542,30 @@ def determine_single_java_compile_args(self, target, compiler): args += ['-sourcepath', sourcepath] return args - def generate_single_java_compile(self, src, target, compiler, args): + def generate_java_compile(self, srcs, target, compiler, args): deps = [os.path.join(self.get_target_dir(l), l.get_filename()) for l in target.link_targets] generated_sources = self.get_target_generated_sources(target) for rel_src in generated_sources.keys(): if rel_src.endswith('.java'): deps.append(rel_src) - rel_src = src.rel_to_builddir(self.build_to_src) - plain_class_path = src.fname[:-4] + 'class' - rel_obj = os.path.join(self.get_target_private_dir(target), plain_class_path) - element = NinjaBuildElement(self.all_outputs, rel_obj, self.compiler_to_rule_name(compiler), rel_src) + + rel_srcs = [] + plain_class_paths = [] + rel_objs = [] + for src in srcs: + rel_src = src.rel_to_builddir(self.build_to_src) + rel_srcs.append(rel_src) + + plain_class_path = src.fname[:-4] + 'class' + plain_class_paths.append(plain_class_path) + rel_obj = os.path.join(self.get_target_private_dir(target), plain_class_path) + rel_objs.append(rel_obj) + element = NinjaBuildElement(self.all_outputs, rel_objs, self.compiler_to_rule_name(compiler), rel_srcs) element.add_dep(deps) element.add_item('ARGS', args) + element.add_item('FOR_JAR', self.get_target_filename(target)) self.add_build(element) - return plain_class_path + return plain_class_paths def generate_java_link(self): rule = 'java_LINKER' @@ -2376,7 +2384,7 @@ def generate_dynamic_link_rules(self): def generate_java_compile_rule(self, compiler): rule = self.compiler_to_rule_name(compiler) command = compiler.get_exelist() + ['$ARGS', '$in'] - description = 'Compiling Java object $in' + description = 'Compiling Java sources for $FOR_JAR' self.add_rule(NinjaRule(rule, command, [], description)) def generate_cs_compile_rule(self, compiler: 'CsCompiler') -> None: From 141100e482e440dd08ea8b5b042927cac968f112 Mon Sep 17 00:00:00 2001 From: Emil Melnikov Date: Mon, 10 Jun 2024 22:56:40 +0200 Subject: [PATCH 48/91] Add note about meson-python and installation path New people that want to use Meson for building Python extensions most probably will read Python module docs first. Direct them to meson-python and suggest to set `python.install_env=auto`. --- docs/markdown/Builtin-options.md | 3 ++- docs/markdown/Python-module.md | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/markdown/Builtin-options.md b/docs/markdown/Builtin-options.md index b4039d646926..6adc4218bda1 100644 --- a/docs/markdown/Builtin-options.md +++ b/docs/markdown/Builtin-options.md @@ -408,7 +408,8 @@ interpreter directly, even if it is a venv. Setting to `venv` will instead use the paths for the virtualenv the python found installation comes from (or fail if it is not a virtualenv). Setting to `auto` will check if the found installation is a virtualenv, and use `venv` or `system` as appropriate (but -never `prefix`). This option is mutually exclusive with the `platlibdir`/`purelibdir`. +never `prefix`). Note that Conda environments are treated as `system`. +This option is mutually exclusive with the `platlibdir`/`purelibdir`. For backwards compatibility purposes, the default `install_env` is `prefix`. diff --git a/docs/markdown/Python-module.md b/docs/markdown/Python-module.md index 05ae57de233a..c02eed91d596 100644 --- a/docs/markdown/Python-module.md +++ b/docs/markdown/Python-module.md @@ -12,6 +12,15 @@ authors: This module provides support for finding and building extensions against python installations, be they python 2 or 3. +If you want to build and package Python extension modules using tools +compatible with [PEP-517](https://peps.python.org/pep-0517/), check out +[meson-python](https://mesonbuild.com/meson-python/index.html). + +If you are building Python extension modules against a Python interpreter +located in a venv or Conda environment, you probably want to set +`python.install_venv=auto`; +see [Python module options](Builtin-options.md#python-module) for details. + *Added 0.46.0* ## Functions From 1b36bf3f3c9f9f6f4fda84c072c6effa28c5e4f4 Mon Sep 17 00:00:00 2001 From: John Wiele Date: Tue, 11 Jun 2024 11:36:22 -0400 Subject: [PATCH 49/91] Add help for meson format to the man page. The added help text was derived from the output of the meson help command much like the markdown online help is derived. --- man/meson.1 | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/man/meson.1 b/man/meson.1 index 885eeb33a467..10a5eb9a443e 100644 --- a/man/meson.1 +++ b/man/meson.1 @@ -608,6 +608,53 @@ Define cpu family for cross compilation. \fB\-\-endian {big,little}\fR Define endianness for cross compilation. +.SH The format command + +.B meson format +formats a meson source file. + +.B meson format [ +.I options +.B ] [ +.I sources... +.B ] + +.SS "positional arguments:" + +.TP +\fBsources...\fR +meson source files + +.SS "options:" + +.TP +\fB-h, --help\fR +show this help message and exit + +.TP +\fB-q, --check-only\fR +exit with 1 if files would be modified by meson format + +.TP +\fB-i, --inplace\fR +format files in-place + +.TP +\fB-r, --recursive\fR +recurse subdirs (requires --check-only or --inplace option) + +.TP +\fB-c meson.format, --configuration meson.format\fR +read configuration from meson.format + +.TP +\fB-e, --editor-config\fR +try to read configuration from .editorconfig + +.TP +\fB-o OUTPUT, --output OUTPUT\fR +output file (implies having exactly one input) + .SH EXIT STATUS .TP From d1abdce88fa263b6e532901564e44ca510df1065 Mon Sep 17 00:00:00 2001 From: Andrew McNulty Date: Wed, 8 May 2024 14:51:25 +0200 Subject: [PATCH 50/91] Python: add load test to limited API test Based on the example in GH issue #13167, the limited API test has been extended with a test to load the compiled module to ensure it can be loaded correctly. --- .../python/9 extmodule limited api/limited.c | 14 ++++++++++++-- .../python/9 extmodule limited api/meson.build | 7 +++++++ .../python/9 extmodule limited api/test_limited.py | 6 ++++++ 3 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 test cases/python/9 extmodule limited api/test_limited.py diff --git a/test cases/python/9 extmodule limited api/limited.c b/test cases/python/9 extmodule limited api/limited.c index 0d1c718200ba..b977419ca0f5 100644 --- a/test cases/python/9 extmodule limited api/limited.c +++ b/test cases/python/9 extmodule limited api/limited.c @@ -6,12 +6,22 @@ #error Wrong value for Py_LIMITED_API #endif +static PyObject * +hello(PyObject * Py_UNUSED(self), PyObject * Py_UNUSED(args)) { + return PyUnicode_FromString("hello world"); +} + +static struct PyMethodDef methods[] = { + { "hello", hello, METH_NOARGS, NULL }, + { NULL, NULL, 0, NULL }, +}; + static struct PyModuleDef limited_module = { PyModuleDef_HEAD_INIT, - "limited_api_test", + "limited", NULL, -1, - NULL + methods }; PyMODINIT_FUNC PyInit_limited(void) { diff --git a/test cases/python/9 extmodule limited api/meson.build b/test cases/python/9 extmodule limited api/meson.build index 68afc96996cb..bdf1b7b904f1 100644 --- a/test cases/python/9 extmodule limited api/meson.build +++ b/test cases/python/9 extmodule limited api/meson.build @@ -14,3 +14,10 @@ ext_mod = py.extension_module('not_limited', 'not_limited.c', install: true, ) + +test('load-test', + py, + args: [files('test_limited.py')], + env: { 'PYTHONPATH': meson.current_build_dir() }, + workdir: meson.current_source_dir() +) diff --git a/test cases/python/9 extmodule limited api/test_limited.py b/test cases/python/9 extmodule limited api/test_limited.py new file mode 100644 index 000000000000..fcbf67b536e1 --- /dev/null +++ b/test cases/python/9 extmodule limited api/test_limited.py @@ -0,0 +1,6 @@ +from limited import hello + +def test_hello(): + assert hello() == "hello world" + +test_hello() From 4023bbfc3655cd9eba80021854ade9a0fcc97d11 Mon Sep 17 00:00:00 2001 From: Andrew McNulty Date: Wed, 8 May 2024 16:05:51 +0200 Subject: [PATCH 51/91] unittests: Add Python unittest for limited API This new unittest runs the existing Python limited API test case, and checks that the resulting library was linked to the correct library. --- unittests/pythontests.py | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/unittests/pythontests.py b/unittests/pythontests.py index 6079bd587681..aaea906ea829 100644 --- a/unittests/pythontests.py +++ b/unittests/pythontests.py @@ -11,7 +11,7 @@ from .baseplatformtests import BasePlatformTests from .helpers import * -from mesonbuild.mesonlib import MachineChoice, TemporaryDirectoryWinProof +from mesonbuild.mesonlib import MachineChoice, TemporaryDirectoryWinProof, is_windows from mesonbuild.modules.python import PythonModule class PythonTests(BasePlatformTests): @@ -86,3 +86,32 @@ def test_bytecompile_single(self): if shutil.which('python2') or PythonModule._get_win_pythonpath('python2'): raise self.skipTest('python2 installed, already tested') self._test_bytecompile() + + def test_limited_api_linked_correct_lib(self): + if not is_windows(): + return self.skipTest('Test only run on Windows.') + + testdir = os.path.join(self.src_root, 'test cases', 'python', '9 extmodule limited api') + + self.init(testdir) + self.build() + + from importlib.machinery import EXTENSION_SUFFIXES + limited_suffix = EXTENSION_SUFFIXES[1] + + limited_library_path = os.path.join(self.builddir, f'limited{limited_suffix}') + self.assertPathExists(limited_library_path) + + limited_dep_name = 'python3.dll' + if shutil.which('dumpbin'): + # MSVC + output = subprocess.check_output(['dumpbin', '/DEPENDENTS', limited_library_path], + stderr=subprocess.STDOUT) + self.assertIn(limited_dep_name, output.decode()) + elif shutil.which('objdump'): + # mingw + output = subprocess.check_output(['objdump', '-p', limited_library_path], + stderr=subprocess.STDOUT) + self.assertIn(limited_dep_name, output.decode()) + else: + raise self.skipTest('Test needs either dumpbin(MSVC) or objdump(mingw).') From f66a527a7c4280c1652f2921912d149aeeca971d Mon Sep 17 00:00:00 2001 From: Andrew McNulty Date: Wed, 8 May 2024 19:01:29 +0200 Subject: [PATCH 52/91] Python: move Windows functions to dependency base This is in preparation for a future commit which makes it possible for a PythonPkgConfigDependency to be used in a context where previously only a PythonSystemDependency would be used. --- mesonbuild/dependencies/python.py | 125 +++++++++++++++--------------- 1 file changed, 62 insertions(+), 63 deletions(-) diff --git a/mesonbuild/dependencies/python.py b/mesonbuild/dependencies/python.py index a0a22c1a54ad..56dddd932a29 100644 --- a/mesonbuild/dependencies/python.py +++ b/mesonbuild/dependencies/python.py @@ -161,69 +161,6 @@ def __init__(self, python_holder: 'BasicPythonExternalProgram', embed: bool): else: self.major_version = 2 - -class PythonPkgConfigDependency(PkgConfigDependency, _PythonDependencyBase): - - def __init__(self, name: str, environment: 'Environment', - kwargs: T.Dict[str, T.Any], installation: 'BasicPythonExternalProgram', - libpc: bool = False): - if libpc: - mlog.debug(f'Searching for {name!r} via pkgconfig lookup in LIBPC') - else: - mlog.debug(f'Searching for {name!r} via fallback pkgconfig lookup in default paths') - - PkgConfigDependency.__init__(self, name, environment, kwargs) - _PythonDependencyBase.__init__(self, installation, kwargs.get('embed', False)) - - if libpc and not self.is_found: - mlog.debug(f'"python-{self.version}" could not be found in LIBPC, this is likely due to a relocated python installation') - - # pkg-config files are usually accurate starting with python 3.8 - if not self.link_libpython and mesonlib.version_compare(self.version, '< 3.8'): - self.link_args = [] - - -class PythonFrameworkDependency(ExtraFrameworkDependency, _PythonDependencyBase): - - def __init__(self, name: str, environment: 'Environment', - kwargs: T.Dict[str, T.Any], installation: 'BasicPythonExternalProgram'): - ExtraFrameworkDependency.__init__(self, name, environment, kwargs) - _PythonDependencyBase.__init__(self, installation, kwargs.get('embed', False)) - - -class PythonSystemDependency(SystemDependency, _PythonDependencyBase): - - def __init__(self, name: str, environment: 'Environment', - kwargs: T.Dict[str, T.Any], installation: 'BasicPythonExternalProgram'): - SystemDependency.__init__(self, name, environment, kwargs) - _PythonDependencyBase.__init__(self, installation, kwargs.get('embed', False)) - - # match pkg-config behavior - if self.link_libpython: - # link args - if mesonlib.is_windows(): - self.find_libpy_windows(environment, limited_api=False) - else: - self.find_libpy(environment) - else: - self.is_found = True - - # compile args - inc_paths = mesonlib.OrderedSet([ - self.variables.get('INCLUDEPY'), - self.paths.get('include'), - self.paths.get('platinclude')]) - - self.compile_args += ['-I' + path for path in inc_paths if path] - - # https://sourceforge.net/p/mingw-w64/mailman/message/30504611/ - # https://github.com/python/cpython/pull/100137 - if mesonlib.is_windows() and self.get_windows_python_arch().endswith('64') and mesonlib.version_compare(self.version, '<3.12'): - self.compile_args += ['-DMS_WIN64='] - - if not self.clib_compiler.has_header('Python.h', '', environment, extra_args=self.compile_args)[0]: - self.is_found = False - def find_libpy(self, environment: 'Environment') -> None: if self.is_pypy: if self.major_version == 3: @@ -347,6 +284,68 @@ def find_libpy_windows(self, env: 'Environment', limited_api: bool = False) -> N self.link_args = largs self.is_found = True +class PythonPkgConfigDependency(PkgConfigDependency, _PythonDependencyBase): + + def __init__(self, name: str, environment: 'Environment', + kwargs: T.Dict[str, T.Any], installation: 'BasicPythonExternalProgram', + libpc: bool = False): + if libpc: + mlog.debug(f'Searching for {name!r} via pkgconfig lookup in LIBPC') + else: + mlog.debug(f'Searching for {name!r} via fallback pkgconfig lookup in default paths') + + PkgConfigDependency.__init__(self, name, environment, kwargs) + _PythonDependencyBase.__init__(self, installation, kwargs.get('embed', False)) + + if libpc and not self.is_found: + mlog.debug(f'"python-{self.version}" could not be found in LIBPC, this is likely due to a relocated python installation') + + # pkg-config files are usually accurate starting with python 3.8 + if not self.link_libpython and mesonlib.version_compare(self.version, '< 3.8'): + self.link_args = [] + + +class PythonFrameworkDependency(ExtraFrameworkDependency, _PythonDependencyBase): + + def __init__(self, name: str, environment: 'Environment', + kwargs: T.Dict[str, T.Any], installation: 'BasicPythonExternalProgram'): + ExtraFrameworkDependency.__init__(self, name, environment, kwargs) + _PythonDependencyBase.__init__(self, installation, kwargs.get('embed', False)) + + +class PythonSystemDependency(SystemDependency, _PythonDependencyBase): + + def __init__(self, name: str, environment: 'Environment', + kwargs: T.Dict[str, T.Any], installation: 'BasicPythonExternalProgram'): + SystemDependency.__init__(self, name, environment, kwargs) + _PythonDependencyBase.__init__(self, installation, kwargs.get('embed', False)) + + # match pkg-config behavior + if self.link_libpython: + # link args + if mesonlib.is_windows(): + self.find_libpy_windows(environment, limited_api=False) + else: + self.find_libpy(environment) + else: + self.is_found = True + + # compile args + inc_paths = mesonlib.OrderedSet([ + self.variables.get('INCLUDEPY'), + self.paths.get('include'), + self.paths.get('platinclude')]) + + self.compile_args += ['-I' + path for path in inc_paths if path] + + # https://sourceforge.net/p/mingw-w64/mailman/message/30504611/ + # https://github.com/python/cpython/pull/100137 + if mesonlib.is_windows() and self.get_windows_python_arch().endswith('64') and mesonlib.version_compare(self.version, '<3.12'): + self.compile_args += ['-DMS_WIN64='] + + if not self.clib_compiler.has_header('Python.h', '', environment, extra_args=self.compile_args)[0]: + self.is_found = False + @staticmethod def log_tried() -> str: return 'sysconfig' From fea7f94b6796e4ad296989c040d45ae3b4c3f444 Mon Sep 17 00:00:00 2001 From: Andrew McNulty Date: Wed, 8 May 2024 18:40:58 +0200 Subject: [PATCH 53/91] Python: fix limited API logic under mingw The Python Limited API support that was added in 1.2 had special handling of Windows, but the condition to check for Windows was not correct: it checked for MSVC and not for the target's OS. This causes mingw installations to not have the special handling applied. This commit fixes this to check explicitly for Windows. --- mesonbuild/modules/python.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/mesonbuild/modules/python.py b/mesonbuild/modules/python.py index d195a3fa5291..e84bee1872d4 100644 --- a/mesonbuild/modules/python.py +++ b/mesonbuild/modules/python.py @@ -184,13 +184,9 @@ def extension_module_method(self, args: T.Tuple[str, T.List[BuildTargetSource]], new_cpp_args.append(limited_api_definition) kwargs['cpp_args'] = new_cpp_args - # When compiled under MSVC, Python's PC/pyconfig.h forcibly inserts pythonMAJOR.MINOR.lib - # into the linker path when not running in debug mode via a series #pragma comment(lib, "") - # directives. We manually override these here as this interferes with the intended - # use of the 'limited_api' kwarg + # On Windows, the limited API DLL is python3.dll, not python3X.dll. for_machine = kwargs['native'] - compilers = self.interpreter.environment.coredata.compilers[for_machine] - if any(compiler.get_id() == 'msvc' for compiler in compilers.values()): + if self.interpreter.environment.machines[for_machine].is_windows(): pydep_copy = copy.copy(pydep) pydep_copy.find_libpy_windows(self.env, limited_api=True) if not pydep_copy.found(): @@ -199,6 +195,12 @@ def extension_module_method(self, args: T.Tuple[str, T.List[BuildTargetSource]], new_deps.remove(pydep) new_deps.append(pydep_copy) + # When compiled under MSVC, Python's PC/pyconfig.h forcibly inserts pythonMAJOR.MINOR.lib + # into the linker path when not running in debug mode via a series #pragma comment(lib, "") + # directives. We manually override these here as this interferes with the intended + # use of the 'limited_api' kwarg + compilers = self.interpreter.environment.coredata.compilers[for_machine] + if any(compiler.get_id() == 'msvc' for compiler in compilers.values()): pyver = pydep.version.replace('.', '') python_windows_debug_link_exception = f'/NODEFAULTLIB:python{pyver}_d.lib' python_windows_release_link_exception = f'/NODEFAULTLIB:python{pyver}.lib' From 328011f77a1cef00655a697cd2e503a97754b745 Mon Sep 17 00:00:00 2001 From: Andrew McNulty Date: Wed, 8 May 2024 19:05:47 +0200 Subject: [PATCH 54/91] Python: link correct limited API lib on mingw This commit fixes GH issue #13167 by linking to the correct library under MINGW when the 'limited_api' kwarg is specified. --- mesonbuild/dependencies/python.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/mesonbuild/dependencies/python.py b/mesonbuild/dependencies/python.py index 56dddd932a29..a74423cb7194 100644 --- a/mesonbuild/dependencies/python.py +++ b/mesonbuild/dependencies/python.py @@ -248,9 +248,15 @@ def get_windows_link_args(self, limited_api: bool) -> T.Optional[T.List[str]]: lib = Path(self.variables.get('base_prefix')) / libpath elif self.platform.startswith('mingw'): if self.static: - libname = self.variables.get('LIBRARY') + if limited_api: + libname = self.variables.get('ABI3DLLLIBRARY') + else: + libname = self.variables.get('LIBRARY') else: - libname = self.variables.get('LDLIBRARY') + if limited_api: + libname = self.variables.get('ABI3LDLIBRARY') + else: + libname = self.variables.get('LDLIBRARY') lib = Path(self.variables.get('LIBDIR')) / libname else: raise mesonlib.MesonBugException( From c0d86024f5e647858f88bc400b45b3ad88a25c97 Mon Sep 17 00:00:00 2001 From: Jussi Pakkanen Date: Sat, 8 Jun 2024 23:16:36 +0300 Subject: [PATCH 55/91] Rename option variable to optstore to make it unique. --- mesonbuild/ast/introspection.py | 2 +- mesonbuild/backend/ninjabackend.py | 16 ++-- mesonbuild/backend/vs2010backend.py | 2 +- mesonbuild/build.py | 10 +-- mesonbuild/cmake/common.py | 4 +- mesonbuild/cmake/executor.py | 2 +- mesonbuild/cmake/interpreter.py | 2 +- mesonbuild/compilers/cuda.py | 2 +- mesonbuild/compilers/mixins/clike.py | 4 +- mesonbuild/compilers/mixins/emscripten.py | 2 +- mesonbuild/coredata.py | 94 ++++++++++---------- mesonbuild/dependencies/boost.py | 4 +- mesonbuild/dependencies/pkgconfig.py | 4 +- mesonbuild/dependencies/python.py | 4 +- mesonbuild/dependencies/qt.py | 4 +- mesonbuild/interpreter/compiler.py | 2 +- mesonbuild/interpreter/interpreter.py | 22 ++--- mesonbuild/interpreter/interpreterobjects.py | 2 +- mesonbuild/mconf.py | 16 ++-- mesonbuild/mintro.py | 10 +-- mesonbuild/modules/gnome.py | 2 +- mesonbuild/modules/python.py | 2 +- mesonbuild/modules/rust.py | 2 +- mesonbuild/msetup.py | 4 +- mesonbuild/rewriter.py | 10 +-- run_tests.py | 2 +- unittests/allplatformstests.py | 60 ++++++------- unittests/datatests.py | 6 +- unittests/internaltests.py | 2 +- unittests/linuxliketests.py | 2 +- 30 files changed, 150 insertions(+), 150 deletions(-) diff --git a/mesonbuild/ast/introspection.py b/mesonbuild/ast/introspection.py index c7dcf73b1443..1d7dd0510ae2 100644 --- a/mesonbuild/ast/introspection.py +++ b/mesonbuild/ast/introspection.py @@ -182,7 +182,7 @@ def _add_languages(self, raw_langs: T.List[TYPE_var], required: bool, for_machin if self.subproject: options = {} for k in comp.get_options(): - v = copy.copy(self.coredata.options[k]) + v = copy.copy(self.coredata.optstore[k]) k = k.evolve(subproject=self.subproject) options[k] = v self.coredata.add_compiler_options(options, lang, for_machine, self.environment, self.subproject) diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 4d56f7d1bfa5..debde7be9d2b 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -623,7 +623,7 @@ def generate(self, capture: bool = False, vslite_ctx: T.Optional[T.Dict] = None) outfile.write('# Do not edit by hand.\n\n') outfile.write('ninja_required_version = 1.8.2\n\n') - num_pools = self.environment.coredata.options[OptionKey('backend_max_links')].value + num_pools = self.environment.coredata.optstore[OptionKey('backend_max_links')].value if num_pools > 0: outfile.write(f'''pool link_pool depth = {num_pools} @@ -656,8 +656,8 @@ def generate(self, capture: bool = False, vslite_ctx: T.Optional[T.Dict] = None) self.generate_dist() mlog.log_timestamp("Dist generated") key = OptionKey('b_coverage') - if (key in self.environment.coredata.options and - self.environment.coredata.options[key].value): + if (key in self.environment.coredata.optstore and + self.environment.coredata.optstore[key].value): gcovr_exe, gcovr_version, lcov_exe, lcov_version, genhtml_exe, llvm_cov_exe = environment.find_coverage_tools(self.environment.coredata) mlog.debug(f'Using {gcovr_exe} ({gcovr_version}), {lcov_exe} and {llvm_cov_exe} for code coverage') if gcovr_exe or (lcov_exe and genhtml_exe): @@ -2287,7 +2287,7 @@ def _rsp_options(self, tool: T.Union['Compiler', 'StaticLinker', 'DynamicLinker' return options def generate_static_link_rules(self): - num_pools = self.environment.coredata.options[OptionKey('backend_max_links')].value + num_pools = self.environment.coredata.optstore[OptionKey('backend_max_links')].value if 'java' in self.environment.coredata.compilers.host: self.generate_java_link() for for_machine in MachineChoice: @@ -2335,7 +2335,7 @@ def generate_static_link_rules(self): self.add_rule(NinjaRule(rule, cmdlist, args, description, **options, extra=pool)) def generate_dynamic_link_rules(self): - num_pools = self.environment.coredata.options[OptionKey('backend_max_links')].value + num_pools = self.environment.coredata.optstore[OptionKey('backend_max_links')].value for for_machine in MachineChoice: complist = self.environment.coredata.compilers[for_machine] for langname, compiler in complist.items(): @@ -3597,7 +3597,7 @@ def generate_gcov_clean(self) -> None: def get_user_option_args(self): cmds = [] - for (k, v) in self.environment.coredata.options.items(): + for k, v in self.environment.coredata.optstore.items(): if k.is_project(): cmds.append('-D' + str(k) + '=' + (v.value if isinstance(v.value, str) else str(v.value).lower())) # The order of these arguments must be the same between runs of Meson @@ -3726,8 +3726,8 @@ def generate_ending(self) -> None: if ctlist: elem.add_dep(self.generate_custom_target_clean(ctlist)) - if OptionKey('b_coverage') in self.environment.coredata.options and \ - self.environment.coredata.options[OptionKey('b_coverage')].value: + if OptionKey('b_coverage') in self.environment.coredata.optstore and \ + self.environment.coredata.optstore[OptionKey('b_coverage')].value: self.generate_gcov_clean() elem.add_dep('clean-gcda') elem.add_dep('clean-gcno') diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py index 91275c77e7b9..b2b8d8764883 100644 --- a/mesonbuild/backend/vs2010backend.py +++ b/mesonbuild/backend/vs2010backend.py @@ -532,7 +532,7 @@ def generate_solution(self, sln_filename: str, projlist: T.List[Project]) -> Non replace_if_different(sln_filename, sln_filename_tmp) def generate_projects(self, vslite_ctx: dict = None) -> T.List[Project]: - startup_project = self.environment.coredata.options[OptionKey('backend_startup_project')].value + startup_project = self.environment.coredata.optstore[OptionKey('backend_startup_project')].value projlist: T.List[Project] = [] startup_idx = 0 for (i, (name, target)) in enumerate(self.build.targets.items()): diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 313f73e00200..517845c14f1c 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -533,7 +533,7 @@ def __post_init__(self, overrides: T.Optional[T.Dict[OptionKey, str]]) -> None: for k, v in overrides.items()} else: ovr = {} - self.options = coredata.OptionsView(self.environment.coredata.options, self.subproject, ovr) + self.options = coredata.OptionsView(self.environment.coredata.optstore, self.subproject, ovr) # XXX: this should happen in the interpreter if has_path_sep(self.name): # Fix failing test 53 when this becomes an error. @@ -1242,8 +1242,8 @@ def _extract_pic_pie(self, kwargs: T.Dict[str, T.Any], arg: str, option: str) -> k = OptionKey(option) if kwargs.get(arg) is not None: val = T.cast('bool', kwargs[arg]) - elif k in self.environment.coredata.options: - val = self.environment.coredata.options[k].value + elif k in self.environment.coredata.optstore: + val = self.environment.coredata.optstore[k].value else: val = False @@ -1930,8 +1930,8 @@ def __init__( compilers: T.Dict[str, 'Compiler'], kwargs): key = OptionKey('b_pie') - if 'pie' not in kwargs and key in environment.coredata.options: - kwargs['pie'] = environment.coredata.options[key].value + if 'pie' not in kwargs and key in environment.coredata.optstore: + kwargs['pie'] = environment.coredata.optstore[key].value super().__init__(name, subdir, subproject, for_machine, sources, structured_sources, objects, environment, compilers, kwargs) self.win_subsystem = kwargs.get('win_subsystem') or 'console' diff --git a/mesonbuild/cmake/common.py b/mesonbuild/cmake/common.py index f117a9f6b562..02648195de99 100644 --- a/mesonbuild/cmake/common.py +++ b/mesonbuild/cmake/common.py @@ -51,9 +51,9 @@ ] def cmake_is_debug(env: 'Environment') -> bool: - if OptionKey('b_vscrt') in env.coredata.options: + if OptionKey('b_vscrt') in env.coredata.optstore: is_debug = env.coredata.get_option(OptionKey('buildtype')) == 'debug' - if env.coredata.options[OptionKey('b_vscrt')].value in {'mdd', 'mtd'}: + if env.coredata.optstore[OptionKey('b_vscrt')].value in {'mdd', 'mtd'}: is_debug = True return is_debug else: diff --git a/mesonbuild/cmake/executor.py b/mesonbuild/cmake/executor.py index dd43cc04ac17..a8850d6fe00b 100644 --- a/mesonbuild/cmake/executor.py +++ b/mesonbuild/cmake/executor.py @@ -51,7 +51,7 @@ def __init__(self, environment: 'Environment', version: str, for_machine: Machin self.cmakebin = None return - self.prefix_paths = self.environment.coredata.options[OptionKey('cmake_prefix_path', machine=self.for_machine)].value + self.prefix_paths = self.environment.coredata.optstore[OptionKey('cmake_prefix_path', machine=self.for_machine)].value if self.prefix_paths: self.extra_cmake_args += ['-DCMAKE_PREFIX_PATH={}'.format(';'.join(self.prefix_paths))] diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py index 4c42dbb095f3..57354d32c243 100644 --- a/mesonbuild/cmake/interpreter.py +++ b/mesonbuild/cmake/interpreter.py @@ -532,7 +532,7 @@ def _all_source_suffixes(self) -> 'ImmutableListProtocol[str]': @lru_cache(maxsize=None) def _all_lang_stds(self, lang: str) -> 'ImmutableListProtocol[str]': try: - res = self.env.coredata.options[OptionKey('std', machine=MachineChoice.BUILD, lang=lang)].choices + res = self.env.coredata.optstore[OptionKey('std', machine=MachineChoice.BUILD, lang=lang)].choices except KeyError: return [] diff --git a/mesonbuild/compilers/cuda.py b/mesonbuild/compilers/cuda.py index 954040e1e037..5a935515d74d 100644 --- a/mesonbuild/compilers/cuda.py +++ b/mesonbuild/compilers/cuda.py @@ -549,7 +549,7 @@ def sanity_check(self, work_dir: str, env: 'Environment') -> None: # Use the -ccbin option, if available, even during sanity checking. # Otherwise, on systems where CUDA does not support the default compiler, # NVCC becomes unusable. - flags += self.get_ccbin_args(env.coredata.options) + flags += self.get_ccbin_args(env.coredata.optstore) # If cross-compiling, we can't run the sanity check, only compile it. if env.need_exe_wrapper(self.for_machine) and not env.has_exe_wrapper(): diff --git a/mesonbuild/compilers/mixins/clike.py b/mesonbuild/compilers/mixins/clike.py index d273015bcae7..87cb81997e7d 100644 --- a/mesonbuild/compilers/mixins/clike.py +++ b/mesonbuild/compilers/mixins/clike.py @@ -376,8 +376,8 @@ def _get_basic_compiler_args(self, env: 'Environment', mode: CompileCheckMode) - # linking with static libraries since MSVC won't select a CRT for # us in that case and will error out asking us to pick one. try: - crt_val = env.coredata.options[OptionKey('b_vscrt')].value - buildtype = env.coredata.options[OptionKey('buildtype')].value + crt_val = env.coredata.optstore[OptionKey('b_vscrt')].value + buildtype = env.coredata.optstore[OptionKey('buildtype')].value cargs += self.get_crt_compile_args(crt_val, buildtype) except (KeyError, AttributeError): pass diff --git a/mesonbuild/compilers/mixins/emscripten.py b/mesonbuild/compilers/mixins/emscripten.py index 110dbc6050f4..8d3dc95bd88f 100644 --- a/mesonbuild/compilers/mixins/emscripten.py +++ b/mesonbuild/compilers/mixins/emscripten.py @@ -51,7 +51,7 @@ def _get_compile_output(self, dirname: str, mode: CompileCheckMode) -> str: def thread_link_flags(self, env: 'Environment') -> T.List[str]: args = ['-pthread'] - count: int = env.coredata.options[OptionKey('thread_count', lang=self.language, machine=self.for_machine)].value + count: int = env.coredata.optstore[OptionKey('thread_count', lang=self.language, machine=self.for_machine)].value if count: args.append(f'-sPTHREAD_POOL_SIZE={count}') return args diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index e4081e30dbc8..8804547e18fa 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -255,7 +255,7 @@ def __init__(self, cmd_options: SharedCMDOptions, scratch_dir: str, meson_comman self.meson_command = meson_command self.target_guids = {} self.version = version - self.options = options.OptionStore() + self.optstore = options.OptionStore() self.cross_files = self.__load_config_files(cmd_options, scratch_dir, 'cross') self.compilers: PerMachine[T.Dict[str, Compiler]] = PerMachine(OrderedDict(), OrderedDict()) @@ -272,8 +272,8 @@ def __init__(self, cmd_options: SharedCMDOptions, scratch_dir: str, meson_comman # For host == build configurations these caches should be the same. self.deps: PerMachine[DependencyCache] = PerMachineDefaultable.default( self.is_cross_build(), - DependencyCache(self.options, MachineChoice.BUILD), - DependencyCache(self.options, MachineChoice.HOST)) + DependencyCache(self.optstore, MachineChoice.BUILD), + DependencyCache(self.optstore, MachineChoice.HOST)) self.compiler_check_cache: T.Dict['CompilerCheckCacheKey', 'CompileResult'] = OrderedDict() self.run_check_cache: T.Dict['RunCheckCacheKey', 'RunResult'] = OrderedDict() @@ -403,10 +403,10 @@ def sanitize_dir_option_value(self, prefix: str, option: OptionKey, value: T.Any def init_builtins(self, subproject: str) -> None: # Create builtin options with default values for key, opt in options.BUILTIN_OPTIONS.items(): - self.add_builtin_option(self.options, key.evolve(subproject=subproject), opt) + self.add_builtin_option(self.optstore, key.evolve(subproject=subproject), opt) for for_machine in iter(MachineChoice): for key, opt in options.BUILTIN_OPTIONS_PER_MACHINE.items(): - self.add_builtin_option(self.options, key.evolve(subproject=subproject, machine=for_machine), opt) + self.add_builtin_option(self.optstore, key.evolve(subproject=subproject, machine=for_machine), opt) @staticmethod def add_builtin_option(opts_map: 'MutableKeyedOptionDictType', key: OptionKey, @@ -422,26 +422,26 @@ def add_builtin_option(opts_map: 'MutableKeyedOptionDictType', key: OptionKey, def init_backend_options(self, backend_name: str) -> None: if backend_name == 'ninja': - self.options[OptionKey('backend_max_links')] = options.UserIntegerOption( + self.optstore[OptionKey('backend_max_links')] = options.UserIntegerOption( 'backend_max_links', 'Maximum number of linker processes to run or 0 for no ' 'limit', (0, None, 0)) elif backend_name.startswith('vs'): - self.options[OptionKey('backend_startup_project')] = options.UserStringOption( + self.optstore[OptionKey('backend_startup_project')] = options.UserStringOption( 'backend_startup_project', 'Default project to execute in Visual Studio', '') def get_option(self, key: OptionKey) -> T.Union[T.List[str], str, int, bool]: try: - v = self.options[key].value + v = self.optstore[key].value return v except KeyError: pass try: - v = self.options[key.as_root()] + v = self.optstore[key.as_root()] if v.yielding: return v.value except KeyError: @@ -455,11 +455,11 @@ def set_option(self, key: OptionKey, value, first_invocation: bool = False) -> b if key.name == 'prefix': value = self.sanitize_prefix(value) else: - prefix = self.options[OptionKey('prefix')].value + prefix = self.optstore[OptionKey('prefix')].value value = self.sanitize_dir_option_value(prefix, key, value) try: - opt = self.options[key] + opt = self.optstore[key] except KeyError: raise MesonException(f'Tried to set unknown builtin option {str(key)}') @@ -510,7 +510,7 @@ def clear_cache(self) -> None: def get_nondefault_buildtype_args(self) -> T.List[T.Union[T.Tuple[str, str, str], T.Tuple[str, bool, bool]]]: result: T.List[T.Union[T.Tuple[str, str, str], T.Tuple[str, bool, bool]]] = [] - value = self.options[OptionKey('buildtype')].value + value = self.optstore[OptionKey('buildtype')].value if value == 'plain': opt = 'plain' debug = False @@ -529,8 +529,8 @@ def get_nondefault_buildtype_args(self) -> T.List[T.Union[T.Tuple[str, str, str] else: assert value == 'custom' return [] - actual_opt = self.options[OptionKey('optimization')].value - actual_debug = self.options[OptionKey('debug')].value + actual_opt = self.optstore[OptionKey('optimization')].value + actual_debug = self.optstore[OptionKey('debug')].value if actual_opt != opt: result.append(('optimization', actual_opt, opt)) if actual_debug != debug: @@ -559,8 +559,8 @@ def _set_others_from_buildtype(self, value: str) -> bool: assert value == 'custom' return False - dirty |= self.options[OptionKey('optimization')].set_value(opt) - dirty |= self.options[OptionKey('debug')].set_value(debug) + dirty |= self.optstore[OptionKey('optimization')].set_value(opt) + dirty |= self.optstore[OptionKey('debug')].set_value(debug) return dirty @@ -572,30 +572,30 @@ def is_per_machine_option(optname: OptionKey) -> bool: def get_external_args(self, for_machine: MachineChoice, lang: str) -> T.List[str]: # mypy cannot analyze type of OptionKey - return T.cast('T.List[str]', self.options[OptionKey('args', machine=for_machine, lang=lang)].value) + return T.cast('T.List[str]', self.optstore[OptionKey('args', machine=for_machine, lang=lang)].value) def get_external_link_args(self, for_machine: MachineChoice, lang: str) -> T.List[str]: # mypy cannot analyze type of OptionKey - return T.cast('T.List[str]', self.options[OptionKey('link_args', machine=for_machine, lang=lang)].value) + return T.cast('T.List[str]', self.optstore[OptionKey('link_args', machine=for_machine, lang=lang)].value) def update_project_options(self, options: 'MutableKeyedOptionDictType', subproject: SubProject) -> None: for key, value in options.items(): if not key.is_project(): continue - if key not in self.options: - self.options[key] = value + if key not in self.optstore: + self.optstore[key] = value continue if key.subproject != subproject: raise MesonBugException(f'Tried to set an option for subproject {key.subproject} from {subproject}!') - oldval = self.options[key] + oldval = self.optstore[key] if type(oldval) is not type(value): - self.options[key] = value + self.optstore[key] = value elif oldval.choices != value.choices: # If the choices have changed, use the new value, but attempt # to keep the old options. If they are not valid keep the new # defaults but warn. - self.options[key] = value + self.optstore[key] = value try: value.set_value(oldval.value) except MesonException: @@ -603,9 +603,9 @@ def update_project_options(self, options: 'MutableKeyedOptionDictType', subproje fatal=False) # Find any extranious keys for this project and remove them - for key in self.options.keys() - options.keys(): + for key in self.optstore.keys() - options.keys(): if key.is_project() and key.subproject == subproject: - del self.options[key] + del self.optstore[key] def is_cross_build(self, when_building_for: MachineChoice = MachineChoice.HOST) -> bool: if when_building_for == MachineChoice.BUILD: @@ -616,13 +616,13 @@ def copy_build_options_from_regular_ones(self) -> bool: dirty = False assert not self.is_cross_build() for k in options.BUILTIN_OPTIONS_PER_MACHINE: - o = self.options[k] - dirty |= self.options[k.as_build()].set_value(o.value) - for bk, bv in self.options.items(): + o = self.optstore[k] + dirty |= self.optstore[k.as_build()].set_value(o.value) + for bk, bv in self.optstore.items(): if bk.machine is MachineChoice.BUILD: hk = bk.as_host() try: - hv = self.options[hk] + hv = self.optstore[hk] dirty |= bv.set_value(hv.value) except KeyError: continue @@ -637,16 +637,16 @@ def set_options(self, opts_to_set: T.Dict[OptionKey, T.Any], subproject: str = ' pfk = OptionKey('prefix') if pfk in opts_to_set: prefix = self.sanitize_prefix(opts_to_set[pfk]) - dirty |= self.options[OptionKey('prefix')].set_value(prefix) + dirty |= self.optstore[OptionKey('prefix')].set_value(prefix) for key in options.BUILTIN_DIR_NOPREFIX_OPTIONS: if key not in opts_to_set: - dirty |= self.options[key].set_value(options.BUILTIN_OPTIONS[key].prefixed_default(key, prefix)) + dirty |= self.optstore[key].set_value(options.BUILTIN_OPTIONS[key].prefixed_default(key, prefix)) unknown_options: T.List[OptionKey] = [] for k, v in opts_to_set.items(): if k == pfk: continue - elif k in self.options: + elif k in self.optstore: dirty |= self.set_option(k, v, first_invocation) elif k.machine != MachineChoice.BUILD and k.type != OptionType.COMPILER: unknown_options.append(k) @@ -690,7 +690,7 @@ def set_default_options(self, default_options: T.MutableMapping[OptionKey, str], # Always test this using the HOST machine, as many builtin options # are not valid for the BUILD machine, but the yielding value does # not differ between them even when they are valid for both. - if subproject and k.is_builtin() and self.options[k.evolve(subproject='', machine=MachineChoice.HOST)].yielding: + if subproject and k.is_builtin() and self.optstore[k.evolve(subproject='', machine=MachineChoice.HOST)].yielding: continue # Skip base, compiler, and backend options, they are handled when # adding languages and setting backend. @@ -710,16 +710,16 @@ def add_compiler_options(self, options: MutableKeyedOptionDictType, lang: str, f if value is not None: o.set_value(value) if not subproject: - self.options[k] = o # override compiler option on reconfigure - self.options.setdefault(k, o) + self.optstore[k] = o # override compiler option on reconfigure + self.optstore.setdefault(k, o) if subproject: sk = k.evolve(subproject=subproject) value = env.options.get(sk) or value if value is not None: o.set_value(value) - self.options[sk] = o # override compiler option on reconfigure - self.options.setdefault(sk, o) + self.optstore[sk] = o # override compiler option on reconfigure + self.optstore.setdefault(sk, o) def add_lang_args(self, lang: str, comp: T.Type['Compiler'], for_machine: MachineChoice, env: 'Environment') -> None: @@ -727,8 +727,8 @@ def add_lang_args(self, lang: str, comp: T.Type['Compiler'], from .compilers import compilers # These options are all new at this point, because the compiler is # responsible for adding its own options, thus calling - # `self.options.update()`` is perfectly safe. - self.options.update(compilers.get_global_options(lang, comp, for_machine, env)) + # `self.optstore.update()`` is perfectly safe. + self.optstore.update(compilers.get_global_options(lang, comp, for_machine, env)) def process_compiler_options(self, lang: str, comp: Compiler, env: Environment, subproject: str) -> None: from . import compilers @@ -741,20 +741,20 @@ def process_compiler_options(self, lang: str, comp: Compiler, env: Environment, skey = key.evolve(subproject=subproject) else: skey = key - if skey not in self.options: - self.options[skey] = copy.deepcopy(compilers.base_options[key]) + if skey not in self.optstore: + self.optstore[skey] = copy.deepcopy(compilers.base_options[key]) if skey in env.options: - self.options[skey].set_value(env.options[skey]) + self.optstore[skey].set_value(env.options[skey]) enabled_opts.append(skey) elif subproject and key in env.options: - self.options[skey].set_value(env.options[key]) + self.optstore[skey].set_value(env.options[key]) enabled_opts.append(skey) - if subproject and key not in self.options: - self.options[key] = copy.deepcopy(self.options[skey]) + if subproject and key not in self.optstore: + self.optstore[key] = copy.deepcopy(self.optstore[skey]) elif skey in env.options: - self.options[skey].set_value(env.options[skey]) + self.optstore[skey].set_value(env.options[skey]) elif subproject and key in env.options: - self.options[skey].set_value(env.options[key]) + self.optstore[skey].set_value(env.options[key]) self.emit_base_options_warnings(enabled_opts) def emit_base_options_warnings(self, enabled_opts: T.List[OptionKey]) -> None: diff --git a/mesonbuild/dependencies/boost.py b/mesonbuild/dependencies/boost.py index cccc0c3bde47..39b3cfe159f1 100644 --- a/mesonbuild/dependencies/boost.py +++ b/mesonbuild/dependencies/boost.py @@ -580,8 +580,8 @@ def filter_libraries(self, libs: T.List[BoostLibraryFile], lib_vers: str) -> T.L # MSVC is very picky with the library tags vscrt = '' try: - crt_val = self.env.coredata.options[mesonlib.OptionKey('b_vscrt')].value - buildtype = self.env.coredata.options[mesonlib.OptionKey('buildtype')].value + crt_val = self.env.coredata.optstore[mesonlib.OptionKey('b_vscrt')].value + buildtype = self.env.coredata.optstore[mesonlib.OptionKey('buildtype')].value vscrt = self.clib_compiler.get_crt_compile_args(crt_val, buildtype)[0] except (KeyError, IndexError, AttributeError): pass diff --git a/mesonbuild/dependencies/pkgconfig.py b/mesonbuild/dependencies/pkgconfig.py index 30e3d2896d46..b6647b4a4099 100644 --- a/mesonbuild/dependencies/pkgconfig.py +++ b/mesonbuild/dependencies/pkgconfig.py @@ -238,7 +238,7 @@ def _check_pkgconfig(self, pkgbin: ExternalProgram) -> T.Optional[str]: def _get_env(self, uninstalled: bool = False) -> EnvironmentVariables: env = EnvironmentVariables() key = OptionKey('pkg_config_path', machine=self.for_machine) - extra_paths: T.List[str] = self.env.coredata.options[key].value[:] + extra_paths: T.List[str] = self.env.coredata.optstore[key].value[:] if uninstalled: uninstalled_path = Path(self.env.get_build_dir(), 'meson-uninstalled').as_posix() if uninstalled_path not in extra_paths: @@ -397,7 +397,7 @@ def _search_libs(self, libs_in: ImmutableListProtocol[str], raw_libs_in: Immutab # # Only prefix_libpaths are reordered here because there should not be # too many system_libpaths to cause library version issues. - pkg_config_path: T.List[str] = self.env.coredata.options[OptionKey('pkg_config_path', machine=self.for_machine)].value + pkg_config_path: T.List[str] = self.env.coredata.optstore[OptionKey('pkg_config_path', machine=self.for_machine)].value pkg_config_path = self._convert_mingw_paths(pkg_config_path) prefix_libpaths = OrderedSet(sort_libpaths(list(prefix_libpaths), pkg_config_path)) system_libpaths: OrderedSet[str] = OrderedSet() diff --git a/mesonbuild/dependencies/python.py b/mesonbuild/dependencies/python.py index a0a22c1a54ad..2ec7b9d92210 100644 --- a/mesonbuild/dependencies/python.py +++ b/mesonbuild/dependencies/python.py @@ -296,8 +296,8 @@ def get_windows_link_args(self, limited_api: bool) -> T.Optional[T.List[str]]: # `debugoptimized` buildtype may not set debug=True currently, see gh-11645 is_debug_build = debug or buildtype == 'debug' vscrt_debug = False - if mesonlib.OptionKey('b_vscrt') in self.env.coredata.options: - vscrt = self.env.coredata.options[mesonlib.OptionKey('b_vscrt')].value + if mesonlib.OptionKey('b_vscrt') in self.env.coredata.optstore: + vscrt = self.env.coredata.optstore[mesonlib.OptionKey('b_vscrt')].value if vscrt in {'mdd', 'mtd', 'from_buildtype', 'static_from_buildtype'}: vscrt_debug = True if is_debug_build and vscrt_debug and not self.variables.get('Py_DEBUG'): diff --git a/mesonbuild/dependencies/qt.py b/mesonbuild/dependencies/qt.py index 65b6a5810961..cc80ce8f544c 100644 --- a/mesonbuild/dependencies/qt.py +++ b/mesonbuild/dependencies/qt.py @@ -297,8 +297,8 @@ def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any]): # Use the buildtype by default, but look at the b_vscrt option if the # compiler supports it. is_debug = self.env.coredata.get_option(mesonlib.OptionKey('buildtype')) == 'debug' - if mesonlib.OptionKey('b_vscrt') in self.env.coredata.options: - if self.env.coredata.options[mesonlib.OptionKey('b_vscrt')].value in {'mdd', 'mtd'}: + if mesonlib.OptionKey('b_vscrt') in self.env.coredata.optstore: + if self.env.coredata.optstore[mesonlib.OptionKey('b_vscrt')].value in {'mdd', 'mtd'}: is_debug = True modules_lib_suffix = _get_modules_lib_suffix(self.version, self.env.machines[self.for_machine], is_debug) diff --git a/mesonbuild/interpreter/compiler.py b/mesonbuild/interpreter/compiler.py index 359e8e726378..1bdb321e47e8 100644 --- a/mesonbuild/interpreter/compiler.py +++ b/mesonbuild/interpreter/compiler.py @@ -270,7 +270,7 @@ def _determine_args(self, kwargs: BaseCompileKW, for idir in i.to_string_list(self.environment.get_source_dir(), self.environment.get_build_dir()): args.extend(self.compiler.get_include_args(idir, False)) if not kwargs['no_builtin_args']: - opts = coredata.OptionsView(self.environment.coredata.options, self.subproject) + opts = coredata.OptionsView(self.environment.coredata.optstore, self.subproject) args += self.compiler.get_option_compile_args(opts) if mode is CompileCheckMode.LINK: args.extend(self.compiler.get_option_link_args(opts)) diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index 6474e60844e8..a423ed8509d9 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -1013,7 +1013,7 @@ def _do_subproject_cmake(self, subp_name: str, subdir: str, kwargs: kwtypes.DoSubproject) -> SubprojectHolder: from ..cmake import CMakeInterpreter with mlog.nested(subp_name): - prefix = self.coredata.options[OptionKey('prefix')].value + prefix = self.coredata.optstore[OptionKey('prefix')].value from ..modules.cmake import CMakeSubprojectOptions options = kwargs.get('options') or CMakeSubprojectOptions() @@ -1052,7 +1052,7 @@ def get_option_internal(self, optname: str) -> options.UserOption: key = OptionKey.from_string(optname).evolve(subproject=self.subproject) if not key.is_project(): - for opts in [self.coredata.options, compilers.base_options]: + for opts in [self.coredata.optstore, compilers.base_options]: v = opts.get(key) if v is None or v.yielding: v = opts.get(key.as_root()) @@ -1061,9 +1061,9 @@ def get_option_internal(self, optname: str) -> options.UserOption: return v try: - opt = self.coredata.options[key] - if opt.yielding and key.subproject and key.as_root() in self.coredata.options: - popt = self.coredata.options[key.as_root()] + opt = self.coredata.optstore[key] + if opt.yielding and key.subproject and key.as_root() in self.coredata.optstore: + popt = self.coredata.optstore[key.as_root()] if type(opt) is type(popt): opt = popt else: @@ -1543,7 +1543,7 @@ def add_languages_for(self, args: T.List[str], required: bool, for_machine: Mach if self.subproject: options = {} for k in comp.get_options(): - v = copy.copy(self.coredata.options[k]) + v = copy.copy(self.coredata.optstore[k]) k = k.evolve(subproject=self.subproject) options[k] = v self.coredata.add_compiler_options(options, lang, for_machine, self.environment, self.subproject) @@ -3041,13 +3041,13 @@ def print_extra_warnings(self) -> None: break def check_clang_asan_lundef(self) -> None: - if OptionKey('b_lundef') not in self.coredata.options: + if OptionKey('b_lundef') not in self.coredata.optstore: return - if OptionKey('b_sanitize') not in self.coredata.options: + if OptionKey('b_sanitize') not in self.coredata.optstore: return - if (self.coredata.options[OptionKey('b_lundef')].value and - self.coredata.options[OptionKey('b_sanitize')].value != 'none'): - value = self.coredata.options[OptionKey('b_sanitize')].value + if (self.coredata.optstore[OptionKey('b_lundef')].value and + self.coredata.optstore[OptionKey('b_sanitize')].value != 'none'): + value = self.coredata.optstore[OptionKey('b_sanitize')].value mlog.warning(textwrap.dedent(f'''\ Trying to use {value} sanitizer on Clang with b_lundef. This will probably not work. diff --git a/mesonbuild/interpreter/interpreterobjects.py b/mesonbuild/interpreter/interpreterobjects.py index adec0d589be8..f5dafa736934 100644 --- a/mesonbuild/interpreter/interpreterobjects.py +++ b/mesonbuild/interpreter/interpreterobjects.py @@ -90,7 +90,7 @@ def __init__(self, option: options.UserFeatureOption, interpreter: 'Interpreter' super().__init__(option, interpreter) if option and option.is_auto(): # TODO: we need to cast here because options is not a TypedDict - auto = T.cast('options.UserFeatureOption', self.env.coredata.options[OptionKey('auto_features')]) + auto = T.cast('options.UserFeatureOption', self.env.coredata.optstore[OptionKey('auto_features')]) self.held_object = copy.copy(auto) self.held_object.name = option.name self.methods.update({'enabled': self.enabled_method, diff --git a/mesonbuild/mconf.py b/mesonbuild/mconf.py index 48359ded23fb..0ed7c927f1c6 100644 --- a/mesonbuild/mconf.py +++ b/mesonbuild/mconf.py @@ -229,11 +229,11 @@ def print_options(self, title: str, opts: 'coredata.KeyedOptionDictType') -> Non return if title: self.add_title(title) - auto = T.cast('options.UserFeatureOption', self.coredata.options[OptionKey('auto_features')]) + auto = T.cast('options.UserFeatureOption', self.coredata.optstore[OptionKey('auto_features')]) for k, o in sorted(opts.items()): printable_value = o.printable_value() root = k.as_root() - if o.yielding and k.subproject and root in self.coredata.options: + if o.yielding and k.subproject and root in self.coredata.optstore: printable_value = '' if isinstance(o, options.UserFeatureOption) and o.is_auto(): printable_value = auto.printable_value() @@ -264,7 +264,7 @@ def print_default_values_warning() -> None: test_options: 'coredata.MutableKeyedOptionDictType' = {} core_options: 'coredata.MutableKeyedOptionDictType' = {} module_options: T.Dict[str, 'coredata.MutableKeyedOptionDictType'] = collections.defaultdict(dict) - for k, v in self.coredata.options.items(): + for k, v in self.coredata.optstore.items(): if k in dir_option_names: dir_options[k] = v elif k in test_option_names: @@ -280,17 +280,17 @@ def print_default_values_warning() -> None: host_core_options = self.split_options_per_subproject({k: v for k, v in core_options.items() if k.machine is MachineChoice.HOST}) build_core_options = self.split_options_per_subproject({k: v for k, v in core_options.items() if k.machine is MachineChoice.BUILD}) - host_compiler_options = self.split_options_per_subproject({k: v for k, v in self.coredata.options.items() if k.is_compiler() and k.machine is MachineChoice.HOST}) - build_compiler_options = self.split_options_per_subproject({k: v for k, v in self.coredata.options.items() if k.is_compiler() and k.machine is MachineChoice.BUILD}) - project_options = self.split_options_per_subproject({k: v for k, v in self.coredata.options.items() if k.is_project()}) + host_compiler_options = self.split_options_per_subproject({k: v for k, v in self.coredata.optstore.items() if k.is_compiler() and k.machine is MachineChoice.HOST}) + build_compiler_options = self.split_options_per_subproject({k: v for k, v in self.coredata.optstore.items() if k.is_compiler() and k.machine is MachineChoice.BUILD}) + project_options = self.split_options_per_subproject({k: v for k, v in self.coredata.optstore.items() if k.is_project()}) show_build_options = self.default_values_only or self.build.environment.is_cross_build() self.add_section('Main project options') self.print_options('Core options', host_core_options['']) if show_build_options: self.print_options('', build_core_options['']) - self.print_options('Backend options', {k: v for k, v in self.coredata.options.items() if k.is_backend()}) - self.print_options('Base options', {k: v for k, v in self.coredata.options.items() if k.is_base()}) + self.print_options('Backend options', {k: v for k, v in self.coredata.optstore.items() if k.is_backend()}) + self.print_options('Base options', {k: v for k, v in self.coredata.optstore.items() if k.is_base()}) self.print_options('Compiler options', host_compiler_options.get('', {})) if show_build_options: self.print_options('', build_compiler_options.get('', {})) diff --git a/mesonbuild/mintro.py b/mesonbuild/mintro.py index c678cf750ee5..b13af546d3a3 100644 --- a/mesonbuild/mintro.py +++ b/mesonbuild/mintro.py @@ -291,7 +291,7 @@ def list_buildoptions(coredata: cdata.CoreData, subprojects: T.Optional[T.List[s dir_options: 'cdata.MutableKeyedOptionDictType' = {} test_options: 'cdata.MutableKeyedOptionDictType' = {} core_options: 'cdata.MutableKeyedOptionDictType' = {} - for k, v in coredata.options.items(): + for k, v in coredata.optstore.items(): if k in dir_option_names: dir_options[k] = v elif k in test_option_names: @@ -326,14 +326,14 @@ def add_keys(opts: 'cdata.KeyedOptionDictType', section: str) -> None: optlist.append(optdict) add_keys(core_options, 'core') - add_keys({k: v for k, v in coredata.options.items() if k.is_backend()}, 'backend') - add_keys({k: v for k, v in coredata.options.items() if k.is_base()}, 'base') + add_keys({k: v for k, v in coredata.optstore.items() if k.is_backend()}, 'backend') + add_keys({k: v for k, v in coredata.optstore.items() if k.is_base()}, 'base') add_keys( - {k: v for k, v in sorted(coredata.options.items(), key=lambda i: i[0].machine) if k.is_compiler()}, + {k: v for k, v in sorted(coredata.optstore.items(), key=lambda i: i[0].machine) if k.is_compiler()}, 'compiler', ) add_keys(dir_options, 'directory') - add_keys({k: v for k, v in coredata.options.items() if k.is_project()}, 'user') + add_keys({k: v for k, v in coredata.optstore.items() if k.is_project()}, 'user') add_keys(test_options, 'test') return optlist diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py index 4405c40cc4a8..f46e4f520e5f 100644 --- a/mesonbuild/modules/gnome.py +++ b/mesonbuild/modules/gnome.py @@ -906,7 +906,7 @@ def _get_langs_compilers_flags(state: 'ModuleState', langs_compilers: T.List[T.T if state.project_args.get(lang): cflags += state.project_args[lang] if mesonlib.OptionKey('b_sanitize') in compiler.base_options: - sanitize = state.environment.coredata.options[mesonlib.OptionKey('b_sanitize')].value + sanitize = state.environment.coredata.optstore[mesonlib.OptionKey('b_sanitize')].value cflags += compiler.sanitizer_compile_args(sanitize) sanitize = sanitize.split(',') # These must be first in ldflags diff --git a/mesonbuild/modules/python.py b/mesonbuild/modules/python.py index d195a3fa5291..3f7affe5e50c 100644 --- a/mesonbuild/modules/python.py +++ b/mesonbuild/modules/python.py @@ -205,7 +205,7 @@ def extension_module_method(self, args: T.Tuple[str, T.List[BuildTargetSource]], new_link_args = mesonlib.extract_as_list(kwargs, 'link_args') - is_debug = self.interpreter.environment.coredata.options[OptionKey('debug')].value + is_debug = self.interpreter.environment.coredata.optstore[OptionKey('debug')].value if is_debug: new_link_args.append(python_windows_debug_link_exception) else: diff --git a/mesonbuild/modules/rust.py b/mesonbuild/modules/rust.py index 8534dfa31a1e..a8e22541c164 100644 --- a/mesonbuild/modules/rust.py +++ b/mesonbuild/modules/rust.py @@ -231,7 +231,7 @@ def bindgen(self, state: ModuleState, args: T.List, kwargs: FuncBindgen) -> Modu # bindgen always uses clang, so it's safe to hardcode -I here clang_args.extend([f'-I{x}' for x in i.to_string_list( state.environment.get_source_dir(), state.environment.get_build_dir())]) - if are_asserts_disabled(state.environment.coredata.options): + if are_asserts_disabled(state.environment.coredata.optstore): clang_args.append('-DNDEBUG') for de in kwargs['dependencies']: diff --git a/mesonbuild/msetup.py b/mesonbuild/msetup.py index c1d71e2e55e2..8f561e43875e 100644 --- a/mesonbuild/msetup.py +++ b/mesonbuild/msetup.py @@ -273,9 +273,9 @@ def _generate(self, env: environment.Environment, capture: bool, vslite_ctx: T.O # collect warnings about unsupported build configurations; must be done after full arg processing # by Interpreter() init, but this is most visible at the end - if env.coredata.options[mesonlib.OptionKey('backend')].value == 'xcode': + if env.coredata.optstore[mesonlib.OptionKey('backend')].value == 'xcode': mlog.warning('xcode backend is currently unmaintained, patches welcome') - if env.coredata.options[mesonlib.OptionKey('layout')].value == 'flat': + if env.coredata.optstore[mesonlib.OptionKey('layout')].value == 'flat': mlog.warning('-Dlayout=flat is unsupported and probably broken. It was a failed experiment at ' 'making Windows build artifacts runnable while uninstalled, due to PATH considerations, ' 'but was untested by CI and anyways breaks reasonable use of conflicting targets in different subdirs. ' diff --git a/mesonbuild/rewriter.py b/mesonbuild/rewriter.py index 0a40a711c4ea..78517bf05f8b 100644 --- a/mesonbuild/rewriter.py +++ b/mesonbuild/rewriter.py @@ -470,11 +470,11 @@ def process_default_options(self, cmd): cdata = self.interpreter.coredata options = { - **{str(k): v for k, v in cdata.options.items()}, - **{str(k): v for k, v in cdata.options.items()}, - **{str(k): v for k, v in cdata.options.items()}, - **{str(k): v for k, v in cdata.options.items()}, - **{str(k): v for k, v in cdata.options.items()}, + **{str(k): v for k, v in cdata.optstore.items()}, + **{str(k): v for k, v in cdata.optstore.items()}, + **{str(k): v for k, v in cdata.optstore.items()}, + **{str(k): v for k, v in cdata.optstore.items()}, + **{str(k): v for k, v in cdata.optstore.items()}, } for key, val in sorted(cmd['options'].items()): diff --git a/run_tests.py b/run_tests.py index 63eb62c19b60..5b229d790d44 100755 --- a/run_tests.py +++ b/run_tests.py @@ -151,7 +151,7 @@ def get_fake_env(sdir='', bdir=None, prefix='', opts=None): if opts is None: opts = get_fake_options(prefix) env = Environment(sdir, bdir, opts) - env.coredata.options[OptionKey('args', lang='c')] = FakeCompilerOptions() + env.coredata.optstore[OptionKey('args', lang='c')] = FakeCompilerOptions() env.machines.host.cpu_family = 'x86_64' # Used on macOS inside find_library # Invalidate cache when using a different Environment object. clear_meson_configure_class_caches() diff --git a/unittests/allplatformstests.py b/unittests/allplatformstests.py index 63445ec2b25c..042f2107c93b 100644 --- a/unittests/allplatformstests.py +++ b/unittests/allplatformstests.py @@ -2621,35 +2621,35 @@ def test_command_line(self): out = self.init(testdir, extra_args=['--profile-self', '--fatal-meson-warnings']) self.assertNotIn('[default: true]', out) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.options[OptionKey('default_library')].value, 'static') - self.assertEqual(obj.options[OptionKey('warning_level')].value, '1') - self.assertEqual(obj.options[OptionKey('set_sub_opt')].value, True) - self.assertEqual(obj.options[OptionKey('subp_opt', 'subp')].value, 'default3') + self.assertEqual(obj.optstore[OptionKey('default_library')].value, 'static') + self.assertEqual(obj.optstore[OptionKey('warning_level')].value, '1') + self.assertEqual(obj.optstore[OptionKey('set_sub_opt')].value, True) + self.assertEqual(obj.optstore[OptionKey('subp_opt', 'subp')].value, 'default3') self.wipe() # warning_level is special, it's --warnlevel instead of --warning-level # for historical reasons self.init(testdir, extra_args=['--warnlevel=2', '--fatal-meson-warnings']) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.options[OptionKey('warning_level')].value, '2') + self.assertEqual(obj.optstore[OptionKey('warning_level')].value, '2') self.setconf('--warnlevel=3') obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.options[OptionKey('warning_level')].value, '3') + self.assertEqual(obj.optstore[OptionKey('warning_level')].value, '3') self.setconf('--warnlevel=everything') obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.options[OptionKey('warning_level')].value, 'everything') + self.assertEqual(obj.optstore[OptionKey('warning_level')].value, 'everything') self.wipe() # But when using -D syntax, it should be 'warning_level' self.init(testdir, extra_args=['-Dwarning_level=2', '--fatal-meson-warnings']) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.options[OptionKey('warning_level')].value, '2') + self.assertEqual(obj.optstore[OptionKey('warning_level')].value, '2') self.setconf('-Dwarning_level=3') obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.options[OptionKey('warning_level')].value, '3') + self.assertEqual(obj.optstore[OptionKey('warning_level')].value, '3') self.setconf('-Dwarning_level=everything') obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.options[OptionKey('warning_level')].value, 'everything') + self.assertEqual(obj.optstore[OptionKey('warning_level')].value, 'everything') self.wipe() # Mixing --option and -Doption is forbidden @@ -2673,15 +2673,15 @@ def test_command_line(self): # --default-library should override default value from project() self.init(testdir, extra_args=['--default-library=both', '--fatal-meson-warnings']) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.options[OptionKey('default_library')].value, 'both') + self.assertEqual(obj.optstore[OptionKey('default_library')].value, 'both') self.setconf('--default-library=shared') obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.options[OptionKey('default_library')].value, 'shared') + self.assertEqual(obj.optstore[OptionKey('default_library')].value, 'shared') if self.backend is Backend.ninja: # reconfigure target works only with ninja backend self.build('reconfigure') obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.options[OptionKey('default_library')].value, 'shared') + self.assertEqual(obj.optstore[OptionKey('default_library')].value, 'shared') self.wipe() # Should fail on unknown options @@ -2718,22 +2718,22 @@ def test_command_line(self): # Test we can set subproject option self.init(testdir, extra_args=['-Dsubp:subp_opt=foo', '--fatal-meson-warnings']) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.options[OptionKey('subp_opt', 'subp')].value, 'foo') + self.assertEqual(obj.optstore[OptionKey('subp_opt', 'subp')].value, 'foo') self.wipe() # c_args value should be parsed with split_args self.init(testdir, extra_args=['-Dc_args=-Dfoo -Dbar "-Dthird=one two"', '--fatal-meson-warnings']) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.options[OptionKey('args', lang='c')].value, ['-Dfoo', '-Dbar', '-Dthird=one two']) + self.assertEqual(obj.optstore[OptionKey('args', lang='c')].value, ['-Dfoo', '-Dbar', '-Dthird=one two']) self.setconf('-Dc_args="foo bar" one two') obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.options[OptionKey('args', lang='c')].value, ['foo bar', 'one', 'two']) + self.assertEqual(obj.optstore[OptionKey('args', lang='c')].value, ['foo bar', 'one', 'two']) self.wipe() self.init(testdir, extra_args=['-Dset_percent_opt=myoption%', '--fatal-meson-warnings']) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.options[OptionKey('set_percent_opt')].value, 'myoption%') + self.assertEqual(obj.optstore[OptionKey('set_percent_opt')].value, 'myoption%') self.wipe() # Setting a 2nd time the same option should override the first value @@ -2744,19 +2744,19 @@ def test_command_line(self): '-Dc_args=-Dfoo', '-Dc_args=-Dbar', '-Db_lundef=false', '--fatal-meson-warnings']) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.options[OptionKey('bindir')].value, 'bar') - self.assertEqual(obj.options[OptionKey('buildtype')].value, 'release') - self.assertEqual(obj.options[OptionKey('b_sanitize')].value, 'thread') - self.assertEqual(obj.options[OptionKey('args', lang='c')].value, ['-Dbar']) + self.assertEqual(obj.optstore[OptionKey('bindir')].value, 'bar') + self.assertEqual(obj.optstore[OptionKey('buildtype')].value, 'release') + self.assertEqual(obj.optstore[OptionKey('b_sanitize')].value, 'thread') + self.assertEqual(obj.optstore[OptionKey('args', lang='c')].value, ['-Dbar']) self.setconf(['--bindir=bar', '--bindir=foo', '-Dbuildtype=release', '-Dbuildtype=plain', '-Db_sanitize=thread', '-Db_sanitize=address', '-Dc_args=-Dbar', '-Dc_args=-Dfoo']) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.options[OptionKey('bindir')].value, 'foo') - self.assertEqual(obj.options[OptionKey('buildtype')].value, 'plain') - self.assertEqual(obj.options[OptionKey('b_sanitize')].value, 'address') - self.assertEqual(obj.options[OptionKey('args', lang='c')].value, ['-Dfoo']) + self.assertEqual(obj.optstore[OptionKey('bindir')].value, 'foo') + self.assertEqual(obj.optstore[OptionKey('buildtype')].value, 'plain') + self.assertEqual(obj.optstore[OptionKey('b_sanitize')].value, 'address') + self.assertEqual(obj.optstore[OptionKey('args', lang='c')].value, ['-Dfoo']) self.wipe() except KeyError: # Ignore KeyError, it happens on CI for compilers that does not @@ -2770,25 +2770,25 @@ def test_warning_level_0(self): # Verify default values when passing no args self.init(testdir) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.options[OptionKey('warning_level')].value, '0') + self.assertEqual(obj.optstore[OptionKey('warning_level')].value, '0') self.wipe() # verify we can override w/ --warnlevel self.init(testdir, extra_args=['--warnlevel=1']) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.options[OptionKey('warning_level')].value, '1') + self.assertEqual(obj.optstore[OptionKey('warning_level')].value, '1') self.setconf('--warnlevel=0') obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.options[OptionKey('warning_level')].value, '0') + self.assertEqual(obj.optstore[OptionKey('warning_level')].value, '0') self.wipe() # verify we can override w/ -Dwarning_level self.init(testdir, extra_args=['-Dwarning_level=1']) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.options[OptionKey('warning_level')].value, '1') + self.assertEqual(obj.optstore[OptionKey('warning_level')].value, '1') self.setconf('-Dwarning_level=0') obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.options[OptionKey('warning_level')].value, '0') + self.assertEqual(obj.optstore[OptionKey('warning_level')].value, '0') self.wipe() def test_feature_check_usage_subprojects(self): diff --git a/unittests/datatests.py b/unittests/datatests.py index 19664e378817..b596446290f0 100644 --- a/unittests/datatests.py +++ b/unittests/datatests.py @@ -163,9 +163,9 @@ def test_builtin_options_documented(self): else: raise RuntimeError(f'Invalid debug value {debug!r} in row:\n{m.group()}') env.coredata.set_option(OptionKey('buildtype'), buildtype) - self.assertEqual(env.coredata.options[OptionKey('buildtype')].value, buildtype) - self.assertEqual(env.coredata.options[OptionKey('optimization')].value, opt) - self.assertEqual(env.coredata.options[OptionKey('debug')].value, debug) + self.assertEqual(env.coredata.optstore[OptionKey('buildtype')].value, buildtype) + self.assertEqual(env.coredata.optstore[OptionKey('optimization')].value, opt) + self.assertEqual(env.coredata.optstore[OptionKey('debug')].value, debug) def test_cpu_families_documented(self): with open("docs/markdown/Reference-tables.md", encoding='utf-8') as f: diff --git a/unittests/internaltests.py b/unittests/internaltests.py index 411c97b36036..42cc41ec5d3b 100644 --- a/unittests/internaltests.py +++ b/unittests/internaltests.py @@ -625,7 +625,7 @@ def create_static_lib(name): env = get_fake_env() compiler = detect_c_compiler(env, MachineChoice.HOST) env.coredata.compilers.host = {'c': compiler} - env.coredata.options[OptionKey('link_args', lang='c')] = FakeCompilerOptions() + env.coredata.optstore[OptionKey('link_args', lang='c')] = FakeCompilerOptions() p1 = Path(tmpdir) / '1' p2 = Path(tmpdir) / '2' p1.mkdir() diff --git a/unittests/linuxliketests.py b/unittests/linuxliketests.py index bfe3586c1b84..1e8038fa03d6 100644 --- a/unittests/linuxliketests.py +++ b/unittests/linuxliketests.py @@ -1124,7 +1124,7 @@ def test_pkgconfig_duplicate_path_entries(self): # option, adding the meson-uninstalled directory to it. PkgConfigInterface.setup_env({}, env, MachineChoice.HOST, uninstalled=True) - pkg_config_path = env.coredata.options[OptionKey('pkg_config_path')].value + pkg_config_path = env.coredata.optstore[OptionKey('pkg_config_path')].value self.assertEqual(pkg_config_path, [pkg_dir]) @skipIfNoPkgconfig From 9a6fcd4d9a0c7bb248c5d0587632b741a3301e03 Mon Sep 17 00:00:00 2001 From: Jussi Pakkanen Date: Sun, 9 Jun 2024 01:03:49 +0300 Subject: [PATCH 56/91] Replace direct indexing with named methods. --- mesonbuild/ast/introspection.py | 2 +- mesonbuild/backend/backends.py | 2 +- mesonbuild/backend/ninjabackend.py | 12 +- mesonbuild/backend/vs2010backend.py | 2 +- mesonbuild/build.py | 6 +- mesonbuild/cmake/common.py | 2 +- mesonbuild/cmake/executor.py | 2 +- mesonbuild/cmake/interpreter.py | 2 +- mesonbuild/compilers/c.py | 84 +++++++------- mesonbuild/compilers/compilers.py | 60 +++++----- mesonbuild/compilers/cpp.py | 110 +++++++++---------- mesonbuild/compilers/cuda.py | 18 +-- mesonbuild/compilers/cython.py | 8 +- mesonbuild/compilers/fortran.py | 18 +-- mesonbuild/compilers/mixins/clike.py | 6 +- mesonbuild/compilers/mixins/emscripten.py | 2 +- mesonbuild/compilers/objc.py | 6 +- mesonbuild/compilers/objcpp.py | 6 +- mesonbuild/compilers/rust.py | 6 +- mesonbuild/coredata.py | 110 +++++++++++-------- mesonbuild/dependencies/boost.py | 4 +- mesonbuild/dependencies/pkgconfig.py | 4 +- mesonbuild/dependencies/python.py | 2 +- mesonbuild/dependencies/qt.py | 2 +- mesonbuild/interpreter/interpreter.py | 14 +-- mesonbuild/interpreter/interpreterobjects.py | 4 +- mesonbuild/mconf.py | 2 +- mesonbuild/modules/gnome.py | 2 +- mesonbuild/modules/python.py | 2 +- mesonbuild/msetup.py | 4 +- mesonbuild/options.py | 34 +++++- run_tests.py | 2 +- unittests/allplatformstests.py | 60 +++++----- unittests/datatests.py | 6 +- unittests/internaltests.py | 2 +- unittests/linuxliketests.py | 2 +- 36 files changed, 327 insertions(+), 283 deletions(-) diff --git a/mesonbuild/ast/introspection.py b/mesonbuild/ast/introspection.py index 1d7dd0510ae2..11975109d480 100644 --- a/mesonbuild/ast/introspection.py +++ b/mesonbuild/ast/introspection.py @@ -182,7 +182,7 @@ def _add_languages(self, raw_langs: T.List[TYPE_var], required: bool, for_machin if self.subproject: options = {} for k in comp.get_options(): - v = copy.copy(self.coredata.optstore[k]) + v = copy.copy(self.coredata.optstore.get_value_object(k)) k = k.evolve(subproject=self.subproject) options[k] = v self.coredata.add_compiler_options(options, lang, for_machine, self.environment, self.subproject) diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index 740f349e4433..c1a72f13d003 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -962,7 +962,7 @@ def create_msvc_pch_implementation(self, target: build.BuildTarget, lang: str, p def target_uses_pch(self, target: build.BuildTarget) -> bool: try: return T.cast('bool', target.get_option(OptionKey('b_pch'))) - except KeyError: + except (KeyError, AttributeError): return False @staticmethod diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index debde7be9d2b..759ae9a93aa9 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -623,7 +623,7 @@ def generate(self, capture: bool = False, vslite_ctx: T.Optional[T.Dict] = None) outfile.write('# Do not edit by hand.\n\n') outfile.write('ninja_required_version = 1.8.2\n\n') - num_pools = self.environment.coredata.optstore[OptionKey('backend_max_links')].value + num_pools = self.environment.coredata.optstore.get_value('backend_max_links') if num_pools > 0: outfile.write(f'''pool link_pool depth = {num_pools} @@ -657,7 +657,7 @@ def generate(self, capture: bool = False, vslite_ctx: T.Optional[T.Dict] = None) mlog.log_timestamp("Dist generated") key = OptionKey('b_coverage') if (key in self.environment.coredata.optstore and - self.environment.coredata.optstore[key].value): + self.environment.coredata.optstore.get_value(key)): gcovr_exe, gcovr_version, lcov_exe, lcov_version, genhtml_exe, llvm_cov_exe = environment.find_coverage_tools(self.environment.coredata) mlog.debug(f'Using {gcovr_exe} ({gcovr_version}), {lcov_exe} and {llvm_cov_exe} for code coverage') if gcovr_exe or (lcov_exe and genhtml_exe): @@ -1983,7 +1983,7 @@ def generate_rust_target(self, target: build.BuildTarget) -> None: buildtype = target.get_option(OptionKey('buildtype')) crt = target.get_option(OptionKey('b_vscrt')) args += rustc.get_crt_link_args(crt, buildtype) - except KeyError: + except (KeyError, AttributeError): pass if mesonlib.version_compare(rustc.version, '>= 1.67.0'): @@ -2287,7 +2287,7 @@ def _rsp_options(self, tool: T.Union['Compiler', 'StaticLinker', 'DynamicLinker' return options def generate_static_link_rules(self): - num_pools = self.environment.coredata.optstore[OptionKey('backend_max_links')].value + num_pools = self.environment.coredata.optstore.get_value('backend_max_links') if 'java' in self.environment.coredata.compilers.host: self.generate_java_link() for for_machine in MachineChoice: @@ -2335,7 +2335,7 @@ def generate_static_link_rules(self): self.add_rule(NinjaRule(rule, cmdlist, args, description, **options, extra=pool)) def generate_dynamic_link_rules(self): - num_pools = self.environment.coredata.optstore[OptionKey('backend_max_links')].value + num_pools = self.environment.coredata.optstore.get_value('backend_max_links') for for_machine in MachineChoice: complist = self.environment.coredata.compilers[for_machine] for langname, compiler in complist.items(): @@ -3727,7 +3727,7 @@ def generate_ending(self) -> None: elem.add_dep(self.generate_custom_target_clean(ctlist)) if OptionKey('b_coverage') in self.environment.coredata.optstore and \ - self.environment.coredata.optstore[OptionKey('b_coverage')].value: + self.environment.coredata.optstore.get_value('b_coverage'): self.generate_gcov_clean() elem.add_dep('clean-gcda') elem.add_dep('clean-gcno') diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py index b2b8d8764883..793b678a5791 100644 --- a/mesonbuild/backend/vs2010backend.py +++ b/mesonbuild/backend/vs2010backend.py @@ -532,7 +532,7 @@ def generate_solution(self, sln_filename: str, projlist: T.List[Project]) -> Non replace_if_different(sln_filename, sln_filename_tmp) def generate_projects(self, vslite_ctx: dict = None) -> T.List[Project]: - startup_project = self.environment.coredata.optstore[OptionKey('backend_startup_project')].value + startup_project = self.environment.coredata.optstore.get_value('backend_startup_project') projlist: T.List[Project] = [] startup_idx = 0 for (i, (name, target)) in enumerate(self.build.targets.items()): diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 517845c14f1c..071dbdfd4562 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -664,7 +664,7 @@ def get_options(self) -> coredata.OptionsView: def get_option(self, key: 'OptionKey') -> T.Union[str, int, bool]: # TODO: if it's possible to annotate get_option or validate_option_value # in the future we might be able to remove the cast here - return T.cast('T.Union[str, int, bool]', self.options[key].value) + return T.cast('T.Union[str, int, bool]', self.options.get_value(key)) @staticmethod def parse_overrides(kwargs: T.Dict[str, T.Any]) -> T.Dict[OptionKey, str]: @@ -1243,7 +1243,7 @@ def _extract_pic_pie(self, kwargs: T.Dict[str, T.Any], arg: str, option: str) -> if kwargs.get(arg) is not None: val = T.cast('bool', kwargs[arg]) elif k in self.environment.coredata.optstore: - val = self.environment.coredata.optstore[k].value + val = self.environment.coredata.optstore.get_value(k) else: val = False @@ -1931,7 +1931,7 @@ def __init__( kwargs): key = OptionKey('b_pie') if 'pie' not in kwargs and key in environment.coredata.optstore: - kwargs['pie'] = environment.coredata.optstore[key].value + kwargs['pie'] = environment.coredata.optstore.get_value(key) super().__init__(name, subdir, subproject, for_machine, sources, structured_sources, objects, environment, compilers, kwargs) self.win_subsystem = kwargs.get('win_subsystem') or 'console' diff --git a/mesonbuild/cmake/common.py b/mesonbuild/cmake/common.py index 02648195de99..ad4ec6b1a002 100644 --- a/mesonbuild/cmake/common.py +++ b/mesonbuild/cmake/common.py @@ -53,7 +53,7 @@ def cmake_is_debug(env: 'Environment') -> bool: if OptionKey('b_vscrt') in env.coredata.optstore: is_debug = env.coredata.get_option(OptionKey('buildtype')) == 'debug' - if env.coredata.optstore[OptionKey('b_vscrt')].value in {'mdd', 'mtd'}: + if env.coredata.optstore.get_value('b_vscrt') in {'mdd', 'mtd'}: is_debug = True return is_debug else: diff --git a/mesonbuild/cmake/executor.py b/mesonbuild/cmake/executor.py index a8850d6fe00b..392063d58590 100644 --- a/mesonbuild/cmake/executor.py +++ b/mesonbuild/cmake/executor.py @@ -51,7 +51,7 @@ def __init__(self, environment: 'Environment', version: str, for_machine: Machin self.cmakebin = None return - self.prefix_paths = self.environment.coredata.optstore[OptionKey('cmake_prefix_path', machine=self.for_machine)].value + self.prefix_paths = self.environment.coredata.optstore.get_value(OptionKey('cmake_prefix_path', machine=self.for_machine)) if self.prefix_paths: self.extra_cmake_args += ['-DCMAKE_PREFIX_PATH={}'.format(';'.join(self.prefix_paths))] diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py index 57354d32c243..f67591f68b98 100644 --- a/mesonbuild/cmake/interpreter.py +++ b/mesonbuild/cmake/interpreter.py @@ -532,7 +532,7 @@ def _all_source_suffixes(self) -> 'ImmutableListProtocol[str]': @lru_cache(maxsize=None) def _all_lang_stds(self, lang: str) -> 'ImmutableListProtocol[str]': try: - res = self.env.coredata.optstore[OptionKey('std', machine=MachineChoice.BUILD, lang=lang)].choices + res = self.env.coredata.optstore.get_value_object(OptionKey('std', machine=MachineChoice.BUILD, lang=lang)).choices except KeyError: return [] diff --git a/mesonbuild/compilers/c.py b/mesonbuild/compilers/c.py index 7f2efc991e9c..f4f1937946a0 100644 --- a/mesonbuild/compilers/c.py +++ b/mesonbuild/compilers/c.py @@ -165,16 +165,16 @@ def get_options(self) -> 'MutableKeyedOptionDictType': def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] key = self.form_langopt_key('std') - std = options[key] - if std.value != 'none': - args.append('-std=' + std.value) + std = options.get_value(key) + if std != 'none': + args.append('-std=' + std) return args def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: if self.info.is_windows() or self.info.is_cygwin(): # without a typedict mypy can't understand this. key = self.form_langopt_key('winlibs') - libs = options[key].value.copy() + libs = options.get_value(key).copy() assert isinstance(libs, list) for l in libs: assert isinstance(l, str) @@ -258,9 +258,9 @@ def get_options(self) -> 'MutableKeyedOptionDictType': def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] key = self.form_langopt_key('std') - std = options[key] - if std.value != 'none': - args.append('-std=' + std.value) + std = options.get_value(key) + if std != 'none': + args.append('-std=' + std) return args def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: @@ -318,16 +318,16 @@ def get_options(self) -> 'MutableKeyedOptionDictType': def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] key = self.form_langopt_key('std') - std = options[key] - if std.value != 'none': - args.append('-std=' + std.value) + std = options.get_value(key) + if std != 'none': + args.append('-std=' + std) return args def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: if self.info.is_windows() or self.info.is_cygwin(): # without a typeddict mypy can't figure this out key = self.form_langopt_key('winlibs') - libs: T.List[str] = options[key].value.copy() + libs: T.List[str] = options.get_value(key).copy() assert isinstance(libs, list) for l in libs: assert isinstance(l, str) @@ -432,9 +432,9 @@ def get_options(self) -> 'MutableKeyedOptionDictType': def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] key = self.form_langopt_key('std') - std = options[key] - if std.value != 'none': - args.append('-std=' + std.value) + std = options.get_value(key) + if std != 'none': + args.append('-std=' + std) return args @@ -461,7 +461,7 @@ def get_options(self) -> MutableKeyedOptionDictType: def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: # need a TypeDict to make this work key = self.form_langopt_key('winlibs') - libs = options[key].value.copy() + libs = options.get_value(key).copy() assert isinstance(libs, list) for l in libs: assert isinstance(l, str) @@ -498,11 +498,11 @@ def get_options(self) -> 'MutableKeyedOptionDictType': def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] key = self.form_langopt_key('std') - std = options[key] + std = options.get_value(key) # As of MVSC 16.8, /std:c11 and /std:c17 are the only valid C standard options. - if std.value in {'c11'}: + if std == 'c11': args.append('/std:c11') - elif std.value in {'c17', 'c18'}: + elif std in {'c17', 'c18'}: args.append('/std:c17') return args @@ -519,7 +519,7 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: key = self.form_langopt_key('std') - std = options[key].value + std = options.get_value(key) if std != "none": return [f'/clang:-std={std}'] return [] @@ -541,7 +541,7 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic def get_options(self) -> 'MutableKeyedOptionDictType': opts = super().get_options() key = self.form_langopt_key('std') - std_opt = opts[key] + std_opt = opts.get_value_object(key) assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(['c89', 'c99', 'c11']) return opts @@ -549,11 +549,11 @@ def get_options(self) -> 'MutableKeyedOptionDictType': def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] key = self.form_langopt_key('std') - std = options[key] - if std.value == 'c89': + std = options.get_value(key) + if std == 'c89': mlog.log("ICL doesn't explicitly implement c89, setting the standard to 'none', which is close.", once=True) - elif std.value != 'none': - args.append('/Qstd:' + std.value) + elif std != 'none': + args.append('/Qstd:' + std) return args @@ -583,9 +583,9 @@ def get_options(self) -> 'MutableKeyedOptionDictType': def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] key = self.form_langopt_key('std') - std = options[key] - if std.value != 'none': - args.append('--' + std.value) + std = options.get_value(key) + if std != 'none': + args.append('--' + std) return args @@ -616,10 +616,10 @@ def get_no_stdinc_args(self) -> T.List[str]: def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] key = self.form_langopt_key('std') - std = options[key] - if std.value == 'c89': + std = options.get_value(key) + if std == 'c89': args.append('-lang=c') - elif std.value == 'c99': + elif std == 'c99': args.append('-lang=c99') return args @@ -664,10 +664,10 @@ def get_no_stdinc_args(self) -> T.List[str]: def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] key = self.form_langopt_key('std') - std = options[key] - if std.value != 'none': + std = options.get_value(key) + if std != 'none': args.append('-ansi') - args.append('-std=' + std.value) + args.append('-std=' + std) return args def get_compile_only_args(self) -> T.List[str]: @@ -748,9 +748,9 @@ def get_no_stdinc_args(self) -> T.List[str]: def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] key = self.form_langopt_key('std') - std = options[key] - if std.value != 'none': - args.append('--' + std.value) + std = options.get_value(key) + if std != 'none': + args.append('--' + std) return args class C2000CCompiler(TICCompiler): @@ -784,10 +784,10 @@ def get_options(self) -> 'MutableKeyedOptionDictType': def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] key = self.form_langopt_key('std') - std = options[key] - if std.value != 'none': + std = options.get_value(key) + if std != 'none': args.append('-lang') - args.append(std.value) + args.append(std) return args class MetrowerksCCompilerEmbeddedPowerPC(MetrowerksCompiler, CCompiler): @@ -814,7 +814,7 @@ def get_options(self) -> 'MutableKeyedOptionDictType': def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] key = self.form_langopt_key('std') - std = options[key] - if std.value != 'none': - args.append('-lang ' + std.value) + std = options.get_value(key) + if std != 'none': + args.append('-lang ' + std) return args diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index ef0ea70f72d4..1e386bbbb19d 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -254,7 +254,7 @@ def option_enabled(boptions: T.Set[OptionKey], options: 'KeyedOptionDictType', try: if option not in boptions: return False - ret = options[option].value + ret = options.get_value(option) assert isinstance(ret, bool), 'must return bool' # could also be str return ret except KeyError: @@ -264,8 +264,8 @@ def option_enabled(boptions: T.Set[OptionKey], options: 'KeyedOptionDictType', 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: + v: '_T' = options.get_value(opt) + except (KeyError, AttributeError): return fallback assert isinstance(v, type(fallback)), f'Should have {type(fallback)!r} but was {type(v)!r}' @@ -279,52 +279,52 @@ def are_asserts_disabled(options: KeyedOptionDictType) -> bool: :param options: OptionDictionary :return: whether to disable assertions or not """ - return (options[OptionKey('b_ndebug')].value == 'true' or - (options[OptionKey('b_ndebug')].value == 'if-release' and - options[OptionKey('buildtype')].value in {'release', 'plain'})) + return (options.get_value('b_ndebug') == 'true' or + (options.get_value('b_ndebug') == 'if-release' and + options.get_value('buildtype') in {'release', 'plain'})) def get_base_compile_args(options: 'KeyedOptionDictType', compiler: 'Compiler', env: 'Environment') -> T.List[str]: args: T.List[str] = [] try: - if options[OptionKey('b_lto')].value: + if options.get_value(OptionKey('b_lto')): 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: + except (KeyError, AttributeError): pass try: - args += compiler.get_colorout_args(options[OptionKey('b_colorout')].value) - except KeyError: + args += compiler.get_colorout_args(options.get_value(OptionKey('b_colorout'))) + except (KeyError, AttributeError): pass try: - args += compiler.sanitizer_compile_args(options[OptionKey('b_sanitize')].value) - except KeyError: + args += compiler.sanitizer_compile_args(options.get_value(OptionKey('b_sanitize'))) + except (KeyError, AttributeError): pass try: - pgo_val = options[OptionKey('b_pgo')].value + pgo_val = options.get_value(OptionKey('b_pgo')) if pgo_val == 'generate': args.extend(compiler.get_profile_generate_args()) elif pgo_val == 'use': args.extend(compiler.get_profile_use_args()) - except KeyError: + except (KeyError, AttributeError): pass try: - if options[OptionKey('b_coverage')].value: + if options.get_value(OptionKey('b_coverage')): args += compiler.get_coverage_args() - except KeyError: + except (KeyError, AttributeError): pass try: args += compiler.get_assert_args(are_asserts_disabled(options), env) - except KeyError: + except (KeyError, AttributeError): pass # This does not need a try...except if option_enabled(compiler.base_options, options, OptionKey('b_bitcode')): args.append('-fembed-bitcode') try: - crt_val = options[OptionKey('b_vscrt')].value - buildtype = options[OptionKey('buildtype')].value try: + crt_val = options.get_value(OptionKey('b_vscrt')) + buildtype = options.get_value(OptionKey('buildtype')) args += compiler.get_crt_compile_args(crt_val, buildtype) except AttributeError: pass @@ -336,8 +336,8 @@ def get_base_link_args(options: 'KeyedOptionDictType', linker: 'Compiler', is_shared_module: bool, build_dir: str) -> T.List[str]: args: T.List[str] = [] try: - if options[OptionKey('b_lto')].value: - if options[OptionKey('werror')].value: + if options.get_value('b_lto'): + if options.get_value('werror'): args.extend(linker.get_werror_args()) thinlto_cache_dir = None @@ -349,24 +349,24 @@ def get_base_link_args(options: 'KeyedOptionDictType', linker: 'Compiler', threads=get_option_value(options, OptionKey('b_lto_threads'), 0), mode=get_option_value(options, OptionKey('b_lto_mode'), 'default'), thinlto_cache_dir=thinlto_cache_dir)) - except KeyError: + except (KeyError, AttributeError): pass try: - args += linker.sanitizer_link_args(options[OptionKey('b_sanitize')].value) - except KeyError: + args += linker.sanitizer_link_args(options.get_value('b_sanitize')) + except (KeyError, AttributeError): pass try: - pgo_val = options[OptionKey('b_pgo')].value + pgo_val = options.get_value('b_pgo') if pgo_val == 'generate': args.extend(linker.get_profile_generate_args()) elif pgo_val == 'use': args.extend(linker.get_profile_use_args()) - except KeyError: + except (KeyError, AttributeError): pass try: - if options[OptionKey('b_coverage')].value: + if options.get_value('b_coverage'): args += linker.get_coverage_link_args() - except KeyError: + except (KeyError, AttributeError): pass as_needed = option_enabled(linker.base_options, options, OptionKey('b_asneeded')) @@ -390,9 +390,9 @@ def get_base_link_args(options: 'KeyedOptionDictType', linker: 'Compiler', args.extend(linker.get_allow_undefined_link_args()) try: - crt_val = options[OptionKey('b_vscrt')].value - buildtype = options[OptionKey('buildtype')].value try: + crt_val = options.get_value(OptionKey('b_vscrt')) + buildtype = options.get_value(OptionKey('buildtype')) args += linker.get_crt_link_args(crt_val, buildtype) except AttributeError: pass diff --git a/mesonbuild/compilers/cpp.py b/mesonbuild/compilers/cpp.py index 12d7201002f9..a01b377f465f 100644 --- a/mesonbuild/compilers/cpp.py +++ b/mesonbuild/compilers/cpp.py @@ -278,15 +278,15 @@ def get_options(self) -> 'MutableKeyedOptionDictType': def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args: T.List[str] = [] key = self.form_langopt_key('std') - std = options[key] - if std.value != 'none': - args.append(self._find_best_cpp_std(std.value)) + std = options.get_value(key) + if std != 'none': + args.append(self._find_best_cpp_std(std)) key = self.form_langopt_key('eh') - non_msvc_eh_options(options[key].value, args) + non_msvc_eh_options(options.get_value(key), args) key = self.form_langopt_key('debugstl') - if options[key].value: + if options.get_value(key): args.append('-D_GLIBCXX_DEBUG=1') # We can't do _LIBCPP_DEBUG because it's unreliable unless libc++ was built with it too: @@ -296,7 +296,7 @@ def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str] args.append('-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG') key = self.form_langopt_key('rtti') - if not options[key].value: + if not options.get_value(key): args.append('-fno-rtti') return args @@ -305,7 +305,7 @@ def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: if self.info.is_windows() or self.info.is_cygwin(): # without a typedict mypy can't understand this. key = self.form_langopt_key('winlibs') - libs = options[key].value.copy() + libs = options.get_value(key).copy() assert isinstance(libs, list) for l in libs: assert isinstance(l, str) @@ -365,9 +365,9 @@ def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_ def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args: T.List[str] = [] key = self.form_langopt_key('std') - std = options[key] - if std.value != 'none': - args.append(self._find_best_cpp_std(std.value)) + std = options.get_value(key) + if std != 'none': + args.append(self._find_best_cpp_std(std)) return args @@ -409,12 +409,12 @@ def get_options(self) -> 'MutableKeyedOptionDictType': def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args: T.List[str] = [] key = self.form_langopt_key('std') - std = options[key] - if std.value != 'none': - args.append('-std=' + std.value) + std = options.get_value(key) + if std != 'none': + args.append('-std=' + std) key = self.form_langopt_key('eh') - non_msvc_eh_options(options[key].value, args) + non_msvc_eh_options(options.get_value(key), args) return args @@ -483,16 +483,16 @@ def get_options(self) -> 'MutableKeyedOptionDictType': def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args: T.List[str] = [] key = self.form_langopt_key('std') - std = options[key] - if std.value != 'none': - args.append(self._find_best_cpp_std(std.value)) + std = options.get_value(key) + if std != 'none': + args.append(self._find_best_cpp_std(std)) - non_msvc_eh_options(options[key.evolve('eh')].value, args) + non_msvc_eh_options(options.get_value(key.evolve('eh')), args) - if not options[key.evolve('rtti')].value: + if not options.get_value(key.evolve('rtti')): args.append('-fno-rtti') - if options[key.evolve('debugstl')].value: + if options.get_value(key.evolve('debugstl')): args.append('-D_GLIBCXX_DEBUG=1') return args @@ -500,7 +500,7 @@ def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: if self.info.is_windows() or self.info.is_cygwin(): # without a typedict mypy can't understand this. key = self.form_langopt_key('winlibs') - libs = options[key].value.copy() + libs = options.get_value(key).copy() assert isinstance(libs, list) for l in libs: assert isinstance(l, str) @@ -616,15 +616,15 @@ def has_function(self, funcname: str, prefix: str, env: 'Environment', *, def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args: T.List[str] = [] key = self.form_langopt_key('std') - std = options[key] + std = options.get_value(key) if std.value != 'none': args.append(self._find_best_cpp_std(std.value)) key = self.form_langopt_key('eh') - non_msvc_eh_options(options[key].value, args) + non_msvc_eh_options(options.get_value(key), args) key = self.form_langopt_key('debugstl') - if options[key].value: + if options.get_value(key): args.append('-D_GLIBCXX_DEBUG=1') return args @@ -688,18 +688,18 @@ def get_options(self) -> 'MutableKeyedOptionDictType': def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args: T.List[str] = [] key = self.form_langopt_key('std') - std = options[key] - if std.value != 'none': + std = options.get_value(key) + if std != 'none': remap_cpp03 = { 'c++03': 'c++98', 'gnu++03': 'gnu++98' } - args.append('-std=' + remap_cpp03.get(std.value, std.value)) - if options[key.evolve('eh')].value == 'none': + args.append('-std=' + remap_cpp03.get(std.value, std)) + if options.get_value(key.evolve('eh')) == 'none': args.append('-fno-exceptions') - if not options[key.evolve('rtti')].value: + if not options.get_value(key.evolve('rtti')): args.append('-fno-rtti') - if options[key.evolve('debugstl')].value: + if options.get_value(key.evolve('debugstl')): args.append('-D_GLIBCXX_DEBUG=1') return args @@ -733,7 +733,7 @@ class VisualStudioLikeCPPCompilerMixin(CompilerMixinBase): def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: # need a typeddict for this key = self.form_langopt_key('winlibs') - return T.cast('T.List[str]', options[key].value[:]) + return T.cast('T.List[str]', options.get_value(key)[:]) def _get_options_impl(self, opts: 'MutableKeyedOptionDictType', cpp_stds: T.List[str]) -> 'MutableKeyedOptionDictType': key = self.form_langopt_key('std') @@ -762,18 +762,18 @@ def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str] args: T.List[str] = [] key = self.form_langopt_key('std') - eh = options[self.form_langopt_key('eh')] - if eh.value == 'default': + eh = options.get_value(self.form_langopt_key('eh')) + if eh == 'default': args.append('/EHsc') - elif eh.value == 'none': + elif eh == 'none': args.append('/EHs-c-') else: - args.append('/EH' + eh.value) + args.append('/EH' + eh) - if not options[self.form_langopt_key('rtti')].value: + if not options.get_value(self.form_langopt_key('rtti')): args.append('/GR-') - permissive, ver = self.VC_VERSION_MAP[options[key].value] + permissive, ver = self.VC_VERSION_MAP[options.get_value(key)] if ver is not None: args.append(f'/std:c++{ver}') @@ -801,7 +801,7 @@ def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str] # (i.e., after VS2015U3) # if one is using anything before that point, one cannot set the standard. key = self.form_langopt_key('std') - if options[key].value in {'vc++11', 'c++11'}: + if options.get_value(key) in {'vc++11', 'c++11'}: mlog.warning(self.id, 'does not support C++11;', 'attempting best effort; setting the standard to C++14', once=True, fatal=False) @@ -809,10 +809,10 @@ def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str] # deepcopy since we're messing with members, and we can't simply # copy the members because the option proxy doesn't support it. options = copy.deepcopy(options) - if options[key].value == 'vc++11': - options[key].value = 'vc++14' + if options.get_value(key) == 'vc++11': + options.set_value(key,'vc++14') else: - options[key].value = 'c++14' + options.set_value(key, 'c++14') return super().get_option_compile_args(options) @@ -848,10 +848,10 @@ def get_options(self) -> 'MutableKeyedOptionDictType': def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: key = self.form_langopt_key('std') - if options[key].value != 'none' and version_compare(self.version, '<19.00.24210'): + if options.get_value(key) != 'none' and version_compare(self.version, '<19.00.24210'): mlog.warning('This version of MSVC does not support cpp_std arguments', fatal=False) options = copy.copy(options) - options[key].value = 'none' + options.set_value(key, 'none') args = super().get_option_compile_args(options) @@ -924,10 +924,10 @@ def get_options(self) -> 'MutableKeyedOptionDictType': def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args: T.List[str] = [] key = self.form_langopt_key('std') - std = options[key] - if std.value == 'c++11': + std = options.get_value(key) + if std == 'c++11': args.append('--cpp11') - elif std.value == 'c++03': + elif std == 'c++03': args.append('--cpp') return args @@ -986,9 +986,9 @@ def get_options(self) -> 'MutableKeyedOptionDictType': def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args: T.List[str] = [] key = self.form_langopt_key('std') - std = options[key] - if std.value != 'none': - args.append('--' + std.value) + std = options.get_value(key) + if std != 'none': + args.append('--' + std) return args def get_always_args(self) -> T.List[str]: @@ -1027,10 +1027,10 @@ def get_options(self) -> 'MutableKeyedOptionDictType': def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args: T.List[str] = [] key = self.form_langopt_key('std') - std = options[key] - if std.value != 'none': + std = options.get_value(key) + if std != 'none': args.append('-lang') - args.append(std.value) + args.append(std) return args class MetrowerksCPPCompilerEmbeddedPowerPC(MetrowerksCompiler, CPPCompiler): @@ -1056,7 +1056,7 @@ def get_options(self) -> 'MutableKeyedOptionDictType': def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args: T.List[str] = [] key = self.form_langopt_key('std') - std = options[key] - if std.value != 'none': - args.append('-lang ' + std.value) + std = options.get_value(key) + if std != 'none': + args.append('-lang ' + std) return args diff --git a/mesonbuild/compilers/cuda.py b/mesonbuild/compilers/cuda.py index 5a935515d74d..2e9218e93d16 100644 --- a/mesonbuild/compilers/cuda.py +++ b/mesonbuild/compilers/cuda.py @@ -13,7 +13,7 @@ from .. import mlog from ..mesonlib import ( EnvironmentException, Popen_safe, - is_windows, LibType, version_compare, + is_windows, LibType, version_compare, OptionKey ) from .compilers import Compiler @@ -655,15 +655,15 @@ def get_options(self) -> 'MutableKeyedOptionDictType': ''), ) - def _to_host_compiler_options(self, options: 'KeyedOptionDictType') -> 'KeyedOptionDictType': + def _to_host_compiler_options(self, master_options: 'KeyedOptionDictType') -> 'KeyedOptionDictType': """ Convert an NVCC Option set to a host compiler's option set. """ # We must strip the -std option from the host compiler option set, as NVCC has # its own -std flag that may not agree with the host compiler's. - host_options = {key: options.get(key, opt) for key, opt in self.host_compiler.get_options().items()} - std_key = self.form_langopt_key('std') + host_options = {key: master_options.get(key, opt) for key, opt in self.host_compiler.get_options().items()} + std_key = OptionKey('std', machine=self.for_machine, lang=self.host_compiler.language) overrides = {std_key: 'none'} return coredata.OptionsView(host_options, overrides=overrides) @@ -674,9 +674,9 @@ def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str] # and attempting to use it will result in a warning: https://stackoverflow.com/a/51272091/741027 if not is_windows(): key = self.form_langopt_key('std') - std = options[key] - if std.value != 'none': - args.append('--std=' + std.value) + std = options.get_value(key) + if std != 'none': + args.append('--std=' + std) return args + self._to_host_flags(self.host_compiler.get_option_compile_args(self._to_host_compiler_options(options))) @@ -792,9 +792,9 @@ def get_dependency_compile_args(self, dep: 'Dependency') -> T.List[str]: def get_dependency_link_args(self, dep: 'Dependency') -> T.List[str]: return self._to_host_flags(super().get_dependency_link_args(dep), _Phase.LINKER) - def get_ccbin_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + def get_ccbin_args(self, ccoptions: 'KeyedOptionDictType') -> T.List[str]: key = self.form_langopt_key('ccbindir') - ccbindir = options[key].value + ccbindir = ccoptions.get_value(key) if isinstance(ccbindir, str) and ccbindir != '': return [self._shield_nvcc_list_arg('-ccbin='+ccbindir, False)] else: diff --git a/mesonbuild/compilers/cython.py b/mesonbuild/compilers/cython.py index 76e66c0b7ae1..7c1128692e40 100644 --- a/mesonbuild/compilers/cython.py +++ b/mesonbuild/compilers/cython.py @@ -84,10 +84,10 @@ def get_options(self) -> 'MutableKeyedOptionDictType': def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args: T.List[str] = [] key = self.form_langopt_key('version') - version = options[key] - args.append(f'-{version.value}') + version = options.get_value(key) + args.append(f'-{version}') key = self.form_langopt_key('language') - lang = options[key] - if lang.value == 'cpp': + lang = options.get_value(key) + if lang == 'cpp': args.append('--cplus') return args diff --git a/mesonbuild/compilers/fortran.py b/mesonbuild/compilers/fortran.py index 66fb466a5485..9b288e99bfb8 100644 --- a/mesonbuild/compilers/fortran.py +++ b/mesonbuild/compilers/fortran.py @@ -154,9 +154,9 @@ def get_options(self) -> 'MutableKeyedOptionDictType': def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args: T.List[str] = [] key = self.form_langopt_key('std') - std = options[key] - if std.value != 'none': - args.append('-std=' + std.value) + std = options.get_value(key) + if std != 'none': + args.append('-std=' + std) return args def get_dependency_gen_args(self, outtarget: str, outfile: str) -> T.List[str]: @@ -291,10 +291,10 @@ def get_options(self) -> 'MutableKeyedOptionDictType': def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args: T.List[str] = [] key = self.form_langopt_key('std') - std = options[key] + std = options.get_value(key) stds = {'legacy': 'none', 'f95': 'f95', 'f2003': 'f03', 'f2008': 'f08', 'f2018': 'f18'} - if std.value != 'none': - args.append('-stand=' + stds[std.value]) + if std != 'none': + args.append('-stand=' + stds[std]) return args def get_preprocess_only_args(self) -> T.List[str]: @@ -346,10 +346,10 @@ def get_options(self) -> 'MutableKeyedOptionDictType': def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args: T.List[str] = [] key = self.form_langopt_key('std') - std = options[key] + std = options.get_value(key) stds = {'legacy': 'none', 'f95': 'f95', 'f2003': 'f03', 'f2008': 'f08', 'f2018': 'f18'} - if std.value != 'none': - args.append('/stand:' + stds[std.value]) + if std != 'none': + args.append('/stand:' + stds[std]) return args def get_werror_args(self) -> T.List[str]: diff --git a/mesonbuild/compilers/mixins/clike.py b/mesonbuild/compilers/mixins/clike.py index 87cb81997e7d..70e81a4b85d9 100644 --- a/mesonbuild/compilers/mixins/clike.py +++ b/mesonbuild/compilers/mixins/clike.py @@ -26,7 +26,7 @@ from ... import mesonlib from ... import mlog from ...linkers.linkers import GnuLikeDynamicLinkerMixin, SolarisDynamicLinker, CompCertDynamicLinker -from ...mesonlib import LibType, OptionKey +from ...mesonlib import LibType from .. import compilers from ..compilers import CompileCheckMode from .visualstudio import VisualStudioLikeCompiler @@ -376,8 +376,8 @@ def _get_basic_compiler_args(self, env: 'Environment', mode: CompileCheckMode) - # linking with static libraries since MSVC won't select a CRT for # us in that case and will error out asking us to pick one. try: - crt_val = env.coredata.optstore[OptionKey('b_vscrt')].value - buildtype = env.coredata.optstore[OptionKey('buildtype')].value + crt_val = env.coredata.optstore.get_value('b_vscrt') + buildtype = env.coredata.optstore.get_value('buildtype') cargs += self.get_crt_compile_args(crt_val, buildtype) except (KeyError, AttributeError): pass diff --git a/mesonbuild/compilers/mixins/emscripten.py b/mesonbuild/compilers/mixins/emscripten.py index 8d3dc95bd88f..6b7f087ba6e1 100644 --- a/mesonbuild/compilers/mixins/emscripten.py +++ b/mesonbuild/compilers/mixins/emscripten.py @@ -51,7 +51,7 @@ def _get_compile_output(self, dirname: str, mode: CompileCheckMode) -> str: def thread_link_flags(self, env: 'Environment') -> T.List[str]: args = ['-pthread'] - count: int = env.coredata.optstore[OptionKey('thread_count', lang=self.language, machine=self.for_machine)].value + count: int = env.coredata.optstore.get_value(OptionKey('thread_count', lang=self.language, machine=self.for_machine)) if count: args.append(f'-sPTHREAD_POOL_SIZE={count}') return args diff --git a/mesonbuild/compilers/objc.py b/mesonbuild/compilers/objc.py index 4d33ec8b23b7..c63f288e314a 100644 --- a/mesonbuild/compilers/objc.py +++ b/mesonbuild/compilers/objc.py @@ -90,9 +90,9 @@ def get_options(self) -> 'coredata.MutableKeyedOptionDictType': def get_option_compile_args(self, options: 'coredata.KeyedOptionDictType') -> T.List[str]: args = [] - std = options[OptionKey('std', machine=self.for_machine, lang='c')] - if std.value != 'none': - args.append('-std=' + std.value) + std = options.get_value(OptionKey('std', machine=self.for_machine, lang='c')) + if std != 'none': + args.append('-std=' + std) return args class AppleClangObjCCompiler(ClangObjCCompiler): diff --git a/mesonbuild/compilers/objcpp.py b/mesonbuild/compilers/objcpp.py index e28e3ed30a41..e24406c32cc5 100644 --- a/mesonbuild/compilers/objcpp.py +++ b/mesonbuild/compilers/objcpp.py @@ -92,9 +92,9 @@ def get_options(self) -> coredata.MutableKeyedOptionDictType: def get_option_compile_args(self, options: 'coredata.KeyedOptionDictType') -> T.List[str]: args = [] - std = options[OptionKey('std', machine=self.for_machine, lang='cpp')] - if std.value != 'none': - args.append('-std=' + std.value) + std = options.get_value(OptionKey('std', machine=self.for_machine, lang='cpp')) + if std != 'none': + args.append('-std=' + std) return args diff --git a/mesonbuild/compilers/rust.py b/mesonbuild/compilers/rust.py index 346a3dd69460..b130c58ee307 100644 --- a/mesonbuild/compilers/rust.py +++ b/mesonbuild/compilers/rust.py @@ -173,9 +173,9 @@ def get_dependency_compile_args(self, dep: 'Dependency') -> T.List[str]: def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] key = self.form_langopt_key('std') - std = options[key] - if std.value != 'none': - args.append('--edition=' + std.value) + std = options.get_value(key) + if std != 'none': + args.append('--edition=' + std) return args def get_crt_compile_args(self, crt_val: str, buildtype: str) -> T.List[str]: diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index 8804547e18fa..60f3574672ac 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -58,7 +58,7 @@ class SharedCMDOptions(Protocol): OptionDictType = T.Union[T.Dict[str, 'options.UserOption[T.Any]'], 'OptionsView'] MutableKeyedOptionDictType = T.Dict['OptionKey', 'options.UserOption[T.Any]'] - KeyedOptionDictType = T.Union['options.OptionStore', MutableKeyedOptionDictType, 'OptionsView'] + KeyedOptionDictType = T.Union['options.OptionStore', 'OptionsView'] CompilerCheckCacheKey = T.Tuple[T.Tuple[str, ...], str, FileOrString, T.Tuple[str, ...], CompileCheckMode] # code, args RunCheckCacheKey = T.Tuple[str, T.Tuple[str, ...]] @@ -150,8 +150,8 @@ def __init__(self, builtins: 'KeyedOptionDictType', for_machine: MachineChoice): def __calculate_subkey(self, type_: DependencyCacheType) -> T.Tuple[str, ...]: data: T.Dict[DependencyCacheType, T.List[str]] = { - DependencyCacheType.PKG_CONFIG: stringlistify(self.__builtins[self.__pkg_conf_key].value), - DependencyCacheType.CMAKE: stringlistify(self.__builtins[self.__cmake_key].value), + DependencyCacheType.PKG_CONFIG: stringlistify(self.__builtins.get_value(self.__pkg_conf_key)), + DependencyCacheType.CMAKE: stringlistify(self.__builtins.get_value(self.__cmake_key)), DependencyCacheType.OTHER: [], } assert type_ in data, 'Someone forgot to update subkey calculations for a new type' @@ -415,33 +415,33 @@ def add_builtin_option(opts_map: 'MutableKeyedOptionDictType', key: OptionKey, if opt.yielding: # This option is global and not per-subproject return - value = opts_map[key.as_root()].value + value = opts_map.get_value(key.as_root()) else: value = None - opts_map[key] = opt.init_option(key, value, options.default_prefix()) + opts_map.add_system_option(key, opt.init_option(key, value, options.default_prefix())) def init_backend_options(self, backend_name: str) -> None: if backend_name == 'ninja': - self.optstore[OptionKey('backend_max_links')] = options.UserIntegerOption( + self.optstore.add_system_option('backend_max_links', options.UserIntegerOption( 'backend_max_links', 'Maximum number of linker processes to run or 0 for no ' 'limit', - (0, None, 0)) + (0, None, 0))) elif backend_name.startswith('vs'): - self.optstore[OptionKey('backend_startup_project')] = options.UserStringOption( + self.optstore.add_system_option('backend_startup_project', options.UserStringOption( 'backend_startup_project', 'Default project to execute in Visual Studio', - '') + '')) def get_option(self, key: OptionKey) -> T.Union[T.List[str], str, int, bool]: try: - v = self.optstore[key].value + v = self.optstore.get_value(key) return v except KeyError: pass try: - v = self.optstore[key.as_root()] + v = self.optstore.get_value_object(key.as_root()) if v.yielding: return v.value except KeyError: @@ -455,11 +455,11 @@ def set_option(self, key: OptionKey, value, first_invocation: bool = False) -> b if key.name == 'prefix': value = self.sanitize_prefix(value) else: - prefix = self.optstore[OptionKey('prefix')].value + prefix = self.optstore.get_value('prefix') value = self.sanitize_dir_option_value(prefix, key, value) try: - opt = self.optstore[key] + opt = self.optstore.get_value_object(key) except KeyError: raise MesonException(f'Tried to set unknown builtin option {str(key)}') @@ -510,7 +510,7 @@ def clear_cache(self) -> None: def get_nondefault_buildtype_args(self) -> T.List[T.Union[T.Tuple[str, str, str], T.Tuple[str, bool, bool]]]: result: T.List[T.Union[T.Tuple[str, str, str], T.Tuple[str, bool, bool]]] = [] - value = self.optstore[OptionKey('buildtype')].value + value = self.optstore.get_value('buildtype') if value == 'plain': opt = 'plain' debug = False @@ -529,8 +529,8 @@ def get_nondefault_buildtype_args(self) -> T.List[T.Union[T.Tuple[str, str, str] else: assert value == 'custom' return [] - actual_opt = self.optstore[OptionKey('optimization')].value - actual_debug = self.optstore[OptionKey('debug')].value + actual_opt = self.optstore.get_value('optimization') + actual_debug = self.optstore.get_value('debug') if actual_opt != opt: result.append(('optimization', actual_opt, opt)) if actual_debug != debug: @@ -559,8 +559,8 @@ def _set_others_from_buildtype(self, value: str) -> bool: assert value == 'custom' return False - dirty |= self.optstore[OptionKey('optimization')].set_value(opt) - dirty |= self.optstore[OptionKey('debug')].set_value(debug) + dirty |= self.optstore.set_value('optimization', opt) + dirty |= self.optstore.set_value('debug', debug) return dirty @@ -572,30 +572,32 @@ def is_per_machine_option(optname: OptionKey) -> bool: def get_external_args(self, for_machine: MachineChoice, lang: str) -> T.List[str]: # mypy cannot analyze type of OptionKey - return T.cast('T.List[str]', self.optstore[OptionKey('args', machine=for_machine, lang=lang)].value) + key = OptionKey('args', machine=for_machine, lang=lang) + return T.cast('T.List[str]', self.optstore.get_value(key)) def get_external_link_args(self, for_machine: MachineChoice, lang: str) -> T.List[str]: # mypy cannot analyze type of OptionKey - return T.cast('T.List[str]', self.optstore[OptionKey('link_args', machine=for_machine, lang=lang)].value) + key = OptionKey('link_args', machine=for_machine, lang=lang) + return T.cast('T.List[str]', self.optstore.get_value(key)) - def update_project_options(self, options: 'MutableKeyedOptionDictType', subproject: SubProject) -> None: - for key, value in options.items(): + def update_project_options(self, project_options: 'MutableKeyedOptionDictType', subproject: SubProject) -> None: + for key, value in project_options.items(): if not key.is_project(): continue if key not in self.optstore: - self.optstore[key] = value + self.optstore.add_project_option(key, value) continue if key.subproject != subproject: raise MesonBugException(f'Tried to set an option for subproject {key.subproject} from {subproject}!') - oldval = self.optstore[key] + oldval = self.optstore.get_value_object(key) if type(oldval) is not type(value): - self.optstore[key] = value + self.optstore.set_value(key, value.value) elif oldval.choices != value.choices: # If the choices have changed, use the new value, but attempt # to keep the old options. If they are not valid keep the new # defaults but warn. - self.optstore[key] = value + self.optstore.set_value_object(key, value) try: value.set_value(oldval.value) except MesonException: @@ -603,9 +605,9 @@ def update_project_options(self, options: 'MutableKeyedOptionDictType', subproje fatal=False) # Find any extranious keys for this project and remove them - for key in self.optstore.keys() - options.keys(): + for key in self.optstore.keys() - project_options.keys(): if key.is_project() and key.subproject == subproject: - del self.optstore[key] + self.optstore.remove(key) def is_cross_build(self, when_building_for: MachineChoice = MachineChoice.HOST) -> bool: if when_building_for == MachineChoice.BUILD: @@ -616,13 +618,13 @@ def copy_build_options_from_regular_ones(self) -> bool: dirty = False assert not self.is_cross_build() for k in options.BUILTIN_OPTIONS_PER_MACHINE: - o = self.optstore[k] - dirty |= self.optstore[k.as_build()].set_value(o.value) + o = self.optstore.get_value_object(k) + dirty |= self.optstore.set_value(k.as_build(), o.value) for bk, bv in self.optstore.items(): if bk.machine is MachineChoice.BUILD: hk = bk.as_host() try: - hv = self.optstore[hk] + hv = self.optstore.get_value_object(hk) dirty |= bv.set_value(hv.value) except KeyError: continue @@ -637,10 +639,10 @@ def set_options(self, opts_to_set: T.Dict[OptionKey, T.Any], subproject: str = ' pfk = OptionKey('prefix') if pfk in opts_to_set: prefix = self.sanitize_prefix(opts_to_set[pfk]) - dirty |= self.optstore[OptionKey('prefix')].set_value(prefix) + dirty |= self.optstore.set_value('prefix', prefix) for key in options.BUILTIN_DIR_NOPREFIX_OPTIONS: if key not in opts_to_set: - dirty |= self.optstore[key].set_value(options.BUILTIN_OPTIONS[key].prefixed_default(key, prefix)) + dirty |= self.optstore.set_value(key, options.BUILTIN_OPTIONS[key].prefixed_default(key, prefix)) unknown_options: T.List[OptionKey] = [] for k, v in opts_to_set.items(): @@ -690,7 +692,7 @@ def set_default_options(self, default_options: T.MutableMapping[OptionKey, str], # Always test this using the HOST machine, as many builtin options # are not valid for the BUILD machine, but the yielding value does # not differ between them even when they are valid for both. - if subproject and k.is_builtin() and self.optstore[k.evolve(subproject='', machine=MachineChoice.HOST)].yielding: + if subproject and k.is_builtin() and self.optstore.get_value_object(k.evolve(subproject='', machine=MachineChoice.HOST)).yielding: continue # Skip base, compiler, and backend options, they are handled when # adding languages and setting backend. @@ -703,14 +705,14 @@ def set_default_options(self, default_options: T.MutableMapping[OptionKey, str], self.set_options(options, subproject=subproject, first_invocation=env.first_invocation) - def add_compiler_options(self, options: MutableKeyedOptionDictType, lang: str, for_machine: MachineChoice, + def add_compiler_options(self, c_options: MutableKeyedOptionDictType, lang: str, for_machine: MachineChoice, env: Environment, subproject: str) -> None: - for k, o in options.items(): + for k, o in c_options.items(): value = env.options.get(k) if value is not None: o.set_value(value) if not subproject: - self.optstore[k] = o # override compiler option on reconfigure + self.optstore.set_value_object(k, o) # override compiler option on reconfigure self.optstore.setdefault(k, o) if subproject: @@ -718,7 +720,7 @@ def add_compiler_options(self, options: MutableKeyedOptionDictType, lang: str, f value = env.options.get(sk) or value if value is not None: o.set_value(value) - self.optstore[sk] = o # override compiler option on reconfigure + self.optstore.set_value_object(sk, o) # override compiler option on reconfigure self.optstore.setdefault(sk, o) def add_lang_args(self, lang: str, comp: T.Type['Compiler'], @@ -742,19 +744,19 @@ def process_compiler_options(self, lang: str, comp: Compiler, env: Environment, else: skey = key if skey not in self.optstore: - self.optstore[skey] = copy.deepcopy(compilers.base_options[key]) + self.optstore.add_system_option(skey, copy.deepcopy(compilers.base_options[key])) if skey in env.options: - self.optstore[skey].set_value(env.options[skey]) + self.optstore.set_value(skey, env.options[skey]) enabled_opts.append(skey) elif subproject and key in env.options: - self.optstore[skey].set_value(env.options[key]) + self.optstore.set_value(skey, env.options[key]) enabled_opts.append(skey) if subproject and key not in self.optstore: - self.optstore[key] = copy.deepcopy(self.optstore[skey]) + self.optstore.add_system_option(key, copy.deepcopy(self.optstore.get_value_object(skey))) elif skey in env.options: - self.optstore[skey].set_value(env.options[skey]) + self.optstore.set_value(skey, env.options[skey]) elif subproject and key in env.options: - self.optstore[skey].set_value(env.options[key]) + self.optstore.set_value(skey, env.options[key]) self.emit_base_options_warnings(enabled_opts) def emit_base_options_warnings(self, enabled_opts: T.List[OptionKey]) -> None: @@ -905,7 +907,15 @@ def __getitem__(self, key: OptionKey) -> options.UserOption: if not key.is_project(): opt = self.original_options.get(key) if opt is None or opt.yielding: - opt = self.original_options[key.as_root()] + key2 = key.as_root() + # This hack goes away once wi start using OptionStore + # to hold overrides. + if isinstance(self.original_options, options.OptionStore): + if key2 not in self.original_options: + raise KeyError + opt = self.original_options.get_value_object(key2) + else: + opt = self.original_options[key2] else: opt = self.original_options[key] if opt.yielding: @@ -917,6 +927,16 @@ def __getitem__(self, key: OptionKey) -> options.UserOption: opt.set_value(override_value) return opt + def get_value(self, key: T.Union[str, OptionKey]): + if isinstance(key, str): + key = OptionKey(key) + return self[key].value + + def set_value(self, key: T.Union[str, OptionKey], value: T.Union[str, int, bool, T.List[str]]): + if isinstance(key, str): + key = OptionKey(key) + self.overrides[key] = value + def __iter__(self) -> T.Iterator[OptionKey]: return iter(self.original_options) diff --git a/mesonbuild/dependencies/boost.py b/mesonbuild/dependencies/boost.py index 39b3cfe159f1..02aa1ea85cce 100644 --- a/mesonbuild/dependencies/boost.py +++ b/mesonbuild/dependencies/boost.py @@ -580,8 +580,8 @@ def filter_libraries(self, libs: T.List[BoostLibraryFile], lib_vers: str) -> T.L # MSVC is very picky with the library tags vscrt = '' try: - crt_val = self.env.coredata.optstore[mesonlib.OptionKey('b_vscrt')].value - buildtype = self.env.coredata.optstore[mesonlib.OptionKey('buildtype')].value + crt_val = self.env.coredata.optstore.get_value('b_vscrt') + buildtype = self.env.coredata.optstore.get_value('buildtype') vscrt = self.clib_compiler.get_crt_compile_args(crt_val, buildtype)[0] except (KeyError, IndexError, AttributeError): pass diff --git a/mesonbuild/dependencies/pkgconfig.py b/mesonbuild/dependencies/pkgconfig.py index b6647b4a4099..a87f413ad9df 100644 --- a/mesonbuild/dependencies/pkgconfig.py +++ b/mesonbuild/dependencies/pkgconfig.py @@ -238,7 +238,7 @@ def _check_pkgconfig(self, pkgbin: ExternalProgram) -> T.Optional[str]: def _get_env(self, uninstalled: bool = False) -> EnvironmentVariables: env = EnvironmentVariables() key = OptionKey('pkg_config_path', machine=self.for_machine) - extra_paths: T.List[str] = self.env.coredata.optstore[key].value[:] + extra_paths: T.List[str] = self.env.coredata.optstore.get_value(key)[:] if uninstalled: uninstalled_path = Path(self.env.get_build_dir(), 'meson-uninstalled').as_posix() if uninstalled_path not in extra_paths: @@ -397,7 +397,7 @@ def _search_libs(self, libs_in: ImmutableListProtocol[str], raw_libs_in: Immutab # # Only prefix_libpaths are reordered here because there should not be # too many system_libpaths to cause library version issues. - pkg_config_path: T.List[str] = self.env.coredata.optstore[OptionKey('pkg_config_path', machine=self.for_machine)].value + pkg_config_path: T.List[str] = self.env.coredata.optstore.get_value(OptionKey('pkg_config_path', machine=self.for_machine)) pkg_config_path = self._convert_mingw_paths(pkg_config_path) prefix_libpaths = OrderedSet(sort_libpaths(list(prefix_libpaths), pkg_config_path)) system_libpaths: OrderedSet[str] = OrderedSet() diff --git a/mesonbuild/dependencies/python.py b/mesonbuild/dependencies/python.py index 2ec7b9d92210..27340ebaa3ec 100644 --- a/mesonbuild/dependencies/python.py +++ b/mesonbuild/dependencies/python.py @@ -297,7 +297,7 @@ def get_windows_link_args(self, limited_api: bool) -> T.Optional[T.List[str]]: is_debug_build = debug or buildtype == 'debug' vscrt_debug = False if mesonlib.OptionKey('b_vscrt') in self.env.coredata.optstore: - vscrt = self.env.coredata.optstore[mesonlib.OptionKey('b_vscrt')].value + vscrt = self.env.coredata.optstore.get_value('b_vscrt') if vscrt in {'mdd', 'mtd', 'from_buildtype', 'static_from_buildtype'}: vscrt_debug = True if is_debug_build and vscrt_debug and not self.variables.get('Py_DEBUG'): diff --git a/mesonbuild/dependencies/qt.py b/mesonbuild/dependencies/qt.py index cc80ce8f544c..86e32140e924 100644 --- a/mesonbuild/dependencies/qt.py +++ b/mesonbuild/dependencies/qt.py @@ -298,7 +298,7 @@ def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any]): # compiler supports it. is_debug = self.env.coredata.get_option(mesonlib.OptionKey('buildtype')) == 'debug' if mesonlib.OptionKey('b_vscrt') in self.env.coredata.optstore: - if self.env.coredata.optstore[mesonlib.OptionKey('b_vscrt')].value in {'mdd', 'mtd'}: + if self.env.coredata.optstore.get_value('b_vscrt') in {'mdd', 'mtd'}: is_debug = True modules_lib_suffix = _get_modules_lib_suffix(self.version, self.env.machines[self.for_machine], is_debug) diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index a423ed8509d9..ef28d864fb47 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -1013,7 +1013,7 @@ def _do_subproject_cmake(self, subp_name: str, subdir: str, kwargs: kwtypes.DoSubproject) -> SubprojectHolder: from ..cmake import CMakeInterpreter with mlog.nested(subp_name): - prefix = self.coredata.optstore[OptionKey('prefix')].value + prefix = self.coredata.optstore.get_value('prefix') from ..modules.cmake import CMakeSubprojectOptions options = kwargs.get('options') or CMakeSubprojectOptions() @@ -1061,9 +1061,9 @@ def get_option_internal(self, optname: str) -> options.UserOption: return v try: - opt = self.coredata.optstore[key] + opt = self.coredata.optstore.get_value_object(key) if opt.yielding and key.subproject and key.as_root() in self.coredata.optstore: - popt = self.coredata.optstore[key.as_root()] + popt = self.coredata.optstore.get_value_object(key.as_root()) if type(opt) is type(popt): opt = popt else: @@ -1543,7 +1543,7 @@ def add_languages_for(self, args: T.List[str], required: bool, for_machine: Mach if self.subproject: options = {} for k in comp.get_options(): - v = copy.copy(self.coredata.optstore[k]) + v = copy.copy(self.coredata.optstore.get_value_object(k)) k = k.evolve(subproject=self.subproject) options[k] = v self.coredata.add_compiler_options(options, lang, for_machine, self.environment, self.subproject) @@ -3045,9 +3045,9 @@ def check_clang_asan_lundef(self) -> None: return if OptionKey('b_sanitize') not in self.coredata.optstore: return - if (self.coredata.optstore[OptionKey('b_lundef')].value and - self.coredata.optstore[OptionKey('b_sanitize')].value != 'none'): - value = self.coredata.optstore[OptionKey('b_sanitize')].value + if (self.coredata.optstore.get_value('b_lundef') and + self.coredata.optstore.get_value('b_sanitize') != 'none'): + value = self.coredata.optstore.get_value('b_sanitize') mlog.warning(textwrap.dedent(f'''\ Trying to use {value} sanitizer on Clang with b_lundef. This will probably not work. diff --git a/mesonbuild/interpreter/interpreterobjects.py b/mesonbuild/interpreter/interpreterobjects.py index f5dafa736934..32f05bafb852 100644 --- a/mesonbuild/interpreter/interpreterobjects.py +++ b/mesonbuild/interpreter/interpreterobjects.py @@ -24,7 +24,7 @@ from ..interpreter.type_checking import NoneType, ENV_KW, ENV_SEPARATOR_KW, PKGCONFIG_DEFINE_KW from ..dependencies import Dependency, ExternalLibrary, InternalDependency from ..programs import ExternalProgram -from ..mesonlib import HoldableObject, OptionKey, listify, Popen_safe +from ..mesonlib import HoldableObject, listify, Popen_safe import typing as T @@ -90,7 +90,7 @@ def __init__(self, option: options.UserFeatureOption, interpreter: 'Interpreter' super().__init__(option, interpreter) if option and option.is_auto(): # TODO: we need to cast here because options is not a TypedDict - auto = T.cast('options.UserFeatureOption', self.env.coredata.optstore[OptionKey('auto_features')]) + auto = T.cast('options.UserFeatureOption', self.env.coredata.optstore.get_value_object('auto_features')) self.held_object = copy.copy(auto) self.held_object.name = option.name self.methods.update({'enabled': self.enabled_method, diff --git a/mesonbuild/mconf.py b/mesonbuild/mconf.py index 0ed7c927f1c6..1f167df3e2e0 100644 --- a/mesonbuild/mconf.py +++ b/mesonbuild/mconf.py @@ -229,7 +229,7 @@ def print_options(self, title: str, opts: 'coredata.KeyedOptionDictType') -> Non return if title: self.add_title(title) - auto = T.cast('options.UserFeatureOption', self.coredata.optstore[OptionKey('auto_features')]) + auto = T.cast('options.UserFeatureOption', self.coredata.optstore.get_value_object('auto_features')) for k, o in sorted(opts.items()): printable_value = o.printable_value() root = k.as_root() diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py index f46e4f520e5f..cab7c76a3d86 100644 --- a/mesonbuild/modules/gnome.py +++ b/mesonbuild/modules/gnome.py @@ -906,7 +906,7 @@ def _get_langs_compilers_flags(state: 'ModuleState', langs_compilers: T.List[T.T if state.project_args.get(lang): cflags += state.project_args[lang] if mesonlib.OptionKey('b_sanitize') in compiler.base_options: - sanitize = state.environment.coredata.optstore[mesonlib.OptionKey('b_sanitize')].value + sanitize = state.environment.coredata.optstore.get_value('b_sanitize') cflags += compiler.sanitizer_compile_args(sanitize) sanitize = sanitize.split(',') # These must be first in ldflags diff --git a/mesonbuild/modules/python.py b/mesonbuild/modules/python.py index 3f7affe5e50c..b599a5eca0a0 100644 --- a/mesonbuild/modules/python.py +++ b/mesonbuild/modules/python.py @@ -205,7 +205,7 @@ def extension_module_method(self, args: T.Tuple[str, T.List[BuildTargetSource]], new_link_args = mesonlib.extract_as_list(kwargs, 'link_args') - is_debug = self.interpreter.environment.coredata.optstore[OptionKey('debug')].value + is_debug = self.interpreter.environment.coredata.optstore.get_value('debug') if is_debug: new_link_args.append(python_windows_debug_link_exception) else: diff --git a/mesonbuild/msetup.py b/mesonbuild/msetup.py index 8f561e43875e..931b1eba342a 100644 --- a/mesonbuild/msetup.py +++ b/mesonbuild/msetup.py @@ -273,9 +273,9 @@ def _generate(self, env: environment.Environment, capture: bool, vslite_ctx: T.O # collect warnings about unsupported build configurations; must be done after full arg processing # by Interpreter() init, but this is most visible at the end - if env.coredata.optstore[mesonlib.OptionKey('backend')].value == 'xcode': + if env.coredata.optstore.get_value('backend') == 'xcode': mlog.warning('xcode backend is currently unmaintained, patches welcome') - if env.coredata.optstore[mesonlib.OptionKey('layout')].value == 'flat': + if env.coredata.optstore.get_value('layout') == 'flat': mlog.warning('-Dlayout=flat is unsupported and probably broken. It was a failed experiment at ' 'making Windows build artifacts runnable while uninstalled, due to PATH considerations, ' 'but was untested by CI and anyways breaks reasonable use of conflicting targets in different subdirs. ' diff --git a/mesonbuild/options.py b/mesonbuild/options.py index 6ffcf1a278c0..435c53def424 100644 --- a/mesonbuild/options.py +++ b/mesonbuild/options.py @@ -481,16 +481,40 @@ def __init__(self): def __len__(self): return len(self.d) - def __setitem__(self, key, value): - self.d[key] = value + def ensure_key(self,key: T.Union[OptionKey, str]) -> OptionKey: + if isinstance(key, str): + return OptionKey(key) + return key - def __getitem__(self, key): - return self.d[key] + def get_value_object(self, key: T.Union[OptionKey, str]) -> 'UserOption[T.Any]': + return self.d[self.ensure_key(key)] - def __delitem__(self, key): + def get_value(self, key: T.Union[OptionKey, str]) -> 'T.Any': + return self.get_value_object(key).value + + def add_system_option(self, key: T.Union[OptionKey, str], valobj: 'UserOption[T.Any'): + key = self.ensure_key(key) + self.d[key] = valobj + + def add_project_option(self, key: T.Union[OptionKey, str], valobj: 'UserOption[T.Any]'): + key = self.ensure_key(key) + self.d[key] = valobj + + def set_value(self, key: T.Union[OptionKey, str], new_value: 'T.Any') -> bool: + key = self.ensure_key(key) + return self.d[key].set_value(new_value) + + # FIXME, this should be removed.or renamed to "change_type_of_existing_object" or something like that + def set_value_object(self, key: T.Union[OptionKey, str], new_object: 'UserOption[T.Any]') -> bool: + key = self.ensure_key(key) + self.d[key] = new_object + + + def remove(self, key): del self.d[key] def __contains__(self, key): + key = self.ensure_key(key) return key in self.d def __repr__(self): diff --git a/run_tests.py b/run_tests.py index 5b229d790d44..8ab53a1a5c80 100755 --- a/run_tests.py +++ b/run_tests.py @@ -151,7 +151,7 @@ def get_fake_env(sdir='', bdir=None, prefix='', opts=None): if opts is None: opts = get_fake_options(prefix) env = Environment(sdir, bdir, opts) - env.coredata.optstore[OptionKey('args', lang='c')] = FakeCompilerOptions() + env.coredata.optstore.set_value_object(OptionKey('args', lang='c'), FakeCompilerOptions()) env.machines.host.cpu_family = 'x86_64' # Used on macOS inside find_library # Invalidate cache when using a different Environment object. clear_meson_configure_class_caches() diff --git a/unittests/allplatformstests.py b/unittests/allplatformstests.py index 042f2107c93b..201dae695901 100644 --- a/unittests/allplatformstests.py +++ b/unittests/allplatformstests.py @@ -2621,35 +2621,35 @@ def test_command_line(self): out = self.init(testdir, extra_args=['--profile-self', '--fatal-meson-warnings']) self.assertNotIn('[default: true]', out) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.optstore[OptionKey('default_library')].value, 'static') - self.assertEqual(obj.optstore[OptionKey('warning_level')].value, '1') - self.assertEqual(obj.optstore[OptionKey('set_sub_opt')].value, True) - self.assertEqual(obj.optstore[OptionKey('subp_opt', 'subp')].value, 'default3') + self.assertEqual(obj.optstore.get_value('default_library'), 'static') + self.assertEqual(obj.optstore.get_value('warning_level'), '1') + self.assertEqual(obj.optstore.get_value('set_sub_opt'), True) + self.assertEqual(obj.optstore.get_value(OptionKey('subp_opt', 'subp')), 'default3') self.wipe() # warning_level is special, it's --warnlevel instead of --warning-level # for historical reasons self.init(testdir, extra_args=['--warnlevel=2', '--fatal-meson-warnings']) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.optstore[OptionKey('warning_level')].value, '2') + self.assertEqual(obj.optstore.get_value('warning_level'), '2') self.setconf('--warnlevel=3') obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.optstore[OptionKey('warning_level')].value, '3') + self.assertEqual(obj.optstore.get_value('warning_level'), '3') self.setconf('--warnlevel=everything') obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.optstore[OptionKey('warning_level')].value, 'everything') + self.assertEqual(obj.optstore.get_value('warning_level'), 'everything') self.wipe() # But when using -D syntax, it should be 'warning_level' self.init(testdir, extra_args=['-Dwarning_level=2', '--fatal-meson-warnings']) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.optstore[OptionKey('warning_level')].value, '2') + self.assertEqual(obj.optstore.get_value('warning_level'), '2') self.setconf('-Dwarning_level=3') obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.optstore[OptionKey('warning_level')].value, '3') + self.assertEqual(obj.optstore.get_value('warning_level'), '3') self.setconf('-Dwarning_level=everything') obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.optstore[OptionKey('warning_level')].value, 'everything') + self.assertEqual(obj.optstore.get_value('warning_level'), 'everything') self.wipe() # Mixing --option and -Doption is forbidden @@ -2673,15 +2673,15 @@ def test_command_line(self): # --default-library should override default value from project() self.init(testdir, extra_args=['--default-library=both', '--fatal-meson-warnings']) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.optstore[OptionKey('default_library')].value, 'both') + self.assertEqual(obj.optstore.get_value('default_library'), 'both') self.setconf('--default-library=shared') obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.optstore[OptionKey('default_library')].value, 'shared') + self.assertEqual(obj.optstore.get_value('default_library'), 'shared') if self.backend is Backend.ninja: # reconfigure target works only with ninja backend self.build('reconfigure') obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.optstore[OptionKey('default_library')].value, 'shared') + self.assertEqual(obj.optstore.get_value('default_library'), 'shared') self.wipe() # Should fail on unknown options @@ -2718,22 +2718,22 @@ def test_command_line(self): # Test we can set subproject option self.init(testdir, extra_args=['-Dsubp:subp_opt=foo', '--fatal-meson-warnings']) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.optstore[OptionKey('subp_opt', 'subp')].value, 'foo') + self.assertEqual(obj.optstore.get_value(OptionKey('subp_opt', 'subp')), 'foo') self.wipe() # c_args value should be parsed with split_args self.init(testdir, extra_args=['-Dc_args=-Dfoo -Dbar "-Dthird=one two"', '--fatal-meson-warnings']) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.optstore[OptionKey('args', lang='c')].value, ['-Dfoo', '-Dbar', '-Dthird=one two']) + self.assertEqual(obj.optstore.get_value(OptionKey('args', lang='c')), ['-Dfoo', '-Dbar', '-Dthird=one two']) self.setconf('-Dc_args="foo bar" one two') obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.optstore[OptionKey('args', lang='c')].value, ['foo bar', 'one', 'two']) + self.assertEqual(obj.optstore.get_value(OptionKey('args', lang='c')), ['foo bar', 'one', 'two']) self.wipe() self.init(testdir, extra_args=['-Dset_percent_opt=myoption%', '--fatal-meson-warnings']) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.optstore[OptionKey('set_percent_opt')].value, 'myoption%') + self.assertEqual(obj.optstore.get_value('set_percent_opt'), 'myoption%') self.wipe() # Setting a 2nd time the same option should override the first value @@ -2744,19 +2744,19 @@ def test_command_line(self): '-Dc_args=-Dfoo', '-Dc_args=-Dbar', '-Db_lundef=false', '--fatal-meson-warnings']) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.optstore[OptionKey('bindir')].value, 'bar') - self.assertEqual(obj.optstore[OptionKey('buildtype')].value, 'release') - self.assertEqual(obj.optstore[OptionKey('b_sanitize')].value, 'thread') - self.assertEqual(obj.optstore[OptionKey('args', lang='c')].value, ['-Dbar']) + self.assertEqual(obj.optstore.get_value('bindir'), 'bar') + self.assertEqual(obj.optstore.get_value('buildtype'), 'release') + self.assertEqual(obj.optstore.get_value('b_sanitize'), 'thread') + self.assertEqual(obj.optstore.get_value(OptionKey('args', lang='c')), ['-Dbar']) self.setconf(['--bindir=bar', '--bindir=foo', '-Dbuildtype=release', '-Dbuildtype=plain', '-Db_sanitize=thread', '-Db_sanitize=address', '-Dc_args=-Dbar', '-Dc_args=-Dfoo']) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.optstore[OptionKey('bindir')].value, 'foo') - self.assertEqual(obj.optstore[OptionKey('buildtype')].value, 'plain') - self.assertEqual(obj.optstore[OptionKey('b_sanitize')].value, 'address') - self.assertEqual(obj.optstore[OptionKey('args', lang='c')].value, ['-Dfoo']) + self.assertEqual(obj.optstore.get_value('bindir'), 'foo') + self.assertEqual(obj.optstore.get_value('buildtype'), 'plain') + self.assertEqual(obj.optstore.get_value('b_sanitize'), 'address') + self.assertEqual(obj.optstore.get_value(OptionKey('args', lang='c')), ['-Dfoo']) self.wipe() except KeyError: # Ignore KeyError, it happens on CI for compilers that does not @@ -2770,25 +2770,25 @@ def test_warning_level_0(self): # Verify default values when passing no args self.init(testdir) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.optstore[OptionKey('warning_level')].value, '0') + self.assertEqual(obj.optstore.get_value('warning_level'), '0') self.wipe() # verify we can override w/ --warnlevel self.init(testdir, extra_args=['--warnlevel=1']) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.optstore[OptionKey('warning_level')].value, '1') + self.assertEqual(obj.optstore.get_value('warning_level'), '1') self.setconf('--warnlevel=0') obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.optstore[OptionKey('warning_level')].value, '0') + self.assertEqual(obj.optstore.get_value('warning_level'), '0') self.wipe() # verify we can override w/ -Dwarning_level self.init(testdir, extra_args=['-Dwarning_level=1']) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.optstore[OptionKey('warning_level')].value, '1') + self.assertEqual(obj.optstore.get_value('warning_level'), '1') self.setconf('-Dwarning_level=0') obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.optstore[OptionKey('warning_level')].value, '0') + self.assertEqual(obj.optstore.get_value('warning_level'), '0') self.wipe() def test_feature_check_usage_subprojects(self): diff --git a/unittests/datatests.py b/unittests/datatests.py index b596446290f0..b7b2d322a5ed 100644 --- a/unittests/datatests.py +++ b/unittests/datatests.py @@ -163,9 +163,9 @@ def test_builtin_options_documented(self): else: raise RuntimeError(f'Invalid debug value {debug!r} in row:\n{m.group()}') env.coredata.set_option(OptionKey('buildtype'), buildtype) - self.assertEqual(env.coredata.optstore[OptionKey('buildtype')].value, buildtype) - self.assertEqual(env.coredata.optstore[OptionKey('optimization')].value, opt) - self.assertEqual(env.coredata.optstore[OptionKey('debug')].value, debug) + self.assertEqual(env.coredata.optstore.get_value('buildtype'), buildtype) + self.assertEqual(env.coredata.optstore.get_value('optimization'), opt) + self.assertEqual(env.coredata.optstore.get_value('debug'), debug) def test_cpu_families_documented(self): with open("docs/markdown/Reference-tables.md", encoding='utf-8') as f: diff --git a/unittests/internaltests.py b/unittests/internaltests.py index 42cc41ec5d3b..fe9f0d4f5cfa 100644 --- a/unittests/internaltests.py +++ b/unittests/internaltests.py @@ -625,7 +625,7 @@ def create_static_lib(name): env = get_fake_env() compiler = detect_c_compiler(env, MachineChoice.HOST) env.coredata.compilers.host = {'c': compiler} - env.coredata.optstore[OptionKey('link_args', lang='c')] = FakeCompilerOptions() + env.coredata.optstore.set_value_object(OptionKey('link_args', lang='c'), FakeCompilerOptions()) p1 = Path(tmpdir) / '1' p2 = Path(tmpdir) / '2' p1.mkdir() diff --git a/unittests/linuxliketests.py b/unittests/linuxliketests.py index 1e8038fa03d6..ea86784892f3 100644 --- a/unittests/linuxliketests.py +++ b/unittests/linuxliketests.py @@ -1124,7 +1124,7 @@ def test_pkgconfig_duplicate_path_entries(self): # option, adding the meson-uninstalled directory to it. PkgConfigInterface.setup_env({}, env, MachineChoice.HOST, uninstalled=True) - pkg_config_path = env.coredata.optstore[OptionKey('pkg_config_path')].value + pkg_config_path = env.coredata.optstore.get_value('pkg_config_path') self.assertEqual(pkg_config_path, [pkg_dir]) @skipIfNoPkgconfig From 181c3499fde491650269c236ea639c92f10c6914 Mon Sep 17 00:00:00 2001 From: Jussi Pakkanen Date: Tue, 11 Jun 2024 21:20:33 +0300 Subject: [PATCH 57/91] Fix mypy. --- mesonbuild/cargo/interpreter.py | 4 ++-- mesonbuild/compilers/c.py | 3 +++ mesonbuild/compilers/compilers.py | 7 ++++--- mesonbuild/compilers/cpp.py | 2 +- mesonbuild/compilers/cuda.py | 1 + mesonbuild/compilers/mixins/elbrus.py | 6 +++--- mesonbuild/coredata.py | 4 +++- mesonbuild/mconf.py | 6 ++++-- mesonbuild/mintro.py | 4 +++- mesonbuild/options.py | 13 ++++++------- 10 files changed, 30 insertions(+), 20 deletions(-) diff --git a/mesonbuild/cargo/interpreter.py b/mesonbuild/cargo/interpreter.py index 57f2bedeadf5..1d06474c6fc3 100644 --- a/mesonbuild/cargo/interpreter.py +++ b/mesonbuild/cargo/interpreter.py @@ -27,11 +27,11 @@ if T.TYPE_CHECKING: from types import ModuleType + from typing import Any from . import manifest from .. import mparser from ..environment import Environment - from ..coredata import KeyedOptionDictType # tomllib is present in python 3.11, before that it is a pypi module called tomli, # we try to import tomllib, then tomli, @@ -700,7 +700,7 @@ def _create_lib(cargo: Manifest, build: builder.Builder, crate_type: manifest.CR ] -def interpret(subp_name: str, subdir: str, env: Environment) -> T.Tuple[mparser.CodeBlockNode, KeyedOptionDictType]: +def interpret(subp_name: str, subdir: str, env: Environment) -> T.Tuple[mparser.CodeBlockNode, dict[OptionKey, options.UserOption[Any]]]: # subp_name should be in the form "foo-0.1-rs" package_name = subp_name.rsplit('-', 2)[0] manifests = _load_manifests(os.path.join(env.source_dir, subdir)) diff --git a/mesonbuild/compilers/c.py b/mesonbuild/compilers/c.py index f4f1937946a0..8fda3a5a508b 100644 --- a/mesonbuild/compilers/c.py +++ b/mesonbuild/compilers/c.py @@ -541,6 +541,9 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic def get_options(self) -> 'MutableKeyedOptionDictType': opts = super().get_options() key = self.form_langopt_key('std') + # To shut up mypy. + if isinstance(opts, dict): + raise RuntimeError('This is a transitory issue that should not happen. Please report with full backtrace.') std_opt = opts.get_value_object(key) assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(['c89', 'c99', 'c11']) diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index 1e386bbbb19d..4a1fd984cb6a 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -25,6 +25,7 @@ from ..arglist import CompilerArgs if T.TYPE_CHECKING: + from typing import Any from ..build import BuildTarget, DFeatures from ..coredata import MutableKeyedOptionDictType, KeyedOptionDictType from ..envconfig import MachineInfo @@ -247,7 +248,7 @@ def init_option(self, name: OptionKey) -> options._U: choices=MSCRT_VALS + ['from_buildtype', 'static_from_buildtype']), } -base_options: KeyedOptionDictType = {key: base_opt.init_option(key) for key, base_opt in BASE_OPTIONS.items()} +base_options = {key: base_opt.init_option(key) for key, base_opt in BASE_OPTIONS.items()} def option_enabled(boptions: T.Set[OptionKey], options: 'KeyedOptionDictType', option: OptionKey) -> bool: @@ -1357,7 +1358,7 @@ def form_langopt_key(self, basename: str) -> OptionKey: def get_global_options(lang: str, comp: T.Type[Compiler], for_machine: MachineChoice, - env: 'Environment') -> 'KeyedOptionDictType': + env: 'Environment') -> 'dict[OptionKey, options.UserOption[Any]]': """Retrieve options that apply to all compilers for a given language.""" description = f'Extra arguments passed to the {lang}' argkey = OptionKey('args', lang=lang, machine=for_machine) @@ -1387,6 +1388,6 @@ def get_global_options(lang: str, # autotools compatibility. largs.extend_value(comp_options) - opts: 'KeyedOptionDictType' = {argkey: cargs, largkey: largs} + opts: 'dict[OptionKey, options.UserOption[Any]]' = {argkey: cargs, largkey: largs} return opts diff --git a/mesonbuild/compilers/cpp.py b/mesonbuild/compilers/cpp.py index a01b377f465f..df2f90510730 100644 --- a/mesonbuild/compilers/cpp.py +++ b/mesonbuild/compilers/cpp.py @@ -810,7 +810,7 @@ def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str] # copy the members because the option proxy doesn't support it. options = copy.deepcopy(options) if options.get_value(key) == 'vc++11': - options.set_value(key,'vc++14') + options.set_value(key, 'vc++14') else: options.set_value(key, 'c++14') return super().get_option_compile_args(options) diff --git a/mesonbuild/compilers/cuda.py b/mesonbuild/compilers/cuda.py index 2e9218e93d16..dc9cf8addd35 100644 --- a/mesonbuild/compilers/cuda.py +++ b/mesonbuild/compilers/cuda.py @@ -665,6 +665,7 @@ def _to_host_compiler_options(self, master_options: 'KeyedOptionDictType') -> 'K host_options = {key: master_options.get(key, opt) for key, opt in self.host_compiler.get_options().items()} std_key = OptionKey('std', machine=self.for_machine, lang=self.host_compiler.language) overrides = {std_key: 'none'} + # To shut up mypy. return coredata.OptionsView(host_options, overrides=overrides) def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: diff --git a/mesonbuild/compilers/mixins/elbrus.py b/mesonbuild/compilers/mixins/elbrus.py index 27cba803c9fc..10df3decb776 100644 --- a/mesonbuild/compilers/mixins/elbrus.py +++ b/mesonbuild/compilers/mixins/elbrus.py @@ -84,9 +84,9 @@ def get_pch_suffix(self) -> str: def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args: T.List[str] = [] - std = options[OptionKey('std', lang=self.language, machine=self.for_machine)] - if std.value != 'none': - args.append('-std=' + std.value) + std = options.get_value(OptionKey('std', lang=self.language, machine=self.for_machine)) + if std != 'none': + args.append('-std=' + std) return args def openmp_flags(self) -> T.List[str]: diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index 60f3574672ac..36191d93b98c 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -32,6 +32,7 @@ if T.TYPE_CHECKING: from typing_extensions import Protocol + from typing import Any from . import dependencies from .compilers.compilers import Compiler, CompileResult, RunResult, CompileCheckMode @@ -40,6 +41,7 @@ from .mesonlib import FileOrString from .cmake.traceparser import CMakeCacheEntry from .interpreterbase import SubProject + from .options import UserOption class SharedCMDOptions(Protocol): @@ -896,7 +898,7 @@ class OptionsView(abc.Mapping): # TODO: the typing here could be made more explicit using a TypeDict from # python 3.8 or typing_extensions - original_options: KeyedOptionDictType + original_options: T.Union[KeyedOptionDictType, 'dict[OptionKey, UserOption[Any]]'] subproject: T.Optional[str] = None overrides: T.Optional[T.Mapping[OptionKey, T.Union[str, int, bool, T.List[str]]]] = None diff --git a/mesonbuild/mconf.py b/mesonbuild/mconf.py index 1f167df3e2e0..da96ac41ff3b 100644 --- a/mesonbuild/mconf.py +++ b/mesonbuild/mconf.py @@ -25,6 +25,8 @@ if T.TYPE_CHECKING: from typing_extensions import Protocol + from typing import Any + from .options import UserOption import argparse class CMDOptions(coredata.SharedCMDOptions, Protocol): @@ -186,7 +188,7 @@ def wrap_text(text: LOGLINE, width: int) -> mlog.TV_LoggableList: items = [l[i] if l[i] else ' ' * four_column[i] for i in range(4)] mlog.log(*items) - def split_options_per_subproject(self, options: 'coredata.KeyedOptionDictType') -> T.Dict[str, 'coredata.MutableKeyedOptionDictType']: + def split_options_per_subproject(self, options: 'T.Union[dict[OptionKey, UserOption[Any]], coredata.KeyedOptionDictType]') -> T.Dict[str, 'coredata.MutableKeyedOptionDictType']: result: T.Dict[str, 'coredata.MutableKeyedOptionDictType'] = {} for k, o in options.items(): if k.subproject: @@ -224,7 +226,7 @@ def add_section(self, section: str) -> None: self._add_line(mlog.normal_yellow(section + ':'), '', '', '') self.print_margin = 2 - def print_options(self, title: str, opts: 'coredata.KeyedOptionDictType') -> None: + def print_options(self, title: str, opts: 'T.Union[dict[OptionKey, UserOption[Any]], coredata.KeyedOptionDictType]') -> None: if not opts: return if title: diff --git a/mesonbuild/mintro.py b/mesonbuild/mintro.py index b13af546d3a3..dea67d82e1cd 100644 --- a/mesonbuild/mintro.py +++ b/mesonbuild/mintro.py @@ -30,6 +30,8 @@ if T.TYPE_CHECKING: import argparse + from typing import Any + from .options import UserOption from .interpreter import Interpreter from .mparser import BaseNode @@ -302,7 +304,7 @@ def list_buildoptions(coredata: cdata.CoreData, subprojects: T.Optional[T.List[s for s in subprojects: core_options[k.evolve(subproject=s)] = v - def add_keys(opts: 'cdata.KeyedOptionDictType', section: str) -> None: + def add_keys(opts: 'T.Union[dict[OptionKey, UserOption[Any]], cdata.KeyedOptionDictType]', section: str) -> None: for key, opt in sorted(opts.items()): optdict = {'name': str(key), 'value': opt.value, 'section': section, 'machine': key.machine.get_lower_case_name() if coredata.is_per_machine_option(key) else 'any'} diff --git a/mesonbuild/options.py b/mesonbuild/options.py index 435c53def424..d83a312886d5 100644 --- a/mesonbuild/options.py +++ b/mesonbuild/options.py @@ -481,7 +481,7 @@ def __init__(self): def __len__(self): return len(self.d) - def ensure_key(self,key: T.Union[OptionKey, str]) -> OptionKey: + def ensure_key(self, key: T.Union[OptionKey, str]) -> OptionKey: if isinstance(key, str): return OptionKey(key) return key @@ -492,7 +492,7 @@ def get_value_object(self, key: T.Union[OptionKey, str]) -> 'UserOption[T.Any]': def get_value(self, key: T.Union[OptionKey, str]) -> 'T.Any': return self.get_value_object(key).value - def add_system_option(self, key: T.Union[OptionKey, str], valobj: 'UserOption[T.Any'): + def add_system_option(self, key: T.Union[OptionKey, str], valobj: 'UserOption[T.Any]'): key = self.ensure_key(key) self.d[key] = valobj @@ -501,15 +501,14 @@ def add_project_option(self, key: T.Union[OptionKey, str], valobj: 'UserOption[T self.d[key] = valobj def set_value(self, key: T.Union[OptionKey, str], new_value: 'T.Any') -> bool: - key = self.ensure_key(key) + key = self.ensure_key(key) return self.d[key].set_value(new_value) # FIXME, this should be removed.or renamed to "change_type_of_existing_object" or something like that def set_value_object(self, key: T.Union[OptionKey, str], new_object: 'UserOption[T.Any]') -> bool: - key = self.ensure_key(key) + key = self.ensure_key(key) self.d[key] = new_object - def remove(self, key): del self.d[key] @@ -526,7 +525,7 @@ def keys(self): def values(self): return self.d.values() - def items(self) -> ItemsView['OptionKey', 'USerOption[T.Any]']: + def items(self) -> ItemsView['OptionKey', 'UserOption[T.Any]']: return self.d.items() def update(self, *args, **kwargs): @@ -535,5 +534,5 @@ def update(self, *args, **kwargs): def setdefault(self, k, o): return self.d.setdefault(k, o) - def get(self, *args, **kwargs)-> UserOption: + def get(self, *args, **kwargs) -> UserOption: return self.d.get(*args, **kwargs) From c0de2e12645b621793c62d4e2da17dc9541946f8 Mon Sep 17 00:00:00 2001 From: Xavier Claessens Date: Tue, 5 Mar 2024 15:10:29 -0500 Subject: [PATCH 58/91] wrap: Clarify PackageDefinition API This will simplify creating PackageDefinition objects from Cargo.lock file. It contains basically the same information. --- mesonbuild/msubprojects.py | 16 +-- mesonbuild/wrap/wrap.py | 195 +++++++++++++++++---------------- unittests/allplatformstests.py | 8 +- 3 files changed, 113 insertions(+), 106 deletions(-) diff --git a/mesonbuild/msubprojects.py b/mesonbuild/msubprojects.py index 15db3f928a61..c15415485217 100755 --- a/mesonbuild/msubprojects.py +++ b/mesonbuild/msubprojects.py @@ -176,7 +176,9 @@ def update_wrapdb(self) -> bool: latest_version = info['versions'][0] new_branch, new_revision = latest_version.rsplit('-', 1) if new_branch != branch or new_revision != revision: - filename = self.wrap.filename if self.wrap.has_wrap else f'{self.wrap.filename}.wrap' + filename = self.wrap.original_filename + if not filename: + filename = os.path.join(self.wrap.subprojects_dir, f'{self.wrap.name}.wrap') update_wrap_file(filename, self.wrap.name, new_branch, new_revision, options.allow_insecure) @@ -521,16 +523,10 @@ def purge(self) -> bool: return True if self.wrap.redirected: - redirect_file = Path(self.wrap.original_filename).resolve() + wrapfile = Path(self.wrap.original_filename).resolve() if options.confirm: - redirect_file.unlink() - mlog.log(f'Deleting {redirect_file}') - - if self.wrap.type == 'redirect': - redirect_file = Path(self.wrap.filename).resolve() - if options.confirm: - redirect_file.unlink() - self.log(f'Deleting {redirect_file}') + wrapfile.unlink() + mlog.log(f'Deleting {wrapfile}') if options.include_cache: packagecache = Path(self.wrap_resolver.cachedir).resolve() diff --git a/mesonbuild/wrap/wrap.py b/mesonbuild/wrap/wrap.py index a260e0ce681c..96b0ef35c623 100644 --- a/mesonbuild/wrap/wrap.py +++ b/mesonbuild/wrap/wrap.py @@ -51,7 +51,7 @@ REQ_TIMEOUT = 30.0 WHITELIST_SUBDOMAIN = 'wrapdb.mesonbuild.com' -ALL_TYPES = ['file', 'git', 'hg', 'svn'] +ALL_TYPES = ['file', 'git', 'hg', 'svn', 'redirect'] PATCH = shutil.which('patch') @@ -137,46 +137,60 @@ class WrapNotFoundException(WrapException): pass class PackageDefinition: - def __init__(self, fname: str, subproject: str = ''): - self.filename = fname - self.subproject = SubProject(subproject) - self.type: T.Optional[str] = None - self.values: T.Dict[str, str] = {} + def __init__(self, name: str, subprojects_dir: str, type_: T.Optional[str] = None, values: T.Optional[T.Dict[str, str]] = None): + self.name = name + self.subprojects_dir = subprojects_dir + self.type = type_ + self.values = values or {} self.provided_deps: T.Dict[str, T.Optional[str]] = {} self.provided_programs: T.List[str] = [] self.diff_files: T.List[Path] = [] - self.basename = os.path.basename(fname) - self.has_wrap = self.basename.endswith('.wrap') - self.name = self.basename[:-5] if self.has_wrap else self.basename - # must be lowercase for consistency with dep=variable assignment - self.provided_deps[self.name.lower()] = None - # What the original file name was before redirection - self.original_filename = fname - self.redirected = False - if self.has_wrap: - self.parse_wrap() - with open(fname, 'r', encoding='utf-8') as file: - self.wrapfile_hash = hashlib.sha256(file.read().encode('utf-8')).hexdigest() + self.wrapfile_hash: T.Optional[str] = None + self.original_filename: T.Optional[str] = None + self.redirected: bool = False + self.filesdir = os.path.join(self.subprojects_dir, 'packagefiles') self.directory = self.values.get('directory', self.name) if os.path.dirname(self.directory): raise WrapException('Directory key must be a name and not a path') - if self.type and self.type not in ALL_TYPES: - raise WrapException(f'Unknown wrap type {self.type!r}') - self.filesdir = os.path.join(os.path.dirname(self.filename), 'packagefiles') + if 'diff_files' in self.values: + for s in self.values['diff_files'].split(','): + path = Path(s.strip()) + if path.is_absolute(): + raise WrapException('diff_files paths cannot be absolute') + if '..' in path.parts: + raise WrapException('diff_files paths cannot contain ".."') + self.diff_files.append(path) + # must be lowercase for consistency with dep=variable assignment + self.provided_deps[self.name.lower()] = None - def parse_wrap(self) -> None: - try: - config = configparser.ConfigParser(interpolation=None) - config.read(self.filename, encoding='utf-8') - except configparser.Error as e: - raise WrapException(f'Failed to parse {self.basename}: {e!s}') - self.parse_wrap_section(config) - if self.type == 'redirect': + @staticmethod + def from_values(name: str, subprojects_dir: str, type_: str, values: T.Dict[str, str]) -> PackageDefinition: + return PackageDefinition(name, subprojects_dir, type_, values) + + @staticmethod + def from_directory(filename: str) -> PackageDefinition: + name = os.path.basename(filename) + subprojects_dir = os.path.dirname(filename) + return PackageDefinition(name, subprojects_dir) + + @staticmethod + def from_wrap_file(filename: str, subproject: SubProject = SubProject('')) -> PackageDefinition: + config, type_, values = PackageDefinition._parse_wrap(filename) + if 'diff_files' in values: + FeatureNew('Wrap files with diff_files', '0.63.0').use(subproject) + if 'patch_directory' in values: + FeatureNew('Wrap files with patch_directory', '0.55.0').use(subproject) + for what in ['patch', 'source']: + if f'{what}_filename' in values and f'{what}_url' not in values: + FeatureNew(f'Local wrap patch files without {what}_url', '0.55.0').use(subproject) + + subprojects_dir = os.path.dirname(filename) + + if type_ == 'redirect': # [wrap-redirect] have a `filename` value pointing to the real wrap # file we should parse instead. It must be relative to the current # wrap file location and must be in the form foo/subprojects/bar.wrap. - dirname = Path(self.filename).parent - fname = Path(self.values['filename']) + fname = Path(values['filename']) for i, p in enumerate(fname.parts): if i % 2 == 0: if p == '..': @@ -186,37 +200,41 @@ def parse_wrap(self) -> None: raise WrapException('wrap-redirect filename must be in the form foo/subprojects/bar.wrap') if fname.suffix != '.wrap': raise WrapException('wrap-redirect filename must be a .wrap file') - fname = dirname / fname + fname = Path(subprojects_dir, fname) if not fname.is_file(): raise WrapException(f'wrap-redirect {fname} filename does not exist') - self.filename = str(fname) - self.parse_wrap() - self.redirected = True - else: - self.parse_provide_section(config) - if 'patch_directory' in self.values: - FeatureNew('Wrap files with patch_directory', '0.55.0').use(self.subproject) - for what in ['patch', 'source']: - if f'{what}_filename' in self.values and f'{what}_url' not in self.values: - FeatureNew(f'Local wrap patch files without {what}_url', '0.55.0').use(self.subproject) + wrap = PackageDefinition.from_wrap_file(str(fname), subproject) + wrap.original_filename = filename + wrap.redirected = True + return wrap - def parse_wrap_section(self, config: configparser.ConfigParser) -> None: + name = os.path.basename(filename)[:-5] + wrap = PackageDefinition.from_values(name, subprojects_dir, type_, values) + wrap.original_filename = filename + wrap.parse_provide_section(config) + + with open(filename, 'r', encoding='utf-8') as file: + wrap.wrapfile_hash = hashlib.sha256(file.read().encode('utf-8')).hexdigest() + + return wrap + + @staticmethod + def _parse_wrap(filename: str) -> T.Tuple[configparser.ConfigParser, str, T.Dict[str, str]]: + try: + config = configparser.ConfigParser(interpolation=None) + config.read(filename, encoding='utf-8') + except configparser.Error as e: + raise WrapException(f'Failed to parse {filename}: {e!s}') if len(config.sections()) < 1: - raise WrapException(f'Missing sections in {self.basename}') - self.wrap_section = config.sections()[0] - if not self.wrap_section.startswith('wrap-'): - raise WrapException(f'{self.wrap_section!r} is not a valid first section in {self.basename}') - self.type = self.wrap_section[5:] - self.values = dict(config[self.wrap_section]) - if 'diff_files' in self.values: - FeatureNew('Wrap files with diff_files', '0.63.0').use(self.subproject) - for s in self.values['diff_files'].split(','): - path = Path(s.strip()) - if path.is_absolute(): - raise WrapException('diff_files paths cannot be absolute') - if '..' in path.parts: - raise WrapException('diff_files paths cannot contain ".."') - self.diff_files.append(path) + raise WrapException(f'Missing sections in {filename}') + wrap_section = config.sections()[0] + if not wrap_section.startswith('wrap-'): + raise WrapException(f'{wrap_section!r} is not a valid first section in {filename}') + type_ = wrap_section[5:] + if type_ not in ALL_TYPES: + raise WrapException(f'Unknown wrap type {type_!r}') + values = dict(config[wrap_section]) + return config, type_, values def parse_provide_section(self, config: configparser.ConfigParser) -> None: if config.has_section('provides'): @@ -236,7 +254,7 @@ def parse_provide_section(self, config: configparser.ConfigParser) -> None: self.provided_programs += names_list continue if not v: - m = (f'Empty dependency variable name for {k!r} in {self.basename}. ' + m = (f'Empty dependency variable name for {k!r} in {self.name}.wrap. ' 'If the subproject uses meson.override_dependency() ' 'it can be added in the "dependency_names" special key.') raise WrapException(m) @@ -246,20 +264,21 @@ def get(self, key: str) -> str: try: return self.values[key] except KeyError: - raise WrapException(f'Missing key {key!r} in {self.basename}') + raise WrapException(f'Missing key {key!r} in {self.name}.wrap') - def get_hashfile(self, subproject_directory: str) -> str: + @staticmethod + def get_hashfile(subproject_directory: str) -> str: return os.path.join(subproject_directory, '.meson-subproject-wrap-hash.txt') def update_hash_cache(self, subproject_directory: str) -> None: - if self.has_wrap: + if self.wrapfile_hash: with open(self.get_hashfile(subproject_directory), 'w', encoding='utf-8') as file: file.write(self.wrapfile_hash + '\n') def get_directory(subdir_root: str, packagename: str) -> str: fname = os.path.join(subdir_root, packagename + '.wrap') if os.path.isfile(fname): - wrap = PackageDefinition(fname) + wrap = PackageDefinition.from_wrap_file(fname) return wrap.directory return packagename @@ -276,7 +295,7 @@ def verbose_git(cmd: T.List[str], workingdir: str, check: bool = False) -> bool: class Resolver: source_dir: str subdir: str - subproject: str = '' + subproject: SubProject = SubProject('') wrap_mode: WrapMode = WrapMode.default wrap_frontend: bool = False allow_insecure: bool = False @@ -313,7 +332,7 @@ def load_wraps(self) -> None: if not i.endswith('.wrap'): continue fname = os.path.join(self.subdir_root, i) - wrap = PackageDefinition(fname, self.subproject) + wrap = PackageDefinition.from_wrap_file(fname, self.subproject) self.wraps[wrap.name] = wrap ignore_dirs |= {wrap.directory, wrap.name} # Add dummy package definition for directories not associated with a wrap file. @@ -321,7 +340,7 @@ def load_wraps(self) -> None: if i in ignore_dirs: continue fname = os.path.join(self.subdir_root, i) - wrap = PackageDefinition(fname, self.subproject) + wrap = PackageDefinition.from_directory(fname) self.wraps[wrap.name] = wrap for wrap in self.wraps.values(): @@ -331,13 +350,13 @@ def add_wrap(self, wrap: PackageDefinition) -> None: for k in wrap.provided_deps.keys(): if k in self.provided_deps: prev_wrap = self.provided_deps[k] - m = f'Multiple wrap files provide {k!r} dependency: {wrap.basename} and {prev_wrap.basename}' + m = f'Multiple wrap files provide {k!r} dependency: {wrap.name} and {prev_wrap.name}' raise WrapException(m) self.provided_deps[k] = wrap for k in wrap.provided_programs: if k in self.provided_programs: prev_wrap = self.provided_programs[k] - m = f'Multiple wrap files provide {k!r} program: {wrap.basename} and {prev_wrap.basename}' + m = f'Multiple wrap files provide {k!r} program: {wrap.name} and {prev_wrap.name}' raise WrapException(m) self.provided_programs[k] = wrap @@ -363,7 +382,7 @@ def get_from_wrapdb(self, subp_name: str) -> T.Optional[PackageDefinition]: with fname.open('wb') as f: f.write(url.read()) mlog.log(f'Installed {subp_name} version {version} revision {revision}') - wrap = PackageDefinition(str(fname)) + wrap = PackageDefinition.from_wrap_file(str(fname)) self.wraps[wrap.name] = wrap self.add_wrap(wrap) return wrap @@ -409,32 +428,26 @@ def resolve(self, packagename: str, force_method: T.Optional[Method] = None) -> raise WrapNotFoundException(f'Neither a subproject directory nor a {packagename}.wrap file was found.') self.wrap = wrap self.directory = self.wrap.directory + self.dirname = os.path.join(self.wrap.subprojects_dir, self.wrap.directory) + if not os.path.exists(self.dirname): + self.dirname = os.path.join(self.subdir_root, self.directory) + rel_path = os.path.relpath(self.dirname, self.source_dir) - if self.wrap.has_wrap: - # We have a .wrap file, use directory relative to the location of - # the wrap file if it exists, otherwise source code will be placed - # into main project's subproject_dir even if the wrap file comes - # from another subproject. - self.dirname = os.path.join(os.path.dirname(self.wrap.filename), self.wrap.directory) - if not os.path.exists(self.dirname): - self.dirname = os.path.join(self.subdir_root, self.directory) - # Check if the wrap comes from the main project. - main_fname = os.path.join(self.subdir_root, self.wrap.basename) - if self.wrap.filename != main_fname: - rel = os.path.relpath(self.wrap.filename, self.source_dir) + if self.wrap.original_filename: + # If the original wrap file is not in main project's subproject_dir, + # write a wrap-redirect. + basename = os.path.basename(self.wrap.original_filename) + main_fname = os.path.join(self.subdir_root, basename) + if self.wrap.original_filename != main_fname: + rel = os.path.relpath(self.wrap.original_filename, self.source_dir) mlog.log('Using', mlog.bold(rel)) # Write a dummy wrap file in main project that redirect to the # wrap we picked. with open(main_fname, 'w', encoding='utf-8') as f: f.write(textwrap.dedent(f'''\ [wrap-redirect] - filename = {PurePath(os.path.relpath(self.wrap.filename, self.subdir_root)).as_posix()} + filename = {PurePath(os.path.relpath(self.wrap.original_filename, self.subdir_root)).as_posix()} ''')) - else: - # No wrap file, it's a dummy package definition for an existing - # directory. Use the source code in place. - self.dirname = self.wrap.filename - rel_path = os.path.relpath(self.dirname, self.source_dir) # Map each supported method to a file that must exist at the root of source tree. methods_map: T.Dict[Method, str] = { @@ -606,7 +619,7 @@ def _get_git(self, packagename: str) -> None: def validate(self) -> None: # This check is only for subprojects with wraps. - if not self.wrap.has_wrap: + if not self.wrap.wrapfile_hash: return # Retrieve original hash, if it exists. @@ -618,10 +631,8 @@ def validate(self) -> None: # If stored hash doesn't exist then don't warn. return - actual_hash = self.wrap.wrapfile_hash - # Compare hashes and warn the user if they don't match. - if expected_hash != actual_hash: + if expected_hash != self.wrap.wrapfile_hash: mlog.warning(f'Subproject {self.wrap.name}\'s revision may be out of date; its wrap file has changed since it was first configured') def is_git_full_commit_id(self, revno: str) -> bool: @@ -783,7 +794,7 @@ def _get_file_internal(self, what: str, packagename: str) -> str: def apply_patch(self, packagename: str) -> None: if 'patch_filename' in self.wrap.values and 'patch_directory' in self.wrap.values: - m = f'Wrap file {self.wrap.basename!r} must not have both "patch_filename" and "patch_directory"' + m = f'Wrap file {self.wrap.name!r} must not have both "patch_filename" and "patch_directory"' raise WrapException(m) if 'patch_filename' in self.wrap.values: path = self._get_file_internal('patch', packagename) diff --git a/unittests/allplatformstests.py b/unittests/allplatformstests.py index 201dae695901..e912e943977e 100644 --- a/unittests/allplatformstests.py +++ b/unittests/allplatformstests.py @@ -4217,7 +4217,7 @@ def test_wrap_redirect(self): filename = foo/subprojects/real.wrapper ''')) with self.assertRaisesRegex(WrapException, 'wrap-redirect filename must be a .wrap file'): - PackageDefinition(redirect_wrap) + PackageDefinition.from_wrap_file(redirect_wrap) # Invalid redirect, filename cannot be in parent directory with open(redirect_wrap, 'w', encoding='utf-8') as f: @@ -4226,7 +4226,7 @@ def test_wrap_redirect(self): filename = ../real.wrap ''')) with self.assertRaisesRegex(WrapException, 'wrap-redirect filename cannot contain ".."'): - PackageDefinition(redirect_wrap) + PackageDefinition.from_wrap_file(redirect_wrap) # Invalid redirect, filename must be in foo/subprojects/real.wrap with open(redirect_wrap, 'w', encoding='utf-8') as f: @@ -4235,7 +4235,7 @@ def test_wrap_redirect(self): filename = foo/real.wrap ''')) with self.assertRaisesRegex(WrapException, 'wrap-redirect filename must be in the form foo/subprojects/bar.wrap'): - PackageDefinition(redirect_wrap) + PackageDefinition.from_wrap_file(redirect_wrap) # Correct redirect with open(redirect_wrap, 'w', encoding='utf-8') as f: @@ -4248,7 +4248,7 @@ def test_wrap_redirect(self): [wrap-git] url = http://invalid ''')) - wrap = PackageDefinition(redirect_wrap) + wrap = PackageDefinition.from_wrap_file(redirect_wrap) self.assertEqual(wrap.get('url'), 'http://invalid') @skip_if_no_cmake From 9b8378985dbdc0112d11893dd42b33b7bc8d1e62 Mon Sep 17 00:00:00 2001 From: Xavier Claessens Date: Thu, 7 Mar 2024 12:23:13 -0500 Subject: [PATCH 59/91] cargo: Load Cargo.lock Cargo.lock is essentially identical to subprojects/*.wrap files. When a (sub)project has a Cargo.lock file this allows automatic fallback for its cargo dependencies. --- .../markdown/Wrap-dependency-system-manual.md | 4 ++ docs/markdown/snippets/cargo_lock.md | 6 +++ mesonbuild/cargo/__init__.py | 5 +- mesonbuild/cargo/interpreter.py | 46 +++++++++++++++++- mesonbuild/cargo/manifest.py | 17 +++++++ mesonbuild/wrap/wrap.py | 45 +++++++++-------- run_unittests.py | 2 +- test cases/rust/25 cargo lock/Cargo.lock | 7 +++ test cases/rust/25 cargo lock/meson.build | 3 ++ .../subprojects/packagecache/bar-0.1.tar.gz | Bin 0 -> 288 bytes unittests/cargotests.py | 36 +++++++++++++- 11 files changed, 147 insertions(+), 24 deletions(-) create mode 100644 docs/markdown/snippets/cargo_lock.md create mode 100644 test cases/rust/25 cargo lock/Cargo.lock create mode 100644 test cases/rust/25 cargo lock/meson.build create mode 100644 test cases/rust/25 cargo lock/subprojects/packagecache/bar-0.1.tar.gz diff --git a/docs/markdown/Wrap-dependency-system-manual.md b/docs/markdown/Wrap-dependency-system-manual.md index 5f0b473e7a2b..3983d28771e9 100644 --- a/docs/markdown/Wrap-dependency-system-manual.md +++ b/docs/markdown/Wrap-dependency-system-manual.md @@ -377,6 +377,10 @@ Some naming conventions need to be respected: - The `extra_deps` variable is pre-defined and can be used to add extra dependencies. This is typically used as `extra_deps += dependency('foo')`. +Since *1.5.0* Cargo wraps can also be provided with `Cargo.lock` file at the root +of (sub)project source tree. Meson will automatically load that file and convert +it into a serie of wraps definitions. + ## Using wrapped projects Wraps provide a convenient way of obtaining a project into your diff --git a/docs/markdown/snippets/cargo_lock.md b/docs/markdown/snippets/cargo_lock.md new file mode 100644 index 000000000000..e38c5ed35340 --- /dev/null +++ b/docs/markdown/snippets/cargo_lock.md @@ -0,0 +1,6 @@ +## Added support `Cargo.lock` file + +When a (sub)project has a `Cargo.lock` file at its root, it is loaded to provide +an automatic fallback for dependencies it defines, fetching code from +https://crates.io or git. This is identical as providing `subprojects/*.wrap`, +see [cargo wraps](Wrap-dependency-system-manual.md#cargo-wraps) dependency naming convention. diff --git a/mesonbuild/cargo/__init__.py b/mesonbuild/cargo/__init__.py index 0007b9d6469b..10cb0be103c0 100644 --- a/mesonbuild/cargo/__init__.py +++ b/mesonbuild/cargo/__init__.py @@ -1,5 +1,6 @@ __all__ = [ - 'interpret' + 'interpret', + 'load_wraps', ] -from .interpreter import interpret +from .interpreter import interpret, load_wraps diff --git a/mesonbuild/cargo/interpreter.py b/mesonbuild/cargo/interpreter.py index 1d06474c6fc3..13568cd46cf8 100644 --- a/mesonbuild/cargo/interpreter.py +++ b/mesonbuild/cargo/interpreter.py @@ -18,12 +18,14 @@ import os import shutil import collections +import urllib.parse import typing as T from . import builder from . import version from ..mesonlib import MesonException, Popen_safe, OptionKey -from .. import coredata, options +from .. import coredata, options, mlog +from ..wrap.wrap import PackageDefinition if T.TYPE_CHECKING: from types import ModuleType @@ -731,3 +733,45 @@ def interpret(subp_name: str, subdir: str, env: Environment) -> T.Tuple[mparser. ast.extend(_create_lib(cargo, build, crate_type)) return build.block(ast), project_options + + +def load_wraps(source_dir: str, subproject_dir: str) -> T.List[PackageDefinition]: + """ Convert Cargo.lock into a list of wraps """ + + wraps: T.List[PackageDefinition] = [] + filename = os.path.join(source_dir, 'Cargo.lock') + if os.path.exists(filename): + cargolock = T.cast('manifest.CargoLock', load_toml(filename)) + for package in cargolock['package']: + name = package['name'] + version = package['version'] + subp_name = _dependency_name(name, _version_to_api(version)) + source = package.get('source') + if source is None: + # This is project's package, or one of its workspace members. + pass + elif source == 'registry+https://github.com/rust-lang/crates.io-index': + url = f'https://crates.io/api/v1/crates/{name}/{version}/download' + directory = f'{name}-{version}' + wraps.append(PackageDefinition.from_values(subp_name, subproject_dir, 'file', { + 'directory': directory, + 'source_url': url, + 'source_filename': f'{directory}.tar.gz', + 'source_hash': package['checksum'], + 'method': 'cargo', + })) + elif source.startswith('git+'): + parts = urllib.parse.urlparse(source[4:]) + query = urllib.parse.parse_qs(parts.query) + branch = query['branch'][0] if 'branch' in query else '' + revision = parts.fragment or branch + url = urllib.parse.urlunparse(parts._replace(params='', query='', fragment='')) + wraps.append(PackageDefinition.from_values(subp_name, subproject_dir, 'git', { + 'directory': name, + 'url': url, + 'revision': revision, + 'method': 'cargo', + })) + else: + mlog.warning(f'Unsupported source URL in {filename}: {source}') + return wraps diff --git a/mesonbuild/cargo/manifest.py b/mesonbuild/cargo/manifest.py index e6192d03cd98..183d91efd44f 100644 --- a/mesonbuild/cargo/manifest.py +++ b/mesonbuild/cargo/manifest.py @@ -225,3 +225,20 @@ class VirtualManifest(TypedDict): """ workspace: Workspace + +class CargoLockPackage(TypedDict, total=False): + + """A description of a package in the Cargo.lock file format.""" + + name: str + version: str + source: str + checksum: str + + +class CargoLock(TypedDict, total=False): + + """A description of the Cargo.lock file format.""" + + version: str + package: T.List[CargoLockPackage] diff --git a/mesonbuild/wrap/wrap.py b/mesonbuild/wrap/wrap.py index 96b0ef35c623..4e98c600cf87 100644 --- a/mesonbuild/wrap/wrap.py +++ b/mesonbuild/wrap/wrap.py @@ -324,25 +324,32 @@ def load_netrc(self) -> None: mlog.warning(f'failed to process netrc file: {e}.', fatal=False) def load_wraps(self) -> None: - if not os.path.isdir(self.subdir_root): - return - root, dirs, files = next(os.walk(self.subdir_root)) - ignore_dirs = {'packagecache', 'packagefiles'} - for i in files: - if not i.endswith('.wrap'): - continue - fname = os.path.join(self.subdir_root, i) - wrap = PackageDefinition.from_wrap_file(fname, self.subproject) - self.wraps[wrap.name] = wrap - ignore_dirs |= {wrap.directory, wrap.name} - # Add dummy package definition for directories not associated with a wrap file. - for i in dirs: - if i in ignore_dirs: - continue - fname = os.path.join(self.subdir_root, i) - wrap = PackageDefinition.from_directory(fname) - self.wraps[wrap.name] = wrap - + # Load Cargo.lock at the root of source tree + source_dir = os.path.dirname(self.subdir_root) + if os.path.exists(os.path.join(source_dir, 'Cargo.lock')): + from .. import cargo + for wrap in cargo.load_wraps(source_dir, self.subdir_root): + self.wraps[wrap.name] = wrap + # Load subprojects/*.wrap + if os.path.isdir(self.subdir_root): + root, dirs, files = next(os.walk(self.subdir_root)) + for i in files: + if not i.endswith('.wrap'): + continue + fname = os.path.join(self.subdir_root, i) + wrap = PackageDefinition.from_wrap_file(fname, self.subproject) + self.wraps[wrap.name] = wrap + # Add dummy package definition for directories not associated with a wrap file. + ignore_dirs = {'packagecache', 'packagefiles'} + for wrap in self.wraps.values(): + ignore_dirs |= {wrap.directory, wrap.name} + for i in dirs: + if i in ignore_dirs: + continue + fname = os.path.join(self.subdir_root, i) + wrap = PackageDefinition.from_directory(fname) + self.wraps[wrap.name] = wrap + # Add provided deps and programs into our lookup tables for wrap in self.wraps.values(): self.add_wrap(wrap) diff --git a/run_unittests.py b/run_unittests.py index 33b0e0988753..84edb34ccd48 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -25,7 +25,7 @@ import mesonbuild.modules.pkgconfig from unittests.allplatformstests import AllPlatformTests -from unittests.cargotests import CargoVersionTest, CargoCfgTest +from unittests.cargotests import CargoVersionTest, CargoCfgTest, CargoLockTest from unittests.darwintests import DarwinTests from unittests.failuretests import FailureTests from unittests.linuxcrosstests import LinuxCrossArmTests, LinuxCrossMingwTests diff --git a/test cases/rust/25 cargo lock/Cargo.lock b/test cases/rust/25 cargo lock/Cargo.lock new file mode 100644 index 000000000000..9bc98149bb3f --- /dev/null +++ b/test cases/rust/25 cargo lock/Cargo.lock @@ -0,0 +1,7 @@ +version = 3 + +[[package]] +name = "bar" +version = "0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2f34e570dcd5f9fe32e6863ee16ee73a356d3b77bce0d8c78501b8bc81a860" diff --git a/test cases/rust/25 cargo lock/meson.build b/test cases/rust/25 cargo lock/meson.build new file mode 100644 index 000000000000..b359f7bb0aa7 --- /dev/null +++ b/test cases/rust/25 cargo lock/meson.build @@ -0,0 +1,3 @@ +project('cargo lock') + +dependency('bar-0.1-rs') diff --git a/test cases/rust/25 cargo lock/subprojects/packagecache/bar-0.1.tar.gz b/test cases/rust/25 cargo lock/subprojects/packagecache/bar-0.1.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..f4c2ec6725a62f65800ad2128d3567ad355d534e GIT binary patch literal 288 zcmV+*0pI@pH+ooF000E$*0e?f03iVu0001VFXf})5B~t)T>vo{N~ZzB@8=`!?Itm7 zu@4Ldiz)|;h(iPBVpT6cc*BFb|CH}oZpL--w7kPIV{xsoTeO4_4OsmqFwFsa37Z9a z46y)>KzUKcC8{->*jl`R)BzH$68O@7@uq*BEP2)HphXtU8T%oci3@zZDIF-bSw1Mu z7c57i{{kaL>#%YL?5?kN!JyhmAzjD$a&|AHG+m!785+(i0A|y!u?=9lCj+UArIDDJ zc5h_%{~}_0eCqk-ZyHCT{}Pc#RDuTZ`UD-+3Qv;G*(v3ap`aMma@^=}rO6b}9J{`o m0002vnI&H)zm`q_0s8@fAOHYt None: with self.subTest(): value = cfg.ir_to_meson(cfg.parse(iter(cfg.lexer(data))), build) self.assertEqual(value, expected) + +class CargoLockTest(unittest.TestCase): + def test_cargo_lock(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + with open(os.path.join(tmpdir, 'Cargo.lock'), 'w', encoding='utf-8') as f: + f.write(textwrap.dedent('''\ + version = 3 + [[package]] + name = "foo" + version = "0.1" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" + [[package]] + name = "bar" + version = "0.1" + source = "git+https://github.com/gtk-rs/gtk-rs-core?branch=0.19#23c5599424cc75ec66618891c915d9f490f6e4c2" + ''')) + wraps = load_wraps(tmpdir, 'subprojects') + self.assertEqual(len(wraps), 2) + self.assertEqual(wraps[0].name, 'foo-0.1-rs') + self.assertEqual(wraps[0].directory, 'foo-0.1') + self.assertEqual(wraps[0].type, 'file') + self.assertEqual(wraps[0].get('method'), 'cargo') + self.assertEqual(wraps[0].get('source_url'), 'https://crates.io/api/v1/crates/foo/0.1/download') + self.assertEqual(wraps[0].get('source_hash'), '8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb') + self.assertEqual(wraps[1].name, 'bar-0.1-rs') + self.assertEqual(wraps[1].directory, 'bar') + self.assertEqual(wraps[1].type, 'git') + self.assertEqual(wraps[1].get('method'), 'cargo') + self.assertEqual(wraps[1].get('url'), 'https://github.com/gtk-rs/gtk-rs-core') + self.assertEqual(wraps[1].get('revision'), '23c5599424cc75ec66618891c915d9f490f6e4c2') From 2058f63b4e2d67cbe835499a43d8415f3c1d940d Mon Sep 17 00:00:00 2001 From: Rohit Goswami Date: Sat, 15 Jun 2024 22:58:00 +0000 Subject: [PATCH 60/91] BUG: Use an F77 snippet for sanity testing Fortran Closes gh-13319 --- mesonbuild/compilers/fortran.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mesonbuild/compilers/fortran.py b/mesonbuild/compilers/fortran.py index 9b288e99bfb8..ad266e9751a9 100644 --- a/mesonbuild/compilers/fortran.py +++ b/mesonbuild/compilers/fortran.py @@ -59,8 +59,8 @@ def _get_basic_compiler_args(self, env: 'Environment', mode: CompileCheckMode) - return cargs, largs def sanity_check(self, work_dir: str, environment: 'Environment') -> None: - source_name = 'sanitycheckf.f90' - code = 'program main; print *, "Fortran compilation is working."; end program\n' + source_name = 'sanitycheckf.f' + code = ' PROGRAM MAIN\n PRINT *, "Fortran compilation is working."\n END\n' return self._sanity_check_impl(work_dir, environment, source_name, code) def get_optimization_args(self, optimization_level: str) -> T.List[str]: From c199faf9807fb898e1c6744c43728dc87c93a0bc Mon Sep 17 00:00:00 2001 From: Sebastian Wick Date: Mon, 17 Jun 2024 01:16:37 +0200 Subject: [PATCH 61/91] cargo: Fall back to the checksum in Cargo.lock metadata table In ansi_term-0.12.1 the packages do not have a checksum entry but it can be found in the global metadata table. --- mesonbuild/cargo/interpreter.py | 5 ++++- mesonbuild/cargo/manifest.py | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/mesonbuild/cargo/interpreter.py b/mesonbuild/cargo/interpreter.py index 13568cd46cf8..33b9d6073e4b 100644 --- a/mesonbuild/cargo/interpreter.py +++ b/mesonbuild/cargo/interpreter.py @@ -751,13 +751,16 @@ def load_wraps(source_dir: str, subproject_dir: str) -> T.List[PackageDefinition # This is project's package, or one of its workspace members. pass elif source == 'registry+https://github.com/rust-lang/crates.io-index': + checksum = package.get('checksum') + if checksum is None: + checksum = cargolock['metadata'][f'checksum {name} {version} ({source})'] url = f'https://crates.io/api/v1/crates/{name}/{version}/download' directory = f'{name}-{version}' wraps.append(PackageDefinition.from_values(subp_name, subproject_dir, 'file', { 'directory': directory, 'source_url': url, 'source_filename': f'{directory}.tar.gz', - 'source_hash': package['checksum'], + 'source_hash': checksum, 'method': 'cargo', })) elif source.startswith('git+'): diff --git a/mesonbuild/cargo/manifest.py b/mesonbuild/cargo/manifest.py index 183d91efd44f..50c048991333 100644 --- a/mesonbuild/cargo/manifest.py +++ b/mesonbuild/cargo/manifest.py @@ -242,3 +242,4 @@ class CargoLock(TypedDict, total=False): version: str package: T.List[CargoLockPackage] + metadata: T.Dict[str, str] From a3d3efd3cfc6e9b0ce8a7536527e1e9ca134f80a Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Sun, 2 Jun 2024 11:33:07 +0200 Subject: [PATCH 62/91] Add meson test --max-lines Let's allow users to configure how many lines are shown at most when a test fails. --- data/shell-completions/bash/meson | 1 + data/shell-completions/zsh/_meson | 1 + docs/markdown/Unit-tests.md | 8 ++++++++ docs/markdown/snippets/test_max_lines.md | 6 ++++++ mesonbuild/mtest.py | 11 +++++++---- 5 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 docs/markdown/snippets/test_max_lines.md diff --git a/data/shell-completions/bash/meson b/data/shell-completions/bash/meson index dc437f10fd66..88dc15ec3225 100644 --- a/data/shell-completions/bash/meson +++ b/data/shell-completions/bash/meson @@ -580,6 +580,7 @@ _meson-test() { quiet timeout-multiplier setup + max-lines test-args ) diff --git a/data/shell-completions/zsh/_meson b/data/shell-completions/zsh/_meson index 402539f1ba3a..7d6d89b7ef73 100644 --- a/data/shell-completions/zsh/_meson +++ b/data/shell-completions/zsh/_meson @@ -196,6 +196,7 @@ local -a meson_commands=( '(--quiet -q)'{'--quiet','-q'}'[produce less output to the terminal]' '(--timeout-multiplier -t)'{'--timeout-multiplier','-t'}'[a multiplier for test timeouts]:Python floating-point number: ' '--setup[which test setup to use]:test setup: ' + '--max-lines[Maximum number of lines to show from a long test log]:Python integer number: ' '--test-args[arguments to pass to the tests]: : ' '*:Meson tests:__meson_test_names' ) diff --git a/docs/markdown/Unit-tests.md b/docs/markdown/Unit-tests.md index 6fda0f5f6861..b5d3a1b81831 100644 --- a/docs/markdown/Unit-tests.md +++ b/docs/markdown/Unit-tests.md @@ -274,6 +274,14 @@ other useful information as the environmental variables. This is useful, for example, when you run the tests on Travis-CI, Jenkins and the like. +By default, the output from tests will be limited to the last 100 lines. The +maximum number of lines to show can be configured with the `--max-lines` option +*(added 1.5.0)*: + +```console +$ meson test --max-lines=1000 testname +``` + **Timeout** In the test case options, the `timeout` option is specified in a number of seconds. diff --git a/docs/markdown/snippets/test_max_lines.md b/docs/markdown/snippets/test_max_lines.md new file mode 100644 index 000000000000..ea10fbb2791b --- /dev/null +++ b/docs/markdown/snippets/test_max_lines.md @@ -0,0 +1,6 @@ +## The Meson test program supports a new "--max-lines" argument + +By default `meson test` only shows the last 100 lines of test output from tests +that produce large amounts of output. This default can now be changed with the +new `--max-lines` option. For example, `--max-lines=1000` will increase the +maximum number of log output lines from 100 to 1000. diff --git a/mesonbuild/mtest.py b/mesonbuild/mtest.py index 03d2eb254df0..c0ddb30bacf7 100644 --- a/mesonbuild/mtest.py +++ b/mesonbuild/mtest.py @@ -161,6 +161,8 @@ def add_arguments(parser: argparse.ArgumentParser) -> None: help='Which test setup to use.') parser.add_argument('--test-args', default=[], type=split_args, help='Arguments to pass to the specified test(s) or all tests') + parser.add_argument('--max-lines', default=100, dest='max_lines', type=int, + help='Maximum number of lines to show from a long test log. Since 1.5.0.') parser.add_argument('args', nargs='*', help='Optional list of test names to run. "testname" to run all tests with that name, ' '"subprojname:testname" to specifically run "testname" from "subprojname", ' @@ -510,7 +512,8 @@ class ConsoleLogger(TestLogger): HLINE = "\u2015" RTRI = "\u25B6 " - def __init__(self) -> None: + def __init__(self, max_lines: int) -> None: + self.max_lines = max_lines self.running_tests: OrderedSet['TestRun'] = OrderedSet() self.progress_test: T.Optional['TestRun'] = None self.progress_task: T.Optional[asyncio.Future] = None @@ -652,10 +655,10 @@ def shorten_log(self, harness: 'TestHarness', result: 'TestRun') -> str: return log lines = log.splitlines() - if len(lines) < 100: + if len(lines) < self.max_lines: return log else: - return str(mlog.bold('Listing only the last 100 lines from a long log.\n')) + '\n'.join(lines[-100:]) + return str(mlog.bold(f'Listing only the last {self.max_lines} lines from a long log.\n')) + '\n'.join(lines[-self.max_lines:]) def print_log(self, harness: 'TestHarness', result: 'TestRun') -> None: if not result.verbose: @@ -1591,7 +1594,7 @@ def __init__(self, options: argparse.Namespace): self.name_max_len = 0 self.is_run = False self.loggers: T.List[TestLogger] = [] - self.console_logger = ConsoleLogger() + self.console_logger = ConsoleLogger(options.max_lines) self.loggers.append(self.console_logger) self.need_console = False self.ninja: T.List[str] = None From bcbf0685492c61fbc9acac3e7b808a5036dd2439 Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Tue, 18 Jun 2024 16:22:15 +0200 Subject: [PATCH 63/91] Improve `nm` usage in symbolextractor script on macOS This fixes the unit test `TestAllPlatformTests.test_noop_changes_cause_no_rebuilds`, when run with an `nm` binary from `cctools-port` (as shipped by conda-forge, see https://github.com/conda-forge/cctools-and-ld64-feedstock). It also addresses the issue discussed in https://github.com/mesonbuild/meson/discussions/11131, and this build warning: ``` [48/1383] Generating symbol file scipy/special/libsf_error_state.dylib.p/libsf_error_state.dylib.symbols WARNING: ['arm64-apple-darwin20.0.0-nm'] does not work. Relinking will always happen on source changes. error: arm64-apple-darwin20.0.0-nm: invalid argument -- ``` as reported in scipy#20740. The unit test traceback was: ``` > self.assertBuildRelinkedOnlyTarget('mylib') E AssertionError: Lists differ: ['mylib', 'prog'] != ['mylib'] E E First list contains 1 additional elements. E First extra element 1: E 'prog' E E - ['mylib', 'prog'] E + ['mylib'] unittests/allplatformstests.py:1292: AssertionError ``` The `nm` shipped by Apple yields the exact same results either way; the man page for `nm` only lists the single-character form so this seems preferred either way. --- mesonbuild/scripts/symbolextractor.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/mesonbuild/scripts/symbolextractor.py b/mesonbuild/scripts/symbolextractor.py index 52b9b80c51ea..5c45253d5702 100644 --- a/mesonbuild/scripts/symbolextractor.py +++ b/mesonbuild/scripts/symbolextractor.py @@ -134,9 +134,10 @@ def osx_syms(libfilename: str, outfilename: str) -> None: match = i break result = [arr[match + 2], arr[match + 5]] # Libreoffice stores all 5 lines but the others seem irrelevant. - # Get a list of all symbols exported - output = call_tool('nm', ['--extern-only', '--defined-only', - '--format=posix', libfilename]) + # Get a list of all symbols exported. `nm -g -U -P` is equivalent to, and more portable than, + # `nm --extern-only --defined-only --format=posix`; cctools-port only understands the one-character form, + # as does `nm` on very old macOS versions, (see meson#11131). `llvm-nm` understands both forms. + output = call_tool('nm', ['-g', '-U', '-P', libfilename]) if not output: dummy_syms(outfilename) return From 9f4253164aad64297b7d8c001d953217582b2196 Mon Sep 17 00:00:00 2001 From: Charles Brunet Date: Wed, 22 May 2024 09:27:59 -0400 Subject: [PATCH 64/91] Catch format configuration parse error --- mesonbuild/mformat.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/mesonbuild/mformat.py b/mesonbuild/mformat.py index 49ece4f034a4..68c9a1f8f45e 100644 --- a/mesonbuild/mformat.py +++ b/mesonbuild/mformat.py @@ -6,7 +6,7 @@ import argparse import re import typing as T -from configparser import ConfigParser, MissingSectionHeaderError +from configparser import ConfigParser, MissingSectionHeaderError, ParsingError from copy import deepcopy from dataclasses import dataclass, field, fields, asdict from pathlib import Path @@ -829,7 +829,10 @@ def load_configuration(self, configuration_file: T.Optional[Path]) -> FormatterC config = FormatterConfig() if configuration_file: cp = DefaultConfigParser() - cp.read_default(configuration_file) + try: + cp.read_default(configuration_file) + except ParsingError as e: + raise MesonException(f'Unable to parse configuration file "{configuration_file}":\n{e}') from e for f in fields(config): getter = f.metadata['getter'] From bef2fbf75bcea180affc19ebba280708109247a2 Mon Sep 17 00:00:00 2001 From: Charles Brunet Date: Wed, 22 May 2024 09:22:19 -0400 Subject: [PATCH 65/91] Fix crash in meson format There was a case where a trailing comma was missing a whitespaces attribute Fixes #13242 --- mesonbuild/mformat.py | 1 + test cases/format/1 default/gh13242.meson | 18 ++++++++++++++++++ test cases/format/1 default/meson.build | 1 + 3 files changed, 20 insertions(+) create mode 100644 test cases/format/1 default/gh13242.meson diff --git a/mesonbuild/mformat.py b/mesonbuild/mformat.py index 68c9a1f8f45e..5e37019310fa 100644 --- a/mesonbuild/mformat.py +++ b/mesonbuild/mformat.py @@ -626,6 +626,7 @@ def visit_ArgumentNode(self, node: mparser.ArgumentNode) -> None: if need_comma and not has_trailing_comma: comma = mparser.SymbolNode(mparser.Token('comma', node.filename, 0, 0, 0, (0, 0), ',')) comma.condition_level = node.condition_level + comma.whitespaces = mparser.WhitespaceNode(mparser.Token('whitespace', node.filename, 0, 0, 0, (0, 0), '')) node.commas.append(comma) elif has_trailing_comma and not need_comma: node.commas.pop(-1) diff --git a/test cases/format/1 default/gh13242.meson b/test cases/format/1 default/gh13242.meson new file mode 100644 index 000000000000..b9122ec812d8 --- /dev/null +++ b/test cases/format/1 default/gh13242.meson @@ -0,0 +1,18 @@ +# Minimized meson.build +test( + args: [ + shared_library( + f'tstlib-@name@', + build_by_default: false, + override_options: opt, + ), + ], +) + +test( + should_fail: (settings.get('x', false) and not settings['y'] and dep.version( + + ).version_compare( + '>=1.2.3', + )), +) diff --git a/test cases/format/1 default/meson.build b/test cases/format/1 default/meson.build index 5b5b1152af5e..35e5b9694fb8 100644 --- a/test cases/format/1 default/meson.build +++ b/test cases/format/1 default/meson.build @@ -7,6 +7,7 @@ meson_files = { 'self': files('meson.build'), 'comments': files('crazy_comments.meson'), 'indentation': files('indentation.meson'), + 'gh13242': files('gh13242.meson'), } foreach name, f : meson_files From 8967090149cb89f7b82fc6c8f72764975912fa58 Mon Sep 17 00:00:00 2001 From: Charles Brunet Date: Wed, 12 Jun 2024 14:00:13 -0400 Subject: [PATCH 66/91] mformat: fix else token not correctly indented fixes #13316 --- mesonbuild/mformat.py | 4 ++-- test cases/format/1 default/indentation.meson | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/mesonbuild/mformat.py b/mesonbuild/mformat.py index 5e37019310fa..e20e45851820 100644 --- a/mesonbuild/mformat.py +++ b/mesonbuild/mformat.py @@ -458,10 +458,10 @@ def visit_IfClauseNode(self, node: mparser.IfClauseNode) -> None: super().visit_IfClauseNode(node) self.move_whitespaces(node.endif, node) + for if_node in node.ifs: + if_node.whitespaces.value += node.condition_level * self.config.indent_by if isinstance(node.elseblock, mparser.ElseNode): node.elseblock.whitespaces.value += node.condition_level * self.config.indent_by - else: - node.ifs[-1].whitespaces.value += node.condition_level * self.config.indent_by def visit_IfNode(self, node: mparser.IfNode) -> None: super().visit_IfNode(node) diff --git a/test cases/format/1 default/indentation.meson b/test cases/format/1 default/indentation.meson index 31a809abff51..b1edc3a16a52 100644 --- a/test cases/format/1 default/indentation.meson +++ b/test cases/format/1 default/indentation.meson @@ -69,5 +69,9 @@ if meson.project_version().version_compare('>1.2') ], } endforeach + elif 42 in d + d += {'foo': 43} + else # ensure else is correctly indented (issue #13316) + k = 'k' endif endif From 410bdf8c6c72fd1a2772e86a2a14298f64f1377b Mon Sep 17 00:00:00 2001 From: Matt Jolly Date: Thu, 6 Jun 2024 22:03:13 +1000 Subject: [PATCH 67/91] `configure_file`: update \@ escape logic When configuring a 'meson' or 'cmake@' style file, add a case for escaped variables using matched pairs of `\@` i.e. `\@foo\@ -> @foo@`. The match for @var@ has been amended with a negative lookbehind to ensure that any occurrances of `\@foo@` are not evaluated to `\bar`. The previous behaviour, matching `\@` and escaping only that character, had undesirable side effects including mangling valid perl when configuring files. Closes: https://github.com/mesonbuild/meson/issues/7165 --- mesonbuild/utils/universal.py | 39 ++++++++++++------- .../common/14 configure file/config6.h.in | 37 ++++++++++++++---- .../common/14 configure file/meson.build | 2 + test cases/common/14 configure file/prog6.c | 12 ++++-- 4 files changed, 64 insertions(+), 26 deletions(-) diff --git a/mesonbuild/utils/universal.py b/mesonbuild/utils/universal.py index c831169c523d..6aee268ee21b 100644 --- a/mesonbuild/utils/universal.py +++ b/mesonbuild/utils/universal.py @@ -1182,24 +1182,21 @@ def do_replacement(regex: T.Pattern[str], line: str, variable_format: Literal['meson', 'cmake', 'cmake@'], confdata: T.Union[T.Dict[str, T.Tuple[str, T.Optional[str]]], 'ConfigurationData']) -> T.Tuple[str, T.Set[str]]: missing_variables: T.Set[str] = set() - if variable_format == 'cmake': - start_tag = '${' - backslash_tag = '\\${' - else: - start_tag = '@' - backslash_tag = '\\@' def variable_replace(match: T.Match[str]) -> str: - # Pairs of escape characters before '@' or '\@' + # Pairs of escape characters before '@', '\@', '${' or '\${' if match.group(0).endswith('\\'): num_escapes = match.end(0) - match.start(0) return '\\' * (num_escapes // 2) - # Single escape character and '@' - elif match.group(0) == backslash_tag: - return start_tag - # Template variable to be replaced + # Handle cmake escaped \${} tags + elif variable_format == 'cmake' and match.group(0) == '\\${': + return '${' + # \@escaped\@ variables + elif match.groupdict().get('escaped') is not None: + return match.group('escaped')[1:-2]+'@' else: - varname = match.group(1) + # Template variable to be replaced + varname = match.group('variable') var_str = '' if varname in confdata: var, _ = confdata.get(varname) @@ -1280,11 +1277,23 @@ def get_cmake_define(line: str, confdata: 'ConfigurationData') -> str: def get_variable_regex(variable_format: Literal['meson', 'cmake', 'cmake@'] = 'meson') -> T.Pattern[str]: # Only allow (a-z, A-Z, 0-9, _, -) as valid characters for a define - # Also allow escaping '@' with '\@' if variable_format in {'meson', 'cmake@'}: - regex = re.compile(r'(?:\\\\)+(?=\\?@)|\\@|@([-a-zA-Z0-9_]+)@') + # Also allow escaping pairs of '@' with '\@' + regex = re.compile(r''' + (?:\\\\)+(?=\\?@) # Matches multiple backslashes followed by an @ symbol + | # OR + (?[-a-zA-Z0-9_]+)@ # Match a variable enclosed in @ symbols and capture the variable name; no matches beginning with '\@' + | # OR + (?P\\@[-a-zA-Z0-9_]+\\@) # Match an escaped variable enclosed in @ symbols + ''', re.VERBOSE) else: - regex = re.compile(r'(?:\\\\)+(?=\\?\$)|\\\${|\${([-a-zA-Z0-9_]+)}') + regex = re.compile(r''' + (?:\\\\)+(?=\\?\$) # Match multiple backslashes followed by a dollar sign + | # OR + \\\${ # Match a backslash followed by a dollar sign and an opening curly brace + | # OR + \${(?P[-a-zA-Z0-9_]+)} # Match a variable enclosed in curly braces and capture the variable name + ''', re.VERBOSE) return regex def do_conf_str(src: str, data: T.List[str], confdata: 'ConfigurationData', diff --git a/test cases/common/14 configure file/config6.h.in b/test cases/common/14 configure file/config6.h.in index 9719f8715210..0a9154282979 100644 --- a/test cases/common/14 configure file/config6.h.in +++ b/test cases/common/14 configure file/config6.h.in @@ -1,19 +1,40 @@ /* No escape */ #define MESSAGE1 "@var1@" -/* Single escape means no replace */ -#define MESSAGE2 "\@var1@" +/* Escaped whole variable */ +#define MESSAGE2 "\\@var1\\@" /* Replace pairs of escapes before '@' or '\@' with escape characters * (note we have to double number of pairs due to C string escaping) */ #define MESSAGE3 "\\\\@var1@" -/* Pairs of escapes and then single escape to avoid replace */ -#define MESSAGE4 "\\\\\@var1@" +/* Pairs of escapes and then an escaped variable */ +#define MESSAGE4 "\\\\\@var1\@" -/* Check escaped variable does not overlap following variable */ -#define MESSAGE5 "\@var1@var2@" +/* We don't gobble \@ prefixing some text */ +#define MESSAGE5 "\\\\@var1" -/* Check escape character outside variables */ -#define MESSAGE6 "\\ @ \@ \\\\@ \\\\\@" +/* Check escape character outside variables + \ @ \@ */ +#define MESSAGE6 "\\ @ \\\\@" + +/* Catch any edge cases */ + +/* no substitution - not a variable */ +#define MESSAGE7 "@var1" + +/* Escaped variable followed by another variable */ +#define MESSAGE8 "\\\\@var1@var2@" + +/* Variable followed by another variable */ +#define MESSAGE9 "@var1@var2@" + +/* Variable followed by another variable and escaped */ +#define MESSAGE10 "@var1@var2\\\\@" + +/* Lots of substitutions in a row*/ +#define MESSAGE11 "@var1@@var2@@var3@@var4@" + +/* This should never happen in the real world, right? */ +#define MESSAGE12 "@var1@var2\\\\@var3@var4\\\\@" diff --git a/test cases/common/14 configure file/meson.build b/test cases/common/14 configure file/meson.build index 90a468f5e191..8e7d429c722c 100644 --- a/test cases/common/14 configure file/meson.build +++ b/test cases/common/14 configure file/meson.build @@ -143,6 +143,8 @@ test('test5', executable('prog5', 'prog5.c')) conf6 = configuration_data() conf6.set('var1', 'foo') conf6.set('var2', 'bar') +conf6.set('var3', 'baz') +conf6.set('var4', 'qux') configure_file( input : 'config6.h.in', output : '@BASENAME@', diff --git a/test cases/common/14 configure file/prog6.c b/test cases/common/14 configure file/prog6.c index 57f55860515c..b39f9daa7d4b 100644 --- a/test cases/common/14 configure file/prog6.c +++ b/test cases/common/14 configure file/prog6.c @@ -4,8 +4,14 @@ int main(void) { return strcmp(MESSAGE1, "foo") || strcmp(MESSAGE2, "@var1@") - || strcmp(MESSAGE3, "\\foo") + || strcmp(MESSAGE3, "\\@var1@") || strcmp(MESSAGE4, "\\@var1@") - || strcmp(MESSAGE5, "@var1bar") - || strcmp(MESSAGE6, "\\ @ @ \\@ \\@"); + || strcmp(MESSAGE5, "\\@var1") + || strcmp(MESSAGE6, "\\ @ \\@") + || strcmp(MESSAGE7, "@var1") + || strcmp(MESSAGE8, "\\@var1bar") + || strcmp(MESSAGE9, "foovar2@") + || strcmp(MESSAGE10, "foovar2\\@") + || strcmp(MESSAGE11, "foobarbazqux") + || strcmp(MESSAGE12, "foovar2\\@var3@var4\\@"); } From 24cddb6901d8c0421bcff4daa465e422e2d27f44 Mon Sep 17 00:00:00 2001 From: Campbell Jones Date: Wed, 19 Jun 2024 18:48:46 -0400 Subject: [PATCH 68/91] create_test_serialisation: Dedup deps before joining ld_lib paths In large monolithic codebases with many intertwined shared libraries, test serialization can cause configuration times to balloon massively from a single test() invocation due to concatenating the same paths many times over. This commit adds an initial set in which all dependencies on a test target are stored before their paths are constructed to form the test target's LD_LIB_PATH. In testing on a particularly large codebase, this commit reduced total configuration time by a factor of almost 8x. --- mesonbuild/backend/backends.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index c1a72f13d003..055211090d09 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -1263,12 +1263,16 @@ def create_test_serialisation(self, tests: T.List['Test']) -> T.List[TestSeriali t_env = copy.deepcopy(t.env) if not machine.is_windows() and not machine.is_cygwin() and not machine.is_darwin(): - ld_lib_path: T.Set[str] = set() + ld_lib_path_libs: T.Set[build.SharedLibrary] = set() for d in depends: if isinstance(d, build.BuildTarget): for l in d.get_all_link_deps(): if isinstance(l, build.SharedLibrary): - ld_lib_path.add(os.path.join(self.environment.get_build_dir(), l.get_subdir())) + ld_lib_path_libs.add(l) + + env_build_dir = self.environment.get_build_dir() + ld_lib_path: T.Set[str] = set(os.path.join(env_build_dir, l.get_subdir()) for l in ld_lib_path_libs) + if ld_lib_path: t_env.prepend('LD_LIBRARY_PATH', list(ld_lib_path), ':') From c21b886ba8a60cce7fa56e4be40bd7547129fb00 Mon Sep 17 00:00:00 2001 From: Artturin Date: Fri, 31 May 2024 00:04:41 +0300 Subject: [PATCH 69/91] dependencies/boost.py: Allow getting `lib_dir` and `include_dir` via pkg-config `boost_root` doesn't work if lib and include are in different directories like in the `nix` `boost` package. The `prefix` checking could probably be removed, in 2019 conan (the reason why the check was added) had `libdir` and `includedir` in its generated pkg-config file https://www.github.com/mesonbuild/meson/issues/5438#issuecomment-498761454 If there's no return then boost isn't found for some reason, further logic is unnecessary in any case if direct paths are passed. `roots += [Path(boost_lib_dir), Path(boost_inc_dir)]` did not work --- mesonbuild/dependencies/boost.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/mesonbuild/dependencies/boost.py b/mesonbuild/dependencies/boost.py index 02aa1ea85cce..7a461637c4a9 100644 --- a/mesonbuild/dependencies/boost.py +++ b/mesonbuild/dependencies/boost.py @@ -653,9 +653,19 @@ def detect_roots(self) -> None: try: boost_pc = PkgConfigDependency('boost', self.env, {'required': False}) if boost_pc.found(): - boost_root = boost_pc.get_variable(pkgconfig='prefix') - if boost_root: - roots += [Path(boost_root)] + boost_lib_dir = boost_pc.get_variable(pkgconfig='libdir') + boost_inc_dir = boost_pc.get_variable(pkgconfig='includedir') + if boost_lib_dir and boost_inc_dir: + mlog.debug('Trying to find boost with:') + mlog.debug(f' - boost_includedir = {Path(boost_inc_dir)}') + mlog.debug(f' - boost_librarydir = {Path(boost_lib_dir)}') + + self.detect_split_root(Path(boost_inc_dir), Path(boost_lib_dir)) + return + else: + boost_root = boost_pc.get_variable(pkgconfig='prefix') + if boost_root: + roots += [Path(boost_root)] except DependencyException: pass From 5597b6711ddb05499a9d42a5d16ff3be9d549fe8 Mon Sep 17 00:00:00 2001 From: Jade Lovelace Date: Sun, 23 Jun 2024 00:26:18 -0700 Subject: [PATCH 70/91] tests: fix OpenAL test case on case sensitive fs on macOS Preface: why are we doing this? For reasons of cross-platform interop, the Lix team is strongly considering switching to build on a case sensitive filesystem in the macOS installation, since otherwise storing case overlapping filenames is busted and requires very very bad hacks: https://git.lix.systems/lix-project/lix/issues/332 What's wrong: Command line: `clang++ '/nix/temp/meson/b d01bff197e/meson-private/tmpjqid64j1/testfile.cpp' -o '/nix/temp/meson/b d01bff197e/meson-private/tmpjqid64j1/output.exe' -O0 -fpermissive -Werror=implicit-function-declaration -F/nix/store/qa92ravmclyraw7b46cz3q3m834mmbw9-apple-framework-OpenAL/Library/Frameworks -framework openal` -> 1 stderr: ld: framework not found openal clang-16: error: linker command failed with exit code 1 (use -v to see invocation) Why is that happening: $ ls /nix/store/qa92ravmclyraw7b46cz3q3m834mmbw9-apple-framework-OpenAL/Library/Frameworks OpenAL.framework So the test was relying on case insensitive fs, which is not a reliable assumption on all macOS installations (since weird people like us can set their fs to case sensitive!). --- test cases/osx/9 framework recasting/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test cases/osx/9 framework recasting/meson.build b/test cases/osx/9 framework recasting/meson.build index f139485ac2b9..2b1584370d52 100644 --- a/test cases/osx/9 framework recasting/meson.build +++ b/test cases/osx/9 framework recasting/meson.build @@ -1,5 +1,5 @@ project('framework recasting', 'c', 'cpp') -x = dependency('openal') +x = dependency('OpenAL') y = executable('tt', files('main.cpp'), dependencies: x) From 783183260d64e9d5a73b8f2cc69475b126dcebb0 Mon Sep 17 00:00:00 2001 From: Walkusz Date: Thu, 20 Jun 2024 16:33:30 +0200 Subject: [PATCH 71/91] coverage: Change --html-details to --html-nested (gcovr) --html-nested option is used to create a separate web page for each file and directory. Each of these web pages includes the contents of file with annotations that summarize code coverage. Signed-off-by: Ewelina Walkusz --- mesonbuild/scripts/coverage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mesonbuild/scripts/coverage.py b/mesonbuild/scripts/coverage.py index bded052d4a49..17a4a10ae55f 100644 --- a/mesonbuild/scripts/coverage.py +++ b/mesonbuild/scripts/coverage.py @@ -161,7 +161,7 @@ def coverage(outputs: T.List[str], source_root: str, subproject_root: str, build os.mkdir(htmloutdir) subprocess.check_call(gcovr_base_cmd + gcovr_config + ['--html', - '--html-details', + '--html-nested', '--print-summary', '-o', os.path.join(htmloutdir, 'index.html'), ] + gcov_exe_args) From a6258eb5e2660598b1039cf6ee88692a9091affd Mon Sep 17 00:00:00 2001 From: Tomas Popela Date: Mon, 10 Jun 2024 09:01:58 +0200 Subject: [PATCH 72/91] Fix subproject typo in interpreter.py Found by Coverity scan. --- mesonbuild/interpreter/interpreter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index ef28d864fb47..83363fbbe1e5 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -3421,7 +3421,7 @@ def build_target(self, node: mparser.BaseNode, args: T.Tuple[str, SourcesVarargs if kwargs['implib']: if kwargs['export_dynamic'] is False: - FeatureDeprecated.single_use('implib overrides explict export_dynamic off', '1.3.0', self.subprojct, + FeatureDeprecated.single_use('implib overrides explict export_dynamic off', '1.3.0', self.subproject, 'Do not set ths if want export_dynamic disabled if implib is enabled', location=node) kwargs['export_dynamic'] = True From 1951fe5cf0a69b0467859a35cc8e06c16de0a76a Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Sun, 16 Jun 2024 17:50:06 -0500 Subject: [PATCH 73/91] clang-tidy: use -quiet This adds the `-quiet` option when invoking clang-tidy for the generated `clang-tidy` target. (Note that the `clang-tidy-fix` target already does so.) This prevents messages like ``` Suppressed 1084 warnings (1084 in non-user code). Use -header-filter=.* to display errors from all non-system headers. Use -system-headers to display errors from system headers as well. ``` from being repeated for every file, which drowns out the actual warnings/errors from clang-tidy when more than a few files are processed. Also the tip about `-header-fileter` and `-system-headers` is not very useful here because Meson doesn't currently provide a way to supply these options to clang-tidy. Even with `-quiet`, clang-tidy still prints a line like `1084 warnings generated.` for each file. --- mesonbuild/scripts/clangtidy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mesonbuild/scripts/clangtidy.py b/mesonbuild/scripts/clangtidy.py index 353cdc19c288..1e0c4a5a396a 100644 --- a/mesonbuild/scripts/clangtidy.py +++ b/mesonbuild/scripts/clangtidy.py @@ -11,7 +11,7 @@ import typing as T def run_clang_tidy(fname: Path, builddir: Path) -> subprocess.CompletedProcess: - return subprocess.run(['clang-tidy', '-p', str(builddir), str(fname)]) + return subprocess.run(['clang-tidy', '-quiet', '-p', str(builddir), str(fname)]) def run_clang_tidy_fix(fname: Path, builddir: Path) -> subprocess.CompletedProcess: return subprocess.run(['run-clang-tidy', '-fix', '-format', '-quiet', '-p', str(builddir), str(fname)]) From c0ca35c8fd796bea4bd92f88b03cb3d6abd2096c Mon Sep 17 00:00:00 2001 From: TheHillBright <150074496+TheHillBright@users.noreply.github.com> Date: Thu, 20 Jun 2024 07:38:52 +0000 Subject: [PATCH 74/91] feat(compilers): cppm extension support recognize the file extension `cppm` as c++ source file --- mesonbuild/compilers/compilers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index 4a1fd984cb6a..c03f1fd372a0 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -52,7 +52,7 @@ # First suffix is the language's default. lang_suffixes = { 'c': ('c',), - 'cpp': ('cpp', 'cc', 'cxx', 'c++', 'hh', 'hpp', 'ipp', 'hxx', 'ino', 'ixx', 'C', 'H'), + 'cpp': ('cpp', 'cppm', 'cc', 'cxx', 'c++', 'hh', 'hpp', 'ipp', 'hxx', 'ino', 'ixx', 'C', 'H'), 'cuda': ('cu',), # f90, f95, f03, f08 are for free-form fortran ('f90' recommended) # f, for, ftn, fpp are for fixed-form fortran ('f' or 'for' recommended) From a111c28ecef721af960721c26b40f4e5108760c7 Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Mon, 27 May 2024 22:55:39 +0200 Subject: [PATCH 75/91] Add support for detecting free-threaded Python on Windows This does a couple of things: 1. Scrape the `Py_GIL_DISABLED` sysconfig var, which is the best way as of today to determine whether the target interpreter was built with `--disable-gil` 2. link against the correct libpython 3. On Windows, work around a known issue in the python.org installer with a missing define in `pyconfig.h`, which the CPython devs have said is unlikely to be fixed since headers are shared between the regular and free-threaded builds in a single NSIS installer. --- mesonbuild/dependencies/python.py | 14 +++++++++++++- mesonbuild/scripts/python_info.py | 4 ++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/mesonbuild/dependencies/python.py b/mesonbuild/dependencies/python.py index 17c807cafe79..851c63070515 100644 --- a/mesonbuild/dependencies/python.py +++ b/mesonbuild/dependencies/python.py @@ -29,6 +29,7 @@ class PythonIntrospectionDict(TypedDict): install_paths: T.Dict[str, str] is_pypy: bool is_venv: bool + is_freethreaded: bool link_libpython: bool sysconfig_paths: T.Dict[str, str] paths: T.Dict[str, str] @@ -91,6 +92,7 @@ def __init__(self, name: str, command: T.Optional[T.List[str]] = None, 'install_paths': {}, 'is_pypy': False, 'is_venv': False, + 'is_freethreaded': False, 'link_libpython': False, 'sysconfig_paths': {}, 'paths': {}, @@ -146,6 +148,7 @@ def __init__(self, python_holder: 'BasicPythonExternalProgram', embed: bool): self.variables = python_holder.info['variables'] self.paths = python_holder.info['paths'] self.is_pypy = python_holder.info['is_pypy'] + self.is_freethreaded = python_holder.info['is_freethreaded'] # The "-embed" version of python.pc / python-config was introduced in 3.8, # and distutils extension linking was changed to be considered a non embed # usage. Before then, this dependency always uses the embed=True handling @@ -161,6 +164,12 @@ def __init__(self, python_holder: 'BasicPythonExternalProgram', embed: bool): else: self.major_version = 2 + # pyconfig.h is shared between regular and free-threaded builds in the + # Windows installer from python.org, and hence does not define + # Py_GIL_DISABLED correctly. So do it here: + if mesonlib.is_windows() and self.is_freethreaded: + self.compile_args += ['-DPy_GIL_DISABLED'] + def find_libpy(self, environment: 'Environment') -> None: if self.is_pypy: if self.major_version == 3: @@ -220,7 +229,10 @@ def get_windows_link_args(self, limited_api: bool) -> T.Optional[T.List[str]]: else: if limited_api: vernum = vernum[0] - libpath = Path('libs') / f'python{vernum}.lib' + if self.is_freethreaded: + libpath = Path('libs') / f'python{vernum}t.lib' + else: + libpath = Path('libs') / f'python{vernum}.lib' # For a debug build, pyconfig.h may force linking with # pythonX_d.lib (see meson#10776). This cannot be avoided # and won't work unless we also have a debug build of diff --git a/mesonbuild/scripts/python_info.py b/mesonbuild/scripts/python_info.py index 5b048ca4288a..6aab380451a9 100755 --- a/mesonbuild/scripts/python_info.py +++ b/mesonbuild/scripts/python_info.py @@ -106,6 +106,9 @@ def links_against_libpython(): if is_pypy: limited_api_suffix = suffix +# Whether we're targeting a free-threaded CPython interpreter +is_freethreaded = bool(variables.get('Py_GIL_DISABLED', False)) + print(json.dumps({ 'variables': variables, 'paths': paths, @@ -118,4 +121,5 @@ def links_against_libpython(): 'link_libpython': links_against_libpython(), 'suffix': suffix, 'limited_api_suffix': limited_api_suffix, + 'is_freethreaded': is_freethreaded, })) From 9be6e653d49957c974af2c171257cbaf942abbb9 Mon Sep 17 00:00:00 2001 From: Dudemanguy Date: Sat, 22 Jun 2024 10:59:05 -0500 Subject: [PATCH 76/91] find_program: add a kwarg to specify custom version argument When trying to get the version of a program, meson was previously hardcoded to run the binary with `--version`. This does work with the vast majority of programs, but there are a few outliers (e.g. ffmpeg) which have an unusual argument for printing out the version. Support these programs by introducing a version_argument kwarg in find_program which allows users to override `--version` with whatever the custom argument for printing the version may be for the program. --- .../snippets/find_program_version_argument.md | 12 ++++++++++++ docs/yaml/functions/find_program.yaml | 9 ++++++++- mesonbuild/dependencies/python.py | 1 + mesonbuild/interpreter/interpreter.py | 9 +++++++-- mesonbuild/programs.py | 5 +++-- test cases/common/26 find program/meson.build | 3 +++ .../26 find program/print-version-custom-argument.py | 8 ++++++++ 7 files changed, 42 insertions(+), 5 deletions(-) create mode 100644 docs/markdown/snippets/find_program_version_argument.md create mode 100644 test cases/common/26 find program/print-version-custom-argument.py diff --git a/docs/markdown/snippets/find_program_version_argument.md b/docs/markdown/snippets/find_program_version_argument.md new file mode 100644 index 000000000000..99fe62109776 --- /dev/null +++ b/docs/markdown/snippets/find_program_version_argument.md @@ -0,0 +1,12 @@ +## New version_argument kwarg for find_program + +When finding an external program with `find_program`, the `version_argument` +can be used to override the default `--version` argument when trying to parse +the version of the program. + +For example, if the following is used: +```meson +foo = find_program('foo', version_argument: '-version') +``` + +meson will internally run `foo -version` when trying to find the version of `foo`. diff --git a/docs/yaml/functions/find_program.yaml b/docs/yaml/functions/find_program.yaml index 4a17e8637364..1899941ab0e8 100644 --- a/docs/yaml/functions/find_program.yaml +++ b/docs/yaml/functions/find_program.yaml @@ -102,13 +102,20 @@ kwargs: since: 0.52.0 description: | Specifies the required version, see - [[dependency]] for argument format. The version of the program + [[dependency]] for argument format. By default, the version of the program is determined by running `program_name --version` command. If stdout is empty it fallbacks to stderr. If the output contains more text than simply a version number, only the first occurrence of numbers separated by dots is kept. If the output is more complicated than that, the version checking will have to be done manually using [[run_command]]. + version_argument: + type: str + since: 1.5.0 + description: | + Specifies the argument to pass when trying to find the version of the program. + If this is unspecified, `program_name --version` will be used. + dirs: type: list[str] since: 0.53.0 diff --git a/mesonbuild/dependencies/python.py b/mesonbuild/dependencies/python.py index 851c63070515..db4944bab777 100644 --- a/mesonbuild/dependencies/python.py +++ b/mesonbuild/dependencies/python.py @@ -83,6 +83,7 @@ def __init__(self, name: str, command: T.Optional[T.List[str]] = None, self.command = ext_prog.command self.path = ext_prog.path self.cached_version = None + self.version_arg = '--version' # We want strong key values, so we always populate this with bogus data. # Otherwise to make the type checkers happy we'd have to do .get() for diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index 83363fbbe1e5..13f7f224dcc7 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -1642,12 +1642,13 @@ def find_program_impl(self, args: T.List[mesonlib.FileOrString], required: bool = True, silent: bool = True, wanted: T.Union[str, T.List[str]] = '', search_dirs: T.Optional[T.List[str]] = None, + version_arg: T.Optional[str] = '', version_func: T.Optional[ProgramVersionFunc] = None ) -> T.Union['ExternalProgram', 'build.Executable', 'OverrideProgram']: args = mesonlib.listify(args) extra_info: T.List[mlog.TV_Loggable] = [] - progobj = self.program_lookup(args, for_machine, default_options, required, search_dirs, wanted, version_func, extra_info) + progobj = self.program_lookup(args, for_machine, default_options, required, search_dirs, wanted, version_arg, version_func, extra_info) if progobj is None or not self.check_program_version(progobj, wanted, version_func, extra_info): progobj = self.notfound_program(args) @@ -1672,6 +1673,7 @@ def program_lookup(self, args: T.List[mesonlib.FileOrString], for_machine: Machi required: bool, search_dirs: T.List[str], wanted: T.Union[str, T.List[str]], + version_arg: T.Optional[str], version_func: T.Optional[ProgramVersionFunc], extra_info: T.List[mlog.TV_Loggable] ) -> T.Optional[T.Union[ExternalProgram, build.Executable, OverrideProgram]]: @@ -1697,6 +1699,8 @@ def program_lookup(self, args: T.List[mesonlib.FileOrString], for_machine: Machi prog = ExternalProgram('python3', mesonlib.python_command, silent=True) progobj = prog if prog.found() else None + if isinstance(progobj, ExternalProgram) and version_arg: + progobj.version_arg = version_arg if progobj and not self.check_program_version(progobj, wanted, version_func, extra_info): progobj = None @@ -1756,6 +1760,7 @@ def find_program_fallback(self, fallback: str, args: T.List[mesonlib.FileOrStrin REQUIRED_KW, KwargInfo('dirs', ContainerTypeInfo(list, str), default=[], listify=True, since='0.53.0'), KwargInfo('version', ContainerTypeInfo(list, str), default=[], listify=True, since='0.52.0'), + KwargInfo('version_argument', str, default='', since='1.5.0'), DEFAULT_OPTIONS.evolve(since='1.3.0') ) @disablerIfNotFound @@ -1770,7 +1775,7 @@ def func_find_program(self, node: mparser.BaseNode, args: T.Tuple[T.List[mesonli search_dirs = extract_search_dirs(kwargs) default_options = kwargs['default_options'] return self.find_program_impl(args[0], kwargs['native'], default_options=default_options, required=required, - silent=False, wanted=kwargs['version'], + silent=False, wanted=kwargs['version'], version_arg=kwargs['version_argument'], search_dirs=search_dirs) # When adding kwargs, please check if they make sense in dependencies.get_dep_identifier() diff --git a/mesonbuild/programs.py b/mesonbuild/programs.py index b73f9e4025df..fbe241d99607 100644 --- a/mesonbuild/programs.py +++ b/mesonbuild/programs.py @@ -36,6 +36,7 @@ def __init__(self, name: str, command: T.Optional[T.List[str]] = None, self.name = name self.path: T.Optional[str] = None self.cached_version: T.Optional[str] = None + self.version_arg = '--version' if command is not None: self.command = mesonlib.listify(command) if mesonlib.is_windows(): @@ -93,9 +94,9 @@ def description(self) -> str: def get_version(self, interpreter: T.Optional['Interpreter'] = None) -> str: if not self.cached_version: - raw_cmd = self.get_command() + ['--version'] + raw_cmd = self.get_command() + [self.version_arg] if interpreter: - res = interpreter.run_command_impl((self, ['--version']), + res = interpreter.run_command_impl((self, [self.version_arg]), {'capture': True, 'check': True, 'env': mesonlib.EnvironmentVariables()}, diff --git a/test cases/common/26 find program/meson.build b/test cases/common/26 find program/meson.build index 3c4d15cda35e..a20f6b45a142 100644 --- a/test cases/common/26 find program/meson.build +++ b/test cases/common/26 find program/meson.build @@ -32,6 +32,9 @@ assert(prog.version() == '1.0', 'Program version should be detectable') prog = find_program('print-version-with-prefix.py', version : '>=1.0') assert(prog.found(), 'Program version should match') +prog = find_program('print-version-custom-argument.py', version : '>=1.0', version_argument : '-version') +assert(prog.found(), 'Program version should match') + prog = find_program('test_subdir.py', required : false) assert(not prog.found(), 'Program should not be found') diff --git a/test cases/common/26 find program/print-version-custom-argument.py b/test cases/common/26 find program/print-version-custom-argument.py new file mode 100644 index 000000000000..7d9ad58febb1 --- /dev/null +++ b/test cases/common/26 find program/print-version-custom-argument.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python3 + +import sys + +if len(sys.argv) != 2 or sys.argv[1] != '-version': + exit(1) + +print('1.0') From a28dde40b5b85c97b2eefee63db62ac49e46ca0c Mon Sep 17 00:00:00 2001 From: Kai Pastor Date: Sun, 23 Jun 2024 20:58:36 +0200 Subject: [PATCH 77/91] Recast CMake's IMPORTED_LOCATION into framework flags (#13299) * Explicitly look for 'OpenAL' with method: 'cmake' This test was added for testing cmake depenencies, so no other method must be accepted here, and the spelling must match FindOpenAL.cmake. * Resolve frameworks in IMPORTED_LOCATION The IMPORTED_LOCATION property of CMake targets may contain macOS framework paths. These must be processed into flags. By putting the values in the list of targets, they will be processed as if they appeared in INTERFACE_LINK_LIBRARIES. --- mesonbuild/cmake/tracetargets.py | 4 ++-- test cases/osx/9 framework recasting/meson.build | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mesonbuild/cmake/tracetargets.py b/mesonbuild/cmake/tracetargets.py index aee67ea595ce..5a9d35284e29 100644 --- a/mesonbuild/cmake/tracetargets.py +++ b/mesonbuild/cmake/tracetargets.py @@ -137,9 +137,9 @@ def resolve_cmake_trace_targets(target_name: str, elif 'IMPORTED_IMPLIB' in tgt.properties: res.libraries += [x for x in tgt.properties['IMPORTED_IMPLIB'] if x] elif f'IMPORTED_LOCATION_{cfg}' in tgt.properties: - res.libraries += [x for x in tgt.properties[f'IMPORTED_LOCATION_{cfg}'] if x] + targets += [x for x in tgt.properties[f'IMPORTED_LOCATION_{cfg}'] if x] elif 'IMPORTED_LOCATION' in tgt.properties: - res.libraries += [x for x in tgt.properties['IMPORTED_LOCATION'] if x] + targets += [x for x in tgt.properties['IMPORTED_LOCATION'] if x] if 'LINK_LIBRARIES' in tgt.properties: targets += [x for x in tgt.properties['LINK_LIBRARIES'] if x] diff --git a/test cases/osx/9 framework recasting/meson.build b/test cases/osx/9 framework recasting/meson.build index 2b1584370d52..83fe19ed0a28 100644 --- a/test cases/osx/9 framework recasting/meson.build +++ b/test cases/osx/9 framework recasting/meson.build @@ -1,5 +1,5 @@ project('framework recasting', 'c', 'cpp') -x = dependency('OpenAL') +x = dependency('OpenAL', method: 'cmake') y = executable('tt', files('main.cpp'), dependencies: x) From f5ec07e7c35ac2bf7ff9ee13ccf2818da53baabd Mon Sep 17 00:00:00 2001 From: Scott Kyle Date: Sun, 23 Jun 2024 12:00:29 -0700 Subject: [PATCH 78/91] Ensure private directory exists for custom targets (#13196) * Ensure private directory exists for custom targets Some custom target commands will expect the `@PRIVATE_DIR@` to already exist, such as with `make -C @PRIVATE_DIR@ ...` * Prefer `exist_ok` over catching exception --- mesonbuild/backend/backends.py | 6 +++--- mesonbuild/backend/ninjabackend.py | 7 ++----- .../277 custom target private dir/meson.build | 16 ++++++++++++++++ 3 files changed, 21 insertions(+), 8 deletions(-) create mode 100644 test cases/common/277 custom target private dir/meson.build diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index 055211090d09..cfae28babb37 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -1578,9 +1578,9 @@ def eval_custom_target_command( dfilename = os.path.join(outdir, target.depfile) i = i.replace('@DEPFILE@', dfilename) if '@PRIVATE_DIR@' in i: - if target.absolute_paths: - pdir = self.get_target_private_dir_abs(target) - else: + pdir = self.get_target_private_dir_abs(target) + os.makedirs(pdir, exist_ok=True) + if not target.absolute_paths: pdir = self.get_target_private_dir(target) i = i.replace('@PRIVATE_DIR@', pdir) else: diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 30529676f720..eabe75865d2c 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -864,11 +864,8 @@ def create_target_linker_introspection(self, target: build.Target, linker: T.Uni tgt[lnk_hash] = lnk_block def generate_target(self, target): - try: - if isinstance(target, build.BuildTarget): - os.makedirs(self.get_target_private_dir_abs(target)) - except FileExistsError: - pass + if isinstance(target, build.BuildTarget): + os.makedirs(self.get_target_private_dir_abs(target), exist_ok=True) if isinstance(target, build.CustomTarget): self.generate_custom_target(target) if isinstance(target, build.RunTarget): diff --git a/test cases/common/277 custom target private dir/meson.build b/test cases/common/277 custom target private dir/meson.build new file mode 100644 index 000000000000..dc48d1affd31 --- /dev/null +++ b/test cases/common/277 custom target private dir/meson.build @@ -0,0 +1,16 @@ +project('277 custom target private dir') + +python = find_program('python3') + +custom_target( + 'check-private-dir', + command: [ + python, + '-c', + 'import os, sys; os.chdir(sys.argv[1]); open(sys.argv[2], "w")', + '@PRIVATE_DIR@', + '@OUTPUT@', + ], + output: 'check-private-dir', + build_by_default: true, +) From 03ffb5bd6f2e441227e0a1758b258c2a4d3d58d7 Mon Sep 17 00:00:00 2001 From: Tristan Partin Date: Thu, 25 Apr 2024 14:59:13 -0500 Subject: [PATCH 79/91] Suggest mingw Python URL instead of specific package names Suggested by @lazka in order to be clearer when delineating between mingw-w64-x86_64-python and mingw-w64-ucrt-x86_64-python. Fixes: #12772 --- mesonbuild/mesonmain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mesonbuild/mesonmain.py b/mesonbuild/mesonmain.py index 62ed8918cd2a..c486dd846cb9 100644 --- a/mesonbuild/mesonmain.py +++ b/mesonbuild/mesonmain.py @@ -257,7 +257,7 @@ def run(original_args: T.List[str], mainfile: str) -> int: # https://github.com/mesonbuild/meson/issues/3653 if sys.platform == 'cygwin' and os.environ.get('MSYSTEM', '') not in ['MSYS', '']: mlog.error('This python3 seems to be msys/python on MSYS2 Windows, but you are in a MinGW environment') - mlog.error('Please install and use mingw-w64-x86_64-python3 and/or mingw-w64-x86_64-meson with Pacman') + mlog.error('Please install it via https://packages.msys2.org/base/mingw-w64-python') return 2 args = original_args[:] From eba5498e9b40fdc6fd7d9621a9262dbd8364ea62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Sat, 1 Jun 2024 21:26:49 +0200 Subject: [PATCH 80/91] compilers: cpp: fix header name and return value use in header check There are two issues: 1. has_header() wants just the header name without surrounding <> or similar; it fails otherwise. 2. has_header() returns a tuple of two bools, where the first element determines whether or not the header has been found. So use that element specifically, otherwise the tuple will always evaluate to true because it is not empty. Fixes: 675b47b0692131 ("compilers: cpp: improve libc++ vs libstdc++ detection (again)") --- mesonbuild/compilers/cpp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mesonbuild/compilers/cpp.py b/mesonbuild/compilers/cpp.py index df2f90510730..9467a35c58c1 100644 --- a/mesonbuild/compilers/cpp.py +++ b/mesonbuild/compilers/cpp.py @@ -185,7 +185,7 @@ class _StdCPPLibMixin(CompilerMixinBase): def language_stdlib_provider(self, env: Environment) -> str: # https://stackoverflow.com/a/31658120 - header = 'version' if self.has_header('', '', env) else 'ciso646' + header = 'version' if self.has_header('version', '', env)[0] else 'ciso646' is_libcxx = self.has_header_symbol(header, '_LIBCPP_VERSION', '', env)[0] lib = 'c++' if is_libcxx else 'stdc++' return lib From 8fe8b1d829f057c556143a658d3f26bc66c69ee8 Mon Sep 17 00:00:00 2001 From: Eli Schwartz Date: Fri, 10 May 2024 18:17:53 -0400 Subject: [PATCH 81/91] minstall: fix symlink handling on python 3.13 We passed a wrapper hack for shutil.chown because some functionality wasn't available in the stdlib. It was added in python 3.13 beta1, so the tests fail when we actually test symlink handling. Fixes failure to run test_install_subdir_symlinks_with_default_umask_and_mode on python 3.13. --- mesonbuild/minstall.py | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/mesonbuild/minstall.py b/mesonbuild/minstall.py index a82be5e0ec8f..36284f0affb5 100644 --- a/mesonbuild/minstall.py +++ b/mesonbuild/minstall.py @@ -148,23 +148,29 @@ def set_chown(path: str, user: T.Union[str, int, None] = None, # be actually passed properly. # Not nice, but better than actually rewriting shutil.chown until # this python bug is fixed: https://bugs.python.org/issue18108 - real_os_chown = os.chown - def chown(path: T.Union[int, str, 'os.PathLike[str]', bytes, 'os.PathLike[bytes]'], - uid: int, gid: int, *, dir_fd: T.Optional[int] = dir_fd, - follow_symlinks: bool = follow_symlinks) -> None: - """Override the default behavior of os.chown + if sys.version_info >= (3, 13): + # pylint: disable=unexpected-keyword-arg + # cannot handle sys.version_info, https://github.com/pylint-dev/pylint/issues/9138 + shutil.chown(path, user, group, dir_fd=dir_fd, follow_symlinks=follow_symlinks) + else: + real_os_chown = os.chown - Use a real function rather than a lambda to help mypy out. Also real - functions are faster. - """ - real_os_chown(path, uid, gid, dir_fd=dir_fd, follow_symlinks=follow_symlinks) + def chown(path: T.Union[int, str, 'os.PathLike[str]', bytes, 'os.PathLike[bytes]'], + uid: int, gid: int, *, dir_fd: T.Optional[int] = dir_fd, + follow_symlinks: bool = follow_symlinks) -> None: + """Override the default behavior of os.chown - try: - os.chown = chown - shutil.chown(path, user, group) - finally: - os.chown = real_os_chown + Use a real function rather than a lambda to help mypy out. Also real + functions are faster. + """ + real_os_chown(path, uid, gid, dir_fd=dir_fd, follow_symlinks=follow_symlinks) + + try: + os.chown = chown + shutil.chown(path, user, group) + finally: + os.chown = real_os_chown def set_chmod(path: str, mode: int, dir_fd: T.Optional[int] = None, From 8835ad4e02061bb3ff07293580b0986d2bb2cdb4 Mon Sep 17 00:00:00 2001 From: Eli Schwartz Date: Mon, 13 May 2024 16:27:43 -0400 Subject: [PATCH 82/91] msetup: fix regression under py3.13 causing profile.runctx to not write locals() "PEP 667: Consistent views of namespaces" caused locals() to be inconsistent between uses since it is now created afresh every time you invoke it and writes to it are dropped. `sys._getframe().f_locals` is equivalent but preserves writes (it doesn't create a new dict) and unfortunately doesn't help at all as it's documented to be a private implementation detail of CPython that "should be used for internal and specialized purposes only". Work around this by saving locals to a variable reference and both passing it into runctx and reusing it in lookups of the result. This works okay for both new and older versions of python. Per the documentation for locals(): > The contents of this dictionary should not be modified; changes may > not affect the values of local and free variables used by the > interpreter. So... lesson learned? :) This was introduced in commit c34ee374a77fb2dffff90364506ac0cbbb1f00de; before that, we still used locals() but only to pass local variables *in*. Bug: https://github.com/python/cpython/pull/115153 --- mesonbuild/msetup.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mesonbuild/msetup.py b/mesonbuild/msetup.py index 931b1eba342a..47b40af331c3 100644 --- a/mesonbuild/msetup.py +++ b/mesonbuild/msetup.py @@ -242,10 +242,11 @@ def _generate(self, env: environment.Environment, capture: bool, vslite_ctx: T.O self.finalize_postconf_hooks(b, intr) if self.options.profile: + localvars = locals() fname = f'profile-{intr.backend.name}-backend.log' fname = os.path.join(self.build_dir, 'meson-logs', fname) - profile.runctx('gen_result = intr.backend.generate(capture, vslite_ctx)', globals(), locals(), filename=fname) - captured_compile_args = locals()['gen_result'] + profile.runctx('gen_result = intr.backend.generate(capture, vslite_ctx)', globals(), localvars, filename=fname) + captured_compile_args = localvars['gen_result'] assert captured_compile_args is None or isinstance(captured_compile_args, dict) else: captured_compile_args = intr.backend.generate(capture, vslite_ctx) From 75132a94a19d116ca8637f99a35dc913cd861f2c Mon Sep 17 00:00:00 2001 From: Renan Lavarec <124602499+rlavarec-gpsw@users.noreply.github.com> Date: Fri, 31 May 2024 17:58:23 +0200 Subject: [PATCH 83/91] Vs2010Backend: Fix REGEN.vcxproj not getting default debug config in release --- mesonbuild/backend/vs2010backend.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py index 793b678a5791..b1fc29097d16 100644 --- a/mesonbuild/backend/vs2010backend.py +++ b/mesonbuild/backend/vs2010backend.py @@ -627,6 +627,8 @@ def create_basic_project(self, target_name, *, target_platform = self.platform multi_config_buildtype_list = coredata.get_genvs_default_buildtype_list() if self.gen_lite else [self.buildtype] + if "debug" not in multi_config_buildtype_list: + multi_config_buildtype_list += ["debug"] for buildtype in multi_config_buildtype_list: prjconf = ET.SubElement(confitems, 'ProjectConfiguration', {'Include': buildtype + '|' + target_platform}) From 5d80f7740867b04d44459ecd72431f85882cb77e Mon Sep 17 00:00:00 2001 From: Renan Lavarec <124602499+rlavarec-gpsw@users.noreply.github.com> Date: Thu, 6 Jun 2024 15:46:27 +0200 Subject: [PATCH 84/91] Update mesonbuild/backend/vs2010backend.py Co-authored-by: Charles Brunet --- mesonbuild/backend/vs2010backend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py index b1fc29097d16..496e8ffed404 100644 --- a/mesonbuild/backend/vs2010backend.py +++ b/mesonbuild/backend/vs2010backend.py @@ -628,7 +628,7 @@ def create_basic_project(self, target_name, *, multi_config_buildtype_list = coredata.get_genvs_default_buildtype_list() if self.gen_lite else [self.buildtype] if "debug" not in multi_config_buildtype_list: - multi_config_buildtype_list += ["debug"] + multi_config_buildtype_list.append('debug') for buildtype in multi_config_buildtype_list: prjconf = ET.SubElement(confitems, 'ProjectConfiguration', {'Include': buildtype + '|' + target_platform}) From 4cebb77e1ed264555c898505a79bd252533dfe49 Mon Sep 17 00:00:00 2001 From: Sam James Date: Sun, 23 Jun 2024 22:14:27 +0100 Subject: [PATCH 85/91] compilers: detect: fix typo in comment --- mesonbuild/compilers/detect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mesonbuild/compilers/detect.py b/mesonbuild/compilers/detect.py index d6aeff93a182..4cb8a6f1898c 100644 --- a/mesonbuild/compilers/detect.py +++ b/mesonbuild/compilers/detect.py @@ -179,7 +179,7 @@ def detect_static_linker(env: 'Environment', compiler: Compiler) -> StaticLinker else: trials = default_linkers elif compiler.id == 'intel-cl' and compiler.language == 'c': # why not cpp? Is this a bug? - # Intel has it's own linker that acts like microsoft's lib + # Intel has its own linker that acts like microsoft's lib trials = [['xilib']] elif is_windows() and compiler.id == 'pgi': # this handles cpp / nvidia HPC, in addition to just c/fortran trials = [['ar']] # For PGI on Windows, "ar" is just a wrapper calling link/lib. From b56a3198b4e8e8a6738dc372bddc66225abc20f9 Mon Sep 17 00:00:00 2001 From: Sam James Date: Sun, 23 Jun 2024 22:19:14 +0100 Subject: [PATCH 86/91] compilers: detect: fix comment/error string in _get_gnu_compiler_defines Make the debug & error message strings consistent between the GCC and Clang probes. Copy-paste error. Here, we're scraping pre-processor tokens, not checking for the compiler type. --- mesonbuild/compilers/detect.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mesonbuild/compilers/detect.py b/mesonbuild/compilers/detect.py index 4cb8a6f1898c..f16e40f94bdb 100644 --- a/mesonbuild/compilers/detect.py +++ b/mesonbuild/compilers/detect.py @@ -1331,7 +1331,7 @@ def detect_masm_compiler(env: 'Environment', for_machine: MachineChoice) -> Comp def _get_gnu_compiler_defines(compiler: T.List[str]) -> T.Dict[str, str]: """ - Detect GNU compiler platform type (Apple, MinGW, Unix) + Get the list of GCC pre-processor defines """ # Arguments to output compiler pre-processor defines to stdout # gcc, g++, and gfortran all support these arguments @@ -1339,7 +1339,7 @@ def _get_gnu_compiler_defines(compiler: T.List[str]) -> T.Dict[str, str]: mlog.debug(f'Running command: {join_args(args)}') p, output, error = Popen_safe(args, write='', stdin=subprocess.PIPE) if p.returncode != 0: - raise EnvironmentException('Unable to detect GNU compiler type:\n' + raise EnvironmentException('Unable to detect gcc pre-processor defines:\n' f'Compiler stdout:\n{output}\n-----\n' f'Compiler stderr:\n{error}\n-----\n') # Parse several lines of the type: From 4ad792e158b6059eb847dd0562aff9bd7029981f Mon Sep 17 00:00:00 2001 From: Sam James Date: Mon, 3 Jun 2024 09:43:20 +0100 Subject: [PATCH 87/91] compilers: detect: fix pre-processor scraping by defining language _get_gnu_compiler_defines and _get_clang_compiler_defines were broken by not defining the language they used. Neither GCC nor Clang infer the language based on the driver name which means `self.defines` isn't populated correctly in compilers/cpp.py. e.g. ``` $ echo "" | g++ -E -dM - | grep -i cplus $ echo "" | g++ -x c++ -E -dM - | grep -i cplus #define __cplusplus 201703L ``` Fix that by passing '-cpp -x LANGUAGE' as a first pass. If it fails, try again without '-cpp -x LANGUAGE' as before, as its portability isn't certain. We do '-cpp' because during testing, I found Fortran needs this, although per below, I had to drop Fortran in the end and leave it to the fallback (existing) path. Without this change, a63739d394dd77314270f5a46f79171a8c544e77 is only partially effective. It works if the system has injected Clang options via /etc/clang configuration files, but not by e.g. patching the driver (or for GCC there too). Unfortunately, we have to wimp out for Fortran and fallback to the old method because you need the language standard (e.g. -x f95). --- mesonbuild/compilers/detect.py | 91 +++++++++++++++++++++------- mesonbuild/compilers/mixins/clang.py | 7 +++ 2 files changed, 77 insertions(+), 21 deletions(-) diff --git a/mesonbuild/compilers/detect.py b/mesonbuild/compilers/detect.py index f16e40f94bdb..255812c53cfb 100644 --- a/mesonbuild/compilers/detect.py +++ b/mesonbuild/compilers/detect.py @@ -340,7 +340,7 @@ def sanitize(p: T.Optional[str]) -> T.Optional[str]: guess_gcc_or_lcc = None if guess_gcc_or_lcc: - defines = _get_gnu_compiler_defines(compiler) + defines = _get_gnu_compiler_defines(compiler, lang) if not defines: popen_exceptions[join_args(compiler)] = 'no pre-processor defines' continue @@ -449,7 +449,7 @@ def sanitize(p: T.Optional[str]) -> T.Optional[str]: if 'clang' in out or 'Clang' in out: linker = None - defines = _get_clang_compiler_defines(compiler) + defines = _get_clang_compiler_defines(compiler, lang) # Even if the for_machine is darwin, we could be using vanilla # clang. @@ -676,7 +676,7 @@ def detect_fortran_compiler(env: 'Environment', for_machine: MachineChoice) -> C guess_gcc_or_lcc = 'lcc' if guess_gcc_or_lcc: - defines = _get_gnu_compiler_defines(compiler) + defines = _get_gnu_compiler_defines(compiler, 'fortran') if not defines: popen_exceptions[join_args(compiler)] = 'no pre-processor defines' continue @@ -843,7 +843,7 @@ def _detect_objc_or_objcpp_compiler(env: 'Environment', lang: str, for_machine: continue version = search_version(out) if 'Free Software Foundation' in out: - defines = _get_gnu_compiler_defines(compiler) + defines = _get_gnu_compiler_defines(compiler, lang) if not defines: popen_exceptions[join_args(compiler)] = 'no pre-processor defines' continue @@ -855,7 +855,7 @@ def _detect_objc_or_objcpp_compiler(env: 'Environment', lang: str, for_machine: defines, linker=linker) if 'clang' in out: linker = None - defines = _get_clang_compiler_defines(compiler) + defines = _get_clang_compiler_defines(compiler, lang) if not defines: popen_exceptions[join_args(compiler)] = 'no pre-processor defines' continue @@ -1329,19 +1329,43 @@ def detect_masm_compiler(env: 'Environment', for_machine: MachineChoice) -> Comp # GNU/Clang defines and version # ============================= -def _get_gnu_compiler_defines(compiler: T.List[str]) -> T.Dict[str, str]: +def _get_gnu_compiler_defines(compiler: T.List[str], lang: str) -> T.Dict[str, str]: """ Get the list of GCC pre-processor defines """ + from .mixins.gnu import _LANG_MAP as gnu_LANG_MAP + + def _try_obtain_compiler_defines(args: T.List[str]) -> str: + mlog.debug(f'Running command: {join_args(args)}') + p, output, error = Popen_safe(compiler + args, write='', stdin=subprocess.PIPE) + if p.returncode != 0: + raise EnvironmentException('Unable to get gcc pre-processor defines:\n' + f'Compiler stdout:\n{output}\n-----\n' + f'Compiler stderr:\n{error}\n-----\n') + return output + # Arguments to output compiler pre-processor defines to stdout # gcc, g++, and gfortran all support these arguments - args = compiler + ['-E', '-dM', '-'] - mlog.debug(f'Running command: {join_args(args)}') - p, output, error = Popen_safe(args, write='', stdin=subprocess.PIPE) - if p.returncode != 0: - raise EnvironmentException('Unable to detect gcc pre-processor defines:\n' - f'Compiler stdout:\n{output}\n-----\n' - f'Compiler stderr:\n{error}\n-----\n') + baseline_test_args = ['-E', '-dM', '-'] + try: + # We assume that when _get_gnu_compiler_defines is called, it's + # close enough to a GCCish compiler so we reuse the _LANG_MAP + # from the GCC mixin. This isn't a dangerous assumption because + # we fallback if the detection fails anyway. + + # We might not have a match for Fortran, so fallback to detection + # based on the driver. + lang = gnu_LANG_MAP[lang] + + # The compiler may not infer the target language based on the driver name + # so first, try with '-cpp -x lang', then fallback without given it's less + # portable. We try with '-cpp' as GCC needs it for Fortran at least, and + # it seems to do no harm. + output = _try_obtain_compiler_defines(['-cpp', '-x', lang] + baseline_test_args) + except (EnvironmentException, KeyError): + mlog.debug(f'pre-processor extraction using -cpp -x {lang} failed, falling back w/o lang') + output = _try_obtain_compiler_defines(baseline_test_args) + # Parse several lines of the type: # `#define ___SOME_DEF some_value` # and extract `___SOME_DEF` @@ -1358,17 +1382,42 @@ def _get_gnu_compiler_defines(compiler: T.List[str]) -> T.Dict[str, str]: defines[rest[0]] = rest[1] return defines -def _get_clang_compiler_defines(compiler: T.List[str]) -> T.Dict[str, str]: +def _get_clang_compiler_defines(compiler: T.List[str], lang: str) -> T.Dict[str, str]: """ Get the list of Clang pre-processor defines """ - args = compiler + ['-E', '-dM', '-'] - mlog.debug(f'Running command: {join_args(args)}') - p, output, error = Popen_safe(args, write='', stdin=subprocess.PIPE) - if p.returncode != 0: - raise EnvironmentException('Unable to get clang pre-processor defines:\n' - f'Compiler stdout:\n{output}\n-----\n' - f'Compiler stderr:\n{error}\n-----\n') + from .mixins.clang import _LANG_MAP as clang_LANG_MAP + + def _try_obtain_compiler_defines(args: T.List[str]) -> str: + mlog.debug(f'Running command: {join_args(args)}') + p, output, error = Popen_safe(compiler + args, write='', stdin=subprocess.PIPE) + if p.returncode != 0: + raise EnvironmentException('Unable to get clang pre-processor defines:\n' + f'Compiler stdout:\n{output}\n-----\n' + f'Compiler stderr:\n{error}\n-----\n') + return output + + # Arguments to output compiler pre-processor defines to stdout + baseline_test_args = ['-E', '-dM', '-'] + try: + # We assume that when _get_clang_compiler_defines is called, it's + # close enough to a Clangish compiler so we reuse the _LANG_MAP + # from the Clang mixin. This isn't a dangerous assumption because + # we fallback if the detection fails anyway. + + # We might not have a match for Fortran, so fallback to detection + # based on the driver. + lang = clang_LANG_MAP[lang] + + # The compiler may not infer the target language based on the driver name + # so first, try with '-cpp -x lang', then fallback without given it's less + # portable. We try with '-cpp' as GCC needs it for Fortran at least, and + # it seems to do no harm. + output = _try_obtain_compiler_defines(['-cpp', '-x', lang] + baseline_test_args) + except (EnvironmentException, KeyError): + mlog.debug(f'pre-processor extraction using -cpp -x {lang} failed, falling back w/o lang') + output = _try_obtain_compiler_defines(baseline_test_args) + defines: T.Dict[str, str] = {} for line in output.split('\n'): if not line: diff --git a/mesonbuild/compilers/mixins/clang.py b/mesonbuild/compilers/mixins/clang.py index a9bf56609d8d..de9d8f80dbb4 100644 --- a/mesonbuild/compilers/mixins/clang.py +++ b/mesonbuild/compilers/mixins/clang.py @@ -36,6 +36,13 @@ 's': ['-Oz'], } +_LANG_MAP = { + 'c': 'c', + 'cpp': 'c++', + 'objc': 'objective-c', + 'objcpp': 'objective-c++', +} + class ClangCompiler(GnuLikeCompiler): id = 'clang' From 2a9f40ff7a795dd0c36f8acd61757e32e3c41f48 Mon Sep 17 00:00:00 2001 From: Sam James Date: Mon, 24 Jun 2024 00:32:02 +0100 Subject: [PATCH 88/91] compilers: make lang_map public --- mesonbuild/compilers/detect.py | 8 ++++---- mesonbuild/compilers/mixins/clang.py | 2 +- mesonbuild/compilers/mixins/gnu.py | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/mesonbuild/compilers/detect.py b/mesonbuild/compilers/detect.py index 255812c53cfb..3a678211c5a1 100644 --- a/mesonbuild/compilers/detect.py +++ b/mesonbuild/compilers/detect.py @@ -1333,7 +1333,7 @@ def _get_gnu_compiler_defines(compiler: T.List[str], lang: str) -> T.Dict[str, s """ Get the list of GCC pre-processor defines """ - from .mixins.gnu import _LANG_MAP as gnu_LANG_MAP + from .mixins.gnu import gnu_lang_map def _try_obtain_compiler_defines(args: T.List[str]) -> str: mlog.debug(f'Running command: {join_args(args)}') @@ -1355,7 +1355,7 @@ def _try_obtain_compiler_defines(args: T.List[str]) -> str: # We might not have a match for Fortran, so fallback to detection # based on the driver. - lang = gnu_LANG_MAP[lang] + lang = gnu_lang_map[lang] # The compiler may not infer the target language based on the driver name # so first, try with '-cpp -x lang', then fallback without given it's less @@ -1386,7 +1386,7 @@ def _get_clang_compiler_defines(compiler: T.List[str], lang: str) -> T.Dict[str, """ Get the list of Clang pre-processor defines """ - from .mixins.clang import _LANG_MAP as clang_LANG_MAP + from .mixins.clang import clang_lang_map def _try_obtain_compiler_defines(args: T.List[str]) -> str: mlog.debug(f'Running command: {join_args(args)}') @@ -1407,7 +1407,7 @@ def _try_obtain_compiler_defines(args: T.List[str]) -> str: # We might not have a match for Fortran, so fallback to detection # based on the driver. - lang = clang_LANG_MAP[lang] + lang = clang_lang_map[lang] # The compiler may not infer the target language based on the driver name # so first, try with '-cpp -x lang', then fallback without given it's less diff --git a/mesonbuild/compilers/mixins/clang.py b/mesonbuild/compilers/mixins/clang.py index de9d8f80dbb4..e9e83f28615f 100644 --- a/mesonbuild/compilers/mixins/clang.py +++ b/mesonbuild/compilers/mixins/clang.py @@ -36,7 +36,7 @@ 's': ['-Oz'], } -_LANG_MAP = { +clang_lang_map = { 'c': 'c', 'cpp': 'c++', 'objc': 'objective-c', diff --git a/mesonbuild/compilers/mixins/gnu.py b/mesonbuild/compilers/mixins/gnu.py index 79f271607434..587b0cc6d885 100644 --- a/mesonbuild/compilers/mixins/gnu.py +++ b/mesonbuild/compilers/mixins/gnu.py @@ -309,7 +309,7 @@ ], } -_LANG_MAP = { +gnu_lang_map = { 'c': 'c', 'cpp': 'c++', 'objc': 'objective-c', @@ -318,9 +318,9 @@ @functools.lru_cache(maxsize=None) def gnulike_default_include_dirs(compiler: T.Tuple[str, ...], lang: str) -> 'ImmutableListProtocol[str]': - if lang not in _LANG_MAP: + if lang not in gnu_lang_map: return [] - lang = _LANG_MAP[lang] + lang = gnu_lang_map[lang] env = os.environ.copy() env["LC_ALL"] = 'C' cmd = list(compiler) + [f'-x{lang}', '-E', '-v', '-'] @@ -534,7 +534,7 @@ def get_preprocess_to_file_args(self) -> T.List[str]: # We want to allow preprocessing files with any extension, such as # foo.c.in. In that case we need to tell GCC/CLANG to treat them as # assembly file. - lang = _LANG_MAP.get(self.language, 'assembler-with-cpp') + lang = gnu_lang_map.get(self.language, 'assembler-with-cpp') return self.get_preprocess_only_args() + [f'-x{lang}'] From 1570289acf28272aa6f1e029a32229ad6f276d94 Mon Sep 17 00:00:00 2001 From: Nils Werner Date: Wed, 5 Jun 2024 10:49:25 +0200 Subject: [PATCH 89/91] Test case for environment prepend/append acting like set --- test cases/common/41 test args/meson.build | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test cases/common/41 test args/meson.build b/test cases/common/41 test args/meson.build index b21f1ad00b12..4894f3e163a8 100644 --- a/test cases/common/41 test args/meson.build +++ b/test cases/common/41 test args/meson.build @@ -33,3 +33,15 @@ testfilect = custom_target('testfile', build_by_default : true, command : [copy, '@INPUT@', '@OUTPUT@']) test('custom target arg', tester, args : testfilect, env : env_array) + +# https://github.com/mesonbuild/meson/issues/12327 +env = environment() +env.append('PATH', 'something') + +bash = find_program('bash') + +custompathtgt = custom_target('testpathappend', + output : 'nothing.txt', + build_always : true, + command : [bash, '-c', 'env'], + env : env) From aab2533ab4f7f4c16991620b400d71782f89be1c Mon Sep 17 00:00:00 2001 From: Eli Schwartz Date: Sun, 23 Jun 2024 16:41:21 -0400 Subject: [PATCH 90/91] limit wrapped-due-to-env special case for `env` to only apply for env.set In commit 2cb7350d1679fb61826bf4aebfb0f75a9b9103e3 we added a special case for environment() objects to allow skipping `meson --internal exe` overhead when /usr/bin/env exists and can be used to set environment variables instead of using a pickled wrapper. This special case assumed that environment is used for setting variables, but it is also possible, albeit less common, to append/prepend to them, in which case `meson --internal exe` will compute the final value as an offset from whatever the current environment variables inherited from ninja are. It is not possible to precompute this when generating command lines for ninja, so using arguments to /usr/bin/env is not possible. All it can do is set (override) an environment variable. In this case, we have to use the python wrapper and cannot optimize it away. Add a tracking bit to the env object and propagate it to the backend. --- mesonbuild/backend/backends.py | 2 +- mesonbuild/utils/core.py | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index cfae28babb37..b664300f4006 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -627,7 +627,7 @@ def as_meson_exe_cmdline(self, exe: T.Union[str, mesonlib.File, build.BuildTarge # It's also overridden for a few conditions that can't be handled # inside a command line - can_use_env = not force_serialize + can_use_env = env.can_use_env and not force_serialize force_serialize = force_serialize or bool(reasons) if capture: diff --git a/mesonbuild/utils/core.py b/mesonbuild/utils/core.py index 92f9d2c70d40..a87f77acc14f 100644 --- a/mesonbuild/utils/core.py +++ b/mesonbuild/utils/core.py @@ -64,6 +64,7 @@ def __init__(self, values: T.Optional[EnvInitValueType] = None, # The set of all env vars we have operations for. Only used for self.has_name() self.varnames: T.Set[str] = set() self.unset_vars: T.Set[str] = set() + self.can_use_env = True if values: init_func = getattr(self, init_method) @@ -95,7 +96,9 @@ def merge(self, other: EnvironmentVariables) -> None: self.envvars.append((method, name, values, separator)) if name in self.unset_vars: self.unset_vars.remove(name) - self.unset_vars.update(other.unset_vars) + if other.unset_vars: + self.can_use_env = False + self.unset_vars.update(other.unset_vars) def set(self, name: str, values: T.List[str], separator: str = os.pathsep) -> None: if name in self.unset_vars: @@ -104,17 +107,20 @@ def set(self, name: str, values: T.List[str], separator: str = os.pathsep) -> No self.envvars.append((self._set, name, values, separator)) def unset(self, name: str) -> None: + self.can_use_env = False if name in self.varnames: raise MesonException(f'You cannot unset the {name!r} variable because it is already set') self.unset_vars.add(name) def append(self, name: str, values: T.List[str], separator: str = os.pathsep) -> None: + self.can_use_env = False if name in self.unset_vars: raise MesonException(f'You cannot append to unset variable {name!r}') self.varnames.add(name) self.envvars.append((self._append, name, values, separator)) def prepend(self, name: str, values: T.List[str], separator: str = os.pathsep) -> None: + self.can_use_env = False if name in self.unset_vars: raise MesonException(f'You cannot prepend to unset variable {name!r}') self.varnames.add(name) From 7ec1fc507ea6f80e819ac46b56125b955073de7f Mon Sep 17 00:00:00 2001 From: Jussi Pakkanen Date: Mon, 24 Jun 2024 19:32:34 +0300 Subject: [PATCH 91/91] Bump version numbers for rc1. --- man/meson.1 | 2 +- mesonbuild/coredata.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/man/meson.1 b/man/meson.1 index 10a5eb9a443e..182ac17fce19 100644 --- a/man/meson.1 +++ b/man/meson.1 @@ -1,4 +1,4 @@ -.TH MESON "1" "March 2024" "meson 1.4.0" "User Commands" +.TH MESON "1" "June 2024" "meson 1.5.0" "User Commands" .SH NAME meson - a high productivity build system .SH DESCRIPTION diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index 36191d93b98c..7fb3bca023d8 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -72,7 +72,7 @@ class SharedCMDOptions(Protocol): # # Pip requires that RCs are named like this: '0.1.0.rc1' # But the corresponding Git tag needs to be '0.1.0rc1' -version = '1.4.99' +version = '1.5.0.rc1' # The next stable version when we are in dev. This is used to allow projects to # require meson version >=1.2.0 when using 1.1.99. FeatureNew won't warn when