From 72b0f108111b777c92fbab0845d2b48c38615358 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Tue, 4 Mar 2025 10:04:55 -0800 Subject: [PATCH] options: merge set_value and set_option Which are basically the same, except for handling of deprecated options, and various bugs that only existed in one implementation or the other. --- mesonbuild/coredata.py | 18 ++++---- mesonbuild/options.py | 70 ++++++++++-------------------- unittests/platformagnostictests.py | 4 +- 3 files changed, 35 insertions(+), 57 deletions(-) diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index a75a6a2a2a69..457e3500601e 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -416,7 +416,7 @@ def get_option_for_target(self, target: 'BuildTarget', key: T.Union[str, OptionK def set_option(self, key: OptionKey, value, first_invocation: bool = False) -> bool: dirty = False try: - changed = self.optstore.set_value(key, value, first_invocation) + changed = self.optstore.set_option(key, value, first_invocation) except KeyError: raise MesonException(f'Tried to set unknown builtin option {str(key)}') dirty |= changed @@ -483,8 +483,8 @@ def _set_others_from_buildtype(self, value: str) -> bool: 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 @@ -515,7 +515,7 @@ def update_project_options(self, project_options: 'MutableKeyedOptionDictType', oldval = self.optstore.get_value_object(key) if type(oldval) is not type(value): - self.optstore.set_value(key, value.value) + self.optstore.set_option(key, value.value) elif options.choices_are_different(oldval, value): # If the choices have changed, use the new value, but attempt # to keep the old options. If they are not valid keep the new @@ -546,7 +546,7 @@ def copy_build_options_from_regular_ones(self, shut_up_pylint: bool = True) -> b assert not self.is_cross_build() for k in options.BUILTIN_OPTIONS_PER_MACHINE: o = self.optstore.get_value_object_for(k.name) - dirty |= self.optstore.set_value(k, o.value, True) + 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() @@ -690,16 +690,16 @@ def process_compiler_options(self, lang: str, comp: Compiler, env: Environment, 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]) + self.optstore.set_option(skey, env.options[skey]) elif subproject and key in env.options: - self.optstore.set_value(skey, env.options[key]) + self.optstore.set_option(skey, env.options[key]) # FIXME #if subproject and not self.optstore.has_option(key): # self.optstore[key] = copy.deepcopy(self.optstore[skey]) elif skey in env.options: - self.optstore.set_value(skey, env.options[skey]) + self.optstore.set_option(skey, env.options[skey]) elif subproject and key in env.options: - self.optstore.set_value(skey, env.options[key]) + self.optstore.set_option(skey, env.options[key]) self.emit_base_options_warnings() def emit_base_options_warnings(self) -> None: diff --git a/mesonbuild/options.py b/mesonbuild/options.py index 3a3e5e9b7f99..5f35197686b7 100644 --- a/mesonbuild/options.py +++ b/mesonbuild/options.py @@ -941,53 +941,29 @@ def sanitize_dir_option_value(self, prefix: str, option: OptionKey, value: T.Any # .as_posix() keeps the posix-like file separators Meson uses. return value.as_posix() - def set_value(self, key: T.Union[OptionKey, str], new_value: 'T.Any', first_invocation: bool = False) -> bool: - key = self.ensure_and_validate_key(key) - if key.name == 'prefix': - new_value = self.sanitize_prefix(new_value) - elif self.is_builtin_option(key): - prefix = self.get_value_for('prefix') - assert isinstance(prefix, str) - new_value = self.sanitize_dir_option_value(prefix, key, new_value) - if key not in self.options: - raise MesonException(f'Unknown options: "{key.name}" not found.') - - valobj = self.options[key] - old_value = valobj.value - changed = valobj.set_value(new_value) - - if valobj.readonly and changed and not first_invocation: - raise MesonException(f'Tried to modify read only option {str(key)!r}') - - if key.name == 'prefix' and first_invocation and changed: - assert isinstance(old_value, str) - self.reset_prefixed_options(old_value, new_value) - - if changed: - self.set_dependents(key, new_value) - - return changed - - def set_dependents(self, key: OptionKey, value: str) -> None: - if key.name != 'buildtype': - return + def _set_dependents(self, key: OptionKey, value: str) -> None: opt, debug = self.DEFAULT_DEPENDENTS[value] dkey = key.evolve(name='debug') optkey = key.evolve(name='optimization') self.options[dkey].set_value(debug) self.options[optkey].set_value(opt) - def set_option(self, key: OptionKey, new_value: str, first_invocation: bool = False) -> bool: - assert isinstance(key, OptionKey) - # FIXME, dupe of set_value - # Remove one of the two before merging to master. + def set_option(self, key: OptionKey, new_value: ElementaryOptionValues, first_invocation: bool = False) -> bool: if key.name == 'prefix': + assert isinstance(new_value, str), 'for mypy' new_value = self.sanitize_prefix(new_value) elif self.is_builtin_option(key): prefix = self.get_value_for('prefix') - assert isinstance(prefix, str) + assert isinstance(prefix, str), 'for mypy' new_value = self.sanitize_dir_option_value(prefix, key, new_value) - opt = self.get_value_object_for(key) + + try: + opt = self.get_value_object_for(key) + except KeyError: + opt = None + if opt is None: + raise MesonException(f'Unknown options: "{key!s}" not found.') + if opt.deprecated is True: mlog.deprecation(f'Option {key.name!r} is deprecated') elif isinstance(opt.deprecated, list): @@ -1014,15 +990,17 @@ def replace(v: T.Any) -> T.Any: old_value = opt.value changed = opt.set_value(new_value) - if opt.readonly and changed: - raise MesonException(f'Tried modify read only option {str(key)!r}') + if opt.readonly and changed and not first_invocation: + raise MesonException(f'Tried to modify read only option {str(key)!r}') if key.name == 'prefix' and first_invocation and changed: assert isinstance(old_value, str), 'for mypy' + assert isinstance(new_value, str), 'for mypy' self.reset_prefixed_options(old_value, new_value) - if changed: - self.set_dependents(key, new_value) + if changed and key.name == 'buildtype': + assert isinstance(new_value, str), 'for mypy' + self._set_dependents(key, new_value) return changed @@ -1032,9 +1010,9 @@ def set_option_from_string(self, keystr: T.Union[OptionKey, str], new_value: str else: o = OptionKey.from_string(keystr) if o in self.options: - return self.set_value(o, new_value) + return self.set_option(o, new_value) o = o.as_root() - return self.set_value(o, new_value) + return self.set_option(o, new_value) def set_from_configure_command(self, D_args: T.List[str], U_args: T.List[str]) -> bool: dirty = False @@ -1279,7 +1257,7 @@ def initialize_from_top_level_project_call(self, augstr = str(key) self.augments[augstr] = valstr elif key in self.options: - self.set_value(key, valstr, first_invocation) + self.set_option(key, valstr, first_invocation) else: proj_key = key.as_root() if proj_key in self.options: @@ -1330,7 +1308,7 @@ def initialize_from_top_level_project_call(self, if not self.is_cross and key.is_for_build(): continue if key in self.options: - self.set_value(key, valstr, True) + self.set_option(key, valstr, True) elif key.subproject is None: projectkey = key.as_root() if projectkey in self.options: @@ -1371,7 +1349,7 @@ def initialize_from_subproject_call(self, # If the key points to a project option, set the value from that. # Otherwise set an augment. if key in self.project_options: - self.set_value(key, valstr, is_first_invocation) + self.set_option(key, valstr, is_first_invocation) else: self.pending_project_options.pop(key, None) aug_str = f'{subproject}:{keystr}' @@ -1385,6 +1363,6 @@ def initialize_from_subproject_call(self, continue self.pending_project_options.pop(key, None) if key in self.options: - self.set_value(key, valstr, is_first_invocation) + self.set_option(key, valstr, is_first_invocation) else: self.augments[str(key)] = valstr diff --git a/unittests/platformagnostictests.py b/unittests/platformagnostictests.py index cb6a931fc602..00106089cdac 100644 --- a/unittests/platformagnostictests.py +++ b/unittests/platformagnostictests.py @@ -451,7 +451,7 @@ def test_configure_removed_option(self) -> None: f.write(line) with self.assertRaises(subprocess.CalledProcessError) as e: self.setconf('-Dneg_int_opt=0') - self.assertIn('Unknown options: "neg_int_opt"', e.exception.stdout) + self.assertIn('Unknown options: ":neg_int_opt"', e.exception.stdout) def test_configure_option_changed_constraints(self) -> None: """Changing the constraints of an option without reconfiguring should work.""" @@ -491,7 +491,7 @@ def test_configure_options_file_deleted(self) -> None: os.unlink(os.path.join(testdir, 'meson_options.txt')) with self.assertRaises(subprocess.CalledProcessError) as e: self.setconf('-Dneg_int_opt=0') - self.assertIn('Unknown options: "neg_int_opt"', e.exception.stdout) + self.assertIn('Unknown options: ":neg_int_opt"', e.exception.stdout) def test_configure_options_file_added(self) -> None: """A new project option file should be detected."""