diff options
Diffstat (limited to 'mesonbuild/coredata.py')
-rw-r--r-- | mesonbuild/coredata.py | 491 |
1 files changed, 135 insertions, 356 deletions
diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index 36191d9..9a4139b 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -1,6 +1,6 @@ # SPDX-License-Identifier: Apache-2.0 -# Copyright 2013-2024 The Meson development team -# Copyright © 2023-2024 Intel Corporation +# Copyright 2013-2025 The Meson development team +# Copyright © 2023-2025 Intel Corporation from __future__ import annotations @@ -9,39 +9,40 @@ import copy from . import mlog, options import pickle, os, uuid import sys +from functools import lru_cache from itertools import chain -from pathlib import PurePath -from collections import OrderedDict, abc -from dataclasses import dataclass +from collections import OrderedDict +import textwrap from .mesonlib import ( - MesonBugException, MesonException, MachineChoice, PerMachine, PerMachineDefaultable, - OptionKey, OptionType, stringlistify, + default_prefix, + stringlistify, pickle_load ) +from .options import OptionKey + from .machinefile import CmdLineFileParser import ast -import argparse import enum import shlex import typing as T if T.TYPE_CHECKING: + import argparse from typing_extensions import Protocol - from typing import Any from . import dependencies from .compilers.compilers import Compiler, CompileResult, RunResult, CompileCheckMode from .dependencies.detect import TV_DepID - from .environment import Environment from .mesonlib import FileOrString from .cmake.traceparser import CMakeCacheEntry from .interpreterbase import SubProject - from .options import UserOption + from .options import ElementaryOptionValues, MutableKeyedOptionDictType + from .build import BuildTarget class SharedCMDOptions(Protocol): @@ -58,9 +59,7 @@ if T.TYPE_CHECKING: cross_file: T.List[str] native_file: T.List[str] - OptionDictType = T.Union[T.Dict[str, 'options.UserOption[T.Any]'], 'OptionsView'] - MutableKeyedOptionDictType = T.Dict['OptionKey', 'options.UserOption[T.Any]'] - KeyedOptionDictType = T.Union['options.OptionStore', 'OptionsView'] + OptionDictType = T.Dict[str, options.AnyOptionType] CompilerCheckCacheKey = T.Tuple[T.Tuple[str, ...], str, FileOrString, T.Tuple[str, ...], CompileCheckMode] # code, args RunCheckCacheKey = T.Tuple[str, T.Tuple[str, ...]] @@ -72,7 +71,7 @@ if T.TYPE_CHECKING: # # 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.8.99' # 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 @@ -144,16 +143,16 @@ class DependencyCache: successfully lookup by providing a simple get/put interface. """ - def __init__(self, builtins: 'KeyedOptionDictType', for_machine: MachineChoice): + def __init__(self, builtins: options.OptionStore, for_machine: MachineChoice): self.__cache: T.MutableMapping[TV_DepID, DependencySubCache] = OrderedDict() self.__builtins = builtins - self.__pkg_conf_key = OptionKey('pkg_config_path', machine=for_machine) - self.__cmake_key = OptionKey('cmake_prefix_path', machine=for_machine) + self.__pkg_conf_key = options.OptionKey('pkg_config_path') + self.__cmake_key = options.OptionKey('cmake_prefix_path') def __calculate_subkey(self, type_: DependencyCacheType) -> T.Tuple[str, ...]: data: T.Dict[DependencyCacheType, T.List[str]] = { - DependencyCacheType.PKG_CONFIG: stringlistify(self.__builtins.get_value(self.__pkg_conf_key)), - DependencyCacheType.CMAKE: stringlistify(self.__builtins.get_value(self.__cmake_key)), + DependencyCacheType.PKG_CONFIG: stringlistify(self.__builtins.get_value_for(self.__pkg_conf_key)), + DependencyCacheType.CMAKE: stringlistify(self.__builtins.get_value_for(self.__cmake_key)), DependencyCacheType.OTHER: [], } assert type_ in data, 'Someone forgot to update subkey calculations for a new type' @@ -248,6 +247,7 @@ class CoreData: 'default': '8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942', 'c': '8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942', 'cpp': '8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942', + 'masm': '8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942', 'test': '3AC096D0-A1C2-E12C-1390-A8335801FDAB', 'directory': '2150E333-8FDC-42A3-9474-1A3956D46DE8', } @@ -257,9 +257,9 @@ class CoreData: self.meson_command = meson_command self.target_guids = {} self.version = version - 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()) + self.optstore = options.OptionStore(self.is_cross_build()) # Stores the (name, hash) of the options file, The name will be either # "meson_options.txt" or "meson.options". @@ -286,7 +286,7 @@ class CoreData: # Only to print a warning if it changes between Meson invocations. self.config_files = self.__load_config_files(cmd_options, scratch_dir, 'native') self.builtin_options_libdir_cross_fixup() - self.init_builtins('') + self.init_builtins() @staticmethod def __load_config_files(cmd_options: SharedCMDOptions, scratch_dir: str, ftype: str) -> T.List[str]: @@ -315,15 +315,15 @@ class CoreData: # in this case we've been passed some kind of pipe, copy # the contents of that file into the meson private (scratch) # directory so that it can be re-read when wiping/reconfiguring - copy = os.path.join(scratch_dir, f'{uuid.uuid4()}.{ftype}.ini') + fcopy = os.path.join(scratch_dir, f'{uuid.uuid4()}.{ftype}.ini') with open(f, encoding='utf-8') as rf: - with open(copy, 'w', encoding='utf-8') as wf: + with open(fcopy, 'w', encoding='utf-8') as wf: wf.write(rf.read()) - real.append(copy) + real.append(fcopy) # Also replace the command line argument, as the pipe # probably won't exist on reconfigure - filenames[i] = copy + filenames[i] = fcopy continue if sys.platform != 'win32': paths = [ @@ -353,74 +353,33 @@ class CoreData: if self.cross_files: options.BUILTIN_OPTIONS[OptionKey('libdir')].default = 'lib' - def sanitize_prefix(self, prefix: str) -> str: - prefix = os.path.expanduser(prefix) - if not os.path.isabs(prefix): - raise MesonException(f'prefix value {prefix!r} must be an absolute path') - if prefix.endswith('/') or prefix.endswith('\\'): - # On Windows we need to preserve the trailing slash if the - # string is of type 'C:\' because 'C:' is not an absolute path. - if len(prefix) == 3 and prefix[1] == ':': - pass - # If prefix is a single character, preserve it since it is - # the root directory. - elif len(prefix) == 1: - pass - else: - prefix = prefix[:-1] - return prefix - - def sanitize_dir_option_value(self, prefix: str, option: OptionKey, value: T.Any) -> T.Any: - ''' - If the option is an installation directory option, the value is an - absolute path and resides within prefix, return the value - as a path relative to the prefix. Otherwise, return it as is. - - This way everyone can do f.ex, get_option('libdir') and usually get - the library directory relative to prefix, even though it really - should not be relied upon. - ''' - try: - value = PurePath(value) - except TypeError: - return value - if option.name.endswith('dir') and value.is_absolute() and \ - option not in options.BUILTIN_DIR_NOPREFIX_OPTIONS: - try: - # Try to relativize the path. - value = value.relative_to(prefix) - except ValueError: - # Path is not relative, let’s keep it as is. - pass - if '..' in value.parts: - raise MesonException( - f'The value of the \'{option}\' option is \'{value}\' but ' - 'directory options are not allowed to contain \'..\'.\n' - f'If you need a path outside of the {prefix!r} prefix, ' - 'please use an absolute path.' - ) - # .as_posix() keeps the posix-like file separators Meson uses. - return value.as_posix() - - def init_builtins(self, subproject: str) -> None: + def init_builtins(self) -> None: # Create builtin options with default values for key, opt in options.BUILTIN_OPTIONS.items(): - self.add_builtin_option(self.optstore, key.evolve(subproject=subproject), opt) + self.add_builtin_option(self.optstore, key, opt) for for_machine in iter(MachineChoice): for key, opt in options.BUILTIN_OPTIONS_PER_MACHINE.items(): - self.add_builtin_option(self.optstore, key.evolve(subproject=subproject, machine=for_machine), opt) + self.add_builtin_option(self.optstore, key.evolve(machine=for_machine), opt) @staticmethod - def add_builtin_option(opts_map: 'MutableKeyedOptionDictType', key: OptionKey, - opt: 'options.BuiltinOption') -> None: + def add_builtin_option(optstore: options.OptionStore, key: OptionKey, + opt: options.AnyOptionType) -> None: + # Create a copy of the object, as we're going to mutate it + opt = copy.copy(opt) if key.subproject: if opt.yielding: # This option is global and not per-subproject return - value = opts_map.get_value(key.as_root()) else: - value = None - opts_map.add_system_option(key, opt.init_option(key, value, options.default_prefix())) + new_value = options.argparse_prefixed_default( + opt, key, default_prefix()) + opt.set_value(new_value) + + modulename = key.get_module_prefix() + if modulename: + optstore.add_module_option(modulename, key, opt) + else: + optstore.add_system_option(key, opt) def init_backend_options(self, backend_name: str) -> None: if backend_name == 'ninja': @@ -428,75 +387,44 @@ class CoreData: 'backend_max_links', 'Maximum number of linker processes to run or 0 for no ' 'limit', - (0, None, 0))) + 0, + min_value=0)) elif backend_name.startswith('vs'): 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.get_value(key) - return v - except KeyError: - pass - - try: - v = self.optstore.get_value_object(key.as_root()) - if v.yielding: - return v.value - except KeyError: - pass - - raise MesonException(f'Tried to get unknown builtin option {str(key)}') + def get_option_for_target(self, target: 'BuildTarget', key: T.Union[str, OptionKey]) -> ElementaryOptionValues: + if isinstance(key, str): + assert ':' not in key + newkey = OptionKey(key, target.subproject) + else: + newkey = key + if newkey.subproject != target.subproject: + # FIXME: this should be an error. The caller needs to ensure that + # key and target have the same subproject for consistency. + # Now just do this to get things going. + newkey = newkey.evolve(subproject=target.subproject) + (option_object, value) = self.optstore.get_value_object_and_value_for(newkey) + override = target.get_override(newkey.name) + if override is not None: + return option_object.validate_value(override) + return value + + def set_from_configure_command(self, options: SharedCMDOptions) -> bool: + unset_opts = getattr(options, 'unset_opts', []) + all_D = options.projectoptions[:] + for key, valstr in options.cmd_line_options.items(): + all_D.append(f'{key!s}={valstr}') + return self.optstore.set_from_configure_command(all_D, unset_opts) def set_option(self, key: OptionKey, value, first_invocation: bool = False) -> bool: dirty = False - if key.is_builtin(): - if key.name == 'prefix': - value = self.sanitize_prefix(value) - else: - prefix = self.optstore.get_value('prefix') - value = self.sanitize_dir_option_value(prefix, key, value) - try: - opt = self.optstore.get_value_object(key) + changed = self.optstore.set_option(key, value, first_invocation) except KeyError: raise MesonException(f'Tried to set unknown builtin option {str(key)}') - - if opt.deprecated is True: - mlog.deprecation(f'Option {key.name!r} is deprecated') - elif isinstance(opt.deprecated, list): - for v in opt.listify(value): - if v in opt.deprecated: - mlog.deprecation(f'Option {key.name!r} value {v!r} is deprecated') - elif isinstance(opt.deprecated, dict): - def replace(v): - newvalue = opt.deprecated.get(v) - if newvalue is not None: - mlog.deprecation(f'Option {key.name!r} value {v!r} is replaced by {newvalue!r}') - return newvalue - return v - newvalue = [replace(v) for v in opt.listify(value)] - value = ','.join(newvalue) - elif isinstance(opt.deprecated, str): - # Option is deprecated and replaced by another. Note that a project - # option could be replaced by a built-in or module option, which is - # why we use OptionKey.from_string(newname) instead of - # key.evolve(newname). We set the value on both the old and new names, - # assuming they accept the same value. That could for example be - # achieved by adding the values from old option as deprecated on the - # new option, for example in the case of boolean option is replaced - # by a feature option with a different name. - newname = opt.deprecated - newkey = OptionKey.from_string(newname).evolve(subproject=key.subproject) - mlog.deprecation(f'Option {key.name!r} is replaced by {newname!r}') - dirty |= self.set_option(newkey, value, first_invocation) - - changed = opt.set_value(value) - if changed and opt.readonly and not first_invocation: - raise MesonException(f'Tried modify read only option {str(key)!r}') dirty |= changed if key.name == 'buildtype': @@ -512,7 +440,7 @@ class CoreData: 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.get_value('buildtype') + value = self.optstore.get_value_for('buildtype') if value == 'plain': opt = 'plain' debug = False @@ -531,8 +459,8 @@ class CoreData: else: assert value == 'custom' return [] - actual_opt = self.optstore.get_value('optimization') - actual_debug = self.optstore.get_value('debug') + actual_opt = self.optstore.get_value_for('optimization') + actual_debug = self.optstore.get_value_for('debug') if actual_opt != opt: result.append(('optimization', actual_opt, opt)) if actual_debug != debug: @@ -561,67 +489,36 @@ class CoreData: assert value == 'custom' return False - dirty |= self.optstore.set_value('optimization', opt) - dirty |= self.optstore.set_value('debug', debug) + dirty |= self.optstore.set_option(OptionKey('optimization'), opt) + dirty |= self.optstore.set_option(OptionKey('debug'), debug) return dirty - @staticmethod - def is_per_machine_option(optname: OptionKey) -> bool: - if optname.as_host() in options.BUILTIN_OPTIONS_PER_MACHINE: - return True - return optname.lang is not None - def get_external_args(self, for_machine: MachineChoice, lang: str) -> T.List[str]: # mypy cannot analyze type of OptionKey - key = OptionKey('args', machine=for_machine, lang=lang) + key = OptionKey(f'{lang}_args', machine=for_machine) return T.cast('T.List[str]', self.optstore.get_value(key)) + @lru_cache(maxsize=None) def get_external_link_args(self, for_machine: MachineChoice, lang: str) -> T.List[str]: # mypy cannot analyze type of OptionKey - 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, 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.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.get_value_object(key) - if type(oldval) is not type(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.set_value_object(key, value) - try: - value.set_value(oldval.value) - except MesonException: - mlog.warning(f'Old value(s) of {key} are no longer valid, resetting to default ({value.value}).', - fatal=False) - - # Find any extranious keys for this project and remove them - for key in self.optstore.keys() - project_options.keys(): - if key.is_project() and key.subproject == subproject: - self.optstore.remove(key) + linkkey = OptionKey(f'{lang}_link_args', machine=for_machine) + return T.cast('T.List[str]', self.optstore.get_value_for(linkkey)) def is_cross_build(self, when_building_for: MachineChoice = MachineChoice.HOST) -> bool: if when_building_for == MachineChoice.BUILD: return False return len(self.cross_files) > 0 - def copy_build_options_from_regular_ones(self) -> bool: + def copy_build_options_from_regular_ones(self, shut_up_pylint: bool = True) -> bool: + # FIXME, needs cross compilation support. + if shut_up_pylint: + return False dirty = False assert not self.is_cross_build() for k in options.BUILTIN_OPTIONS_PER_MACHINE: - o = self.optstore.get_value_object(k) - dirty |= self.optstore.set_value(k.as_build(), o.value) + o = self.optstore.get_value_object_for(k.name) + dirty |= self.optstore.set_option(k, o.value, True) for bk, bv in self.optstore.items(): if bk.machine is MachineChoice.BUILD: hk = bk.as_host() @@ -640,131 +537,72 @@ class CoreData: # Set prefix first because it's needed to sanitize other options pfk = OptionKey('prefix') if pfk in opts_to_set: - prefix = self.sanitize_prefix(opts_to_set[pfk]) - dirty |= self.optstore.set_value('prefix', prefix) + prefix = self.optstore.sanitize_prefix(opts_to_set[pfk]) for key in options.BUILTIN_DIR_NOPREFIX_OPTIONS: if key not in opts_to_set: - dirty |= self.optstore.set_value(key, options.BUILTIN_OPTIONS[key].prefixed_default(key, prefix)) + val = options.BUILTIN_OPTIONS[key].prefixed_default(key, prefix) + dirty |= self.optstore.set_option(key, val) unknown_options: T.List[OptionKey] = [] for k, v in opts_to_set.items(): if k == pfk: continue - elif k in self.optstore: + elif k.evolve(subproject=None) in self.optstore: dirty |= self.set_option(k, v, first_invocation) - elif k.machine != MachineChoice.BUILD and k.type != OptionType.COMPILER: + elif k.machine != MachineChoice.BUILD and not self.optstore.is_compiler_option(k): unknown_options.append(k) if unknown_options: - unknown_options_str = ', '.join(sorted(str(s) for s in unknown_options)) - sub = f'In subproject {subproject}: ' if subproject else '' - raise MesonException(f'{sub}Unknown options: "{unknown_options_str}"') + if subproject: + # The subproject may have top-level options that should be used + # when it is not a subproject. Ignore those for now. With option + # refactor they will get per-subproject values. + really_unknown = [] + for uo in unknown_options: + topkey = uo.as_root() + if topkey not in self.optstore: + really_unknown.append(uo) + unknown_options = really_unknown + if unknown_options: + unknown_options_str = ', '.join(sorted(str(s) for s in unknown_options)) + sub = f'In subproject {subproject}: ' if subproject else '' + raise MesonException(f'{sub}Unknown options: "{unknown_options_str}"') if not self.is_cross_build(): dirty |= self.copy_build_options_from_regular_ones() return dirty - def set_default_options(self, default_options: T.MutableMapping[OptionKey, str], subproject: str, env: 'Environment') -> None: - from .compilers import base_options - - # Main project can set default options on subprojects, but subprojects - # can only set default options on themselves. - # Preserve order: if env.options has 'buildtype' it must come after - # 'optimization' if it is in default_options. - options: T.MutableMapping[OptionKey, T.Any] = OrderedDict() - for k, v in default_options.items(): - if not subproject or k.subproject == subproject: - options[k] = v - options.update(env.options) - env.options = options - - # Create a subset of options, keeping only project and builtin - # options for this subproject. - # Language and backend specific options will be set later when adding - # languages and setting the backend (builtin options must be set first - # to know which backend we'll use). - options = OrderedDict() - - for k, v in env.options.items(): - # If this is a subproject, don't use other subproject options - if k.subproject and k.subproject != subproject: - continue - # If the option is a builtin and is yielding then it's not allowed per subproject. - # - # 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.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. - if k.type in {OptionType.COMPILER, OptionType.BACKEND}: - continue - if k.type == OptionType.BASE and k.as_root() in base_options: - # set_options will report unknown base options - continue - options[k] = v - - self.set_options(options, subproject=subproject, first_invocation=env.first_invocation) - - def add_compiler_options(self, c_options: MutableKeyedOptionDictType, lang: str, for_machine: MachineChoice, - env: Environment, subproject: str) -> None: + def add_compiler_options(self, c_options: MutableKeyedOptionDictType, lang: str, for_machine: MachineChoice) -> None: 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.set_value_object(k, o) # override compiler option on reconfigure - self.optstore.setdefault(k, o) + assert k.subproject is None and k.machine is for_machine + if lang == 'objc' and k.name == 'c_std': + # For objective C, always fall back to c_std. + self.optstore.add_compiler_option('c', k, o) + elif lang == 'objcpp' and k.name == 'cpp_std': + self.optstore.add_compiler_option('cpp', k, o) + else: + self.optstore.add_compiler_option(lang, k, o) + + def process_compiler_options(self, lang: str, comp: Compiler, subproject: str) -> None: + self.add_compiler_options(comp.get_options(), lang, comp.for_machine) - if subproject: - sk = k.evolve(subproject=subproject) - value = env.options.get(sk) or value - if value is not None: - o.set_value(value) - 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'], - for_machine: MachineChoice, env: 'Environment') -> None: - """Add global language arguments that are needed before compiler/linker detection.""" - 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.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 - - self.add_compiler_options(comp.get_options(), lang, comp.for_machine, env, subproject) - - enabled_opts: T.List[OptionKey] = [] for key in comp.base_options: if subproject: skey = key.evolve(subproject=subproject) else: skey = key if skey not in self.optstore: - self.optstore.add_system_option(skey, copy.deepcopy(compilers.base_options[key])) - if skey in env.options: - self.optstore.set_value(skey, env.options[skey]) - enabled_opts.append(skey) - elif subproject and key in env.options: - self.optstore.set_value(skey, env.options[key]) - enabled_opts.append(skey) - if subproject and key not in self.optstore: - self.optstore.add_system_option(key, copy.deepcopy(self.optstore.get_value_object(skey))) - elif skey in env.options: - self.optstore.set_value(skey, env.options[skey]) - elif subproject and key in env.options: - 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: - if OptionKey('b_bitcode') in enabled_opts: - 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) + self.optstore.add_system_option(skey, copy.deepcopy(options.COMPILER_BASE_OPTIONS[key])) + + self.emit_base_options_warnings() + + def emit_base_options_warnings(self) -> None: + bcodekey = OptionKey('b_bitcode') + if bcodekey in self.optstore and self.optstore.get_value(bcodekey): + msg = textwrap.dedent('''Base option 'b_bitcode' is enabled, which is incompatible with many linker options. + Incompatible options such as \'b_asneeded\' have been disabled.' + Please see https://mesonbuild.com/Builtin-options.html#Notes_about_Apple_Bitcode_support for more details.''') + mlog.warning(msg, once=True, fatal=False) def get_cmd_line_file(build_dir: str) -> str: return os.path.join(build_dir, 'meson-private', 'cmd_line.txt') @@ -852,28 +690,22 @@ def save(obj: CoreData, build_dir: str) -> str: def register_builtin_arguments(parser: argparse.ArgumentParser) -> None: for n, b in options.BUILTIN_OPTIONS.items(): - b.add_to_argparse(str(n), parser, '') + options.option_to_argparse(b, n, parser, '') 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)') + options.option_to_argparse(b, n, parser, ' (just for host machine)') + options.option_to_argparse(b, n.as_build(), parser, ' (just for build machine)') parser.add_argument('-D', action='append', dest='projectoptions', default=[], metavar="option", help='Set the value of an option, can be used several times to set multiple options.') -def create_options_dict(options: T.List[str], subproject: str = '') -> T.Dict[OptionKey, str]: - result: T.OrderedDict[OptionKey, str] = OrderedDict() - for o in options: +def parse_cmd_line_options(args: SharedCMDOptions) -> None: + args.cmd_line_options = {} + for o in args.projectoptions: try: - (key, value) = o.split('=', 1) + keystr, value = o.split('=', 1) except ValueError: raise MesonException(f'Option {o!r} must have a value separated by equals sign.') - k = OptionKey.from_string(key) - if subproject: - k = k.evolve(subproject=subproject) - result[k] = value - return result - -def parse_cmd_line_options(args: SharedCMDOptions) -> None: - args.cmd_line_options = create_options_dict(args.projectoptions) + key = OptionKey.from_string(keystr) + args.cmd_line_options[key] = value # Merge builtin options set with --option into the dict. for key in chain( @@ -885,65 +717,12 @@ def parse_cmd_line_options(args: SharedCMDOptions) -> None: value = getattr(args, name, None) if value is not None: if key in args.cmd_line_options: - cmdline_name = options.BuiltinOption.argparse_name_to_arg(name) + cmdline_name = options.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. - ''' - - # TODO: the typing here could be made more explicit using a TypeDict from - # python 3.8 or typing_extensions - 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 - - 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: - 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: - 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 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) - - def __len__(self) -> int: - return len(self.original_options) FORBIDDEN_TARGET_NAMES = frozenset({ 'clean', |