diff --git a/mesonbuild/options.py b/mesonbuild/options.py index f2cc63c65099..7a83b9264854 100644 --- a/mesonbuild/options.py +++ b/mesonbuild/options.py @@ -680,6 +680,8 @@ def add_to_argparse(self, name: str, parser: argparse.ArgumentParser, help_suffi OPTNAME_SPLITTER = re.compile(OPTNAME_REGEX) OPTNAME_AND_VALUE_SPLITTER = re.compile(OPTNAME_AND_VALUE_REGEX) +BAD_VALUE = 'Qwert ZuiopĆ¼' + class OptionParts: def __init__(self, name, subproject=None, for_build=False): self.name = name @@ -693,6 +695,11 @@ def __eq__(self, other): if isinstance(other, OptionParts): return self.name == other.name and self.subproject == other.subproject and self.for_build == other.for_build + def copy_with(self, *, name=BAD_VALUE, subproject=BAD_VALUE, for_build=BAD_VALUE): + return OptionParts(name if name != BAD_VALUE else self.name, + subproject if subproject != BAD_VALUE else self.subproject, # None is a valid value so it can'the default value in method declaration. + for_build if for_build != BAD_VALUE else self.for_build) + class OptionStore: def __init__(self): self.d: T.Dict['OptionKey', 'UserOption[T.Any]'] = {} @@ -711,26 +718,24 @@ def num_options(self): build = len(self.build_options) if self.build_options else 0 return basic + build - def has_option(self, name, subproject, for_build=False): - cname = self.form_canonical_keystring(name, subproject, for_build) - return cname in self.options + def has_option(self, optparts): + assert isinstance(optparts, OptionParts) + return optparts in self.options - def set_option(self, name, subproject, new_value): - cname = self.form_canonical_keystring(name) - return self.set_option_from_string(cname, new_value) + def set_option(self, optparts, new_value): + assert isinstance(optparts, OptionParts) + return self.options[optparts].set_value(new_value) def set_option_from_string(self, keystr, new_value): - m = re.fullmatch(OPTNAME_REGEX, keystr) # Merely for validation - return self.options[keystr].set_value(new_value) - - def parts_to_canonical_keystring(self, parts): - return self.form_canonical_keystring(parts.name, parts.subproject, parts.for_build) - - def form_canonical_keystring(self, name, subproject=None, for_build=None): - strname = name - if subproject is not None: - strname = f'{subproject}:{strname}' - if for_build: + o = self.split_keystring(keystr) + return self.options[o].set_value(new_value) + + def form_canonical_keystring(self, optparts): + assert isinstance(optparts, OptionParts) + strname = optparts.name + if optparts.subproject is not None: + strname = f'{optparts.subproject}:{strname}' + if optparts.for_build: strname = 'build.' + strname return strname @@ -746,29 +751,30 @@ def split_keystring(self, option_str): def canonicalize_keystring(self, keystr): parts = self.split_keystring(keystr) - return self.form_canonical_keystring(parts.name, parts.subproject, parts.for_build) + return self.form_canonical_keystring(parts) def add_system_option(self, name, value_object): assert isinstance(name, str) - cname = self.form_canonical_keystring(name) + k = OptionParts(name) # FIXME; transfer the old value for combos etc. - if cname not in self.options: - self.options[cname] = value_object + if k not in self.options: + self.options[k] = value_object def add_project_option(self, name, subproject, value_object): - cname = self.form_canonical_keystring(name, subproject) - self.options[cname] = value_object - self.project_options.add(cname) + assert isinstance(name, str) + k = OptionParts(name, subproject) + self.options[k] = value_object + self.project_options.add(k) - def get_value_object_for(self, name, subproject=None): - cname = self.form_canonical_keystring(name, subproject) - potential = self.options.get(cname, None) + def get_value_object_for(self, optioninfo, subproject=None): + assert isinstance(optioninfo, OptionParts) + potential = self.options.get(optioninfo, None) if potential is None: - top_cname = self.form_canonical_keystring(name) - return self.options[top_cname] + top_option = optioninfo.copy_with(subproject=None) + return self.options[top_option] if potential.yielding: - top_cname = self.form_canonical_keystring(name, '') - return self.options.get(top_cname, potential) + top_option = optioninfo.copy_with(subproject='') + return self.options.get(top_option, potential) return potential def add_system_option(self, key: T.Union[OptionKey, str], valobj: 'UserOption[T.Any]'): @@ -885,34 +891,37 @@ def is_compiler_option(self, key: OptionKey) -> bool: def is_module_option(self, key: OptionKey) -> bool: return key in self.module_options - def get_value_for(self, name, subproject=None): - vobject = self.get_value_object_for(name, subproject) - cname = self.form_canonical_keystring(name, subproject) - if cname in self.augments: - return vobject.validate_value(self.augments[cname]) + def get_value_for(self, optioninfo): + assert isinstance(optioninfo, OptionParts) + vobject = self.get_value_object_for(optioninfo) + if optioninfo in self.augments: + return vobject.validate_value(self.augments[optioninfo]) return vobject.value def set_from_configure_command(self, D, A, U): for setval in D: keystr, valstr = setval.split('=', 1) - if keystr in self.augments: - self.augments[keystr] = valstr + key = self.split_keystring(keystr) + if key in self.augments: + self.augments[key] = valstr else: self.set_option_from_string(keystr, valstr) for add in A: keystr, valstr = add.split('=', 1) - keystr = self.canonicalize_keystring(keystr) - if keystr in self.augments: + key = self.split_keystring(keystr) + if key in self.augments: raise MesonException(f'Tried to add augment to option {keystr}, which already has an augment. Set it with -D instead.') - self.augments[keystr] = valstr + self.augments[key] = valstr for delete in U: - delete = self.canonicalize_keystring(delete) + delete = self.split_keystring(delete) if delete in self.augments: del self.augments[delete] def set_subproject_options(self, subproject, spcall_default_options, project_default_options): for o in itertools.chain(spcall_default_options, project_default_options): keystr, valstr = o.split('=', 1) - keystr = f'{subproject}:{keystr}' - if keystr not in self.augments: - self.augments[keystr] = valstr + key = self.split_keystring(keystr) + assert key.subproject is None + key.subproject = subproject + if key not in self.augments: + self.augments[key] = valstr diff --git a/unittests/optiontests.py b/unittests/optiontests.py index 98c7054604d2..04739a0867b4 100644 --- a/unittests/optiontests.py +++ b/unittests/optiontests.py @@ -13,50 +13,54 @@ def test_basic(self): name = 'someoption' default_value = 'somevalue' new_value = 'new_value' - vo = UserStringOption(name, 'An option of some sort', default_value) - optstore.add_system_option(name, vo) - self.assertEqual(optstore.get_value_for(name), default_value) - optstore.set_option(name, '', new_value) - self.assertEqual(optstore.get_value_for(name), new_value) + k = OptionParts(name) + vo = UserStringOption(k, 'An option of some sort', default_value) + optstore.add_system_option(k.name, vo) + self.assertEqual(optstore.get_value_for(k), default_value) + optstore.set_option(k, new_value) + self.assertEqual(optstore.get_value_for(k), new_value) def test_parsing(self): optstore = OptionStore() s1 = optstore.split_keystring('sub:optname') s1_expected = OptionParts('optname', 'sub', False) self.assertEqual(s1, s1_expected) - self.assertEqual(optstore.parts_to_canonical_keystring(s1), 'sub:optname') + self.assertEqual(optstore.form_canonical_keystring(s1), 'sub:optname') s2 = optstore.split_keystring('optname') s2_expected = OptionParts('optname', None, False) self.assertEqual(s2, s2_expected) - self.assertEqual(optstore.parts_to_canonical_keystring(s2), 'optname') + self.assertEqual(optstore.form_canonical_keystring(s2), 'optname') s3 = optstore.split_keystring(':optname') s3_expected = OptionParts('optname', '', False) self.assertEqual(s3, s3_expected) - self.assertEqual(optstore.parts_to_canonical_keystring(s3), ':optname') + self.assertEqual(optstore.form_canonical_keystring(s3), ':optname') def test_subproject_for_system(self): optstore = OptionStore() name = 'someoption' + key = OptionParts(name) + subkey = key.copy_with(subproject='somesubproject') default_value = 'somevalue' vo = UserStringOption(name, 'An option of some sort', default_value) optstore.add_system_option(name, vo) - self.assertEqual(optstore.get_value_for(name, 'somesubproject'), default_value) + self.assertEqual(optstore.get_value_for(subkey), default_value) def test_reset(self): optstore = OptionStore() name = 'someoption' original_value = 'original' reset_value = 'reset' + k = OptionParts(name) vo = UserStringOption(name, 'An option set twice', original_value) optstore.add_system_option(name, vo) - self.assertEqual(optstore.get_value_for(name), original_value) + self.assertEqual(optstore.get_value_for(k), original_value) self.assertEqual(optstore.num_options(), 1) vo2 = UserStringOption(name, 'An option set twice', reset_value) optstore.add_system_option(name, vo2) - self.assertEqual(optstore.get_value_for(name), original_value) + self.assertEqual(optstore.get_value_for(k), original_value) self.assertEqual(optstore.num_options(), 1) def test_project_nonyielding(self): @@ -64,14 +68,16 @@ def test_project_nonyielding(self): name = 'someoption' top_value = 'top' sub_value = 'sub' + top_key = OptionParts(name, '') + sub_key = top_key.copy_with(subproject='sub') vo = UserStringOption(name, 'A top level option', top_value, False) optstore.add_project_option(name, '', vo) - self.assertEqual(optstore.get_value_for(name, ''), top_value, False) + self.assertEqual(optstore.get_value_for(top_key), top_value, False) self.assertEqual(optstore.num_options(), 1) vo2 = UserStringOption(name, 'A subproject option', sub_value) optstore.add_project_option(name, 'sub', vo2) - self.assertEqual(optstore.get_value_for(name, ''), top_value) - self.assertEqual(optstore.get_value_for(name, 'sub'), sub_value) + self.assertEqual(optstore.get_value_for(top_key), top_value) + self.assertEqual(optstore.get_value_for(sub_key), sub_value) self.assertEqual(optstore.num_options(), 2) def test_project_yielding(self): @@ -80,13 +86,15 @@ def test_project_yielding(self): top_value = 'top' sub_value = 'sub' vo = UserStringOption(name, 'A top level option', top_value) + top_key = OptionParts(name, '') + sub_key = top_key.copy_with(subproject='sub') optstore.add_project_option(name, '', vo) - self.assertEqual(optstore.get_value_for(name, ''), top_value) + self.assertEqual(optstore.get_value_for(top_key), top_value) self.assertEqual(optstore.num_options(), 1) vo2 = UserStringOption(name, 'A subproject option', sub_value, True) optstore.add_project_option(name, 'sub', vo2) - self.assertEqual(optstore.get_value_for(name, ''), top_value) - self.assertEqual(optstore.get_value_for(name, 'sub'), top_value) + self.assertEqual(optstore.get_value_for(top_key), top_value) + self.assertEqual(optstore.get_value_for(sub_key), top_value) self.assertEqual(optstore.num_options(), 2) def test_augments(self): @@ -96,45 +104,50 @@ def test_augments(self): sub2_name = 'sub2' top_value = 'c++11' aug_value = 'c++23' + top_key = OptionParts(name) + topsub_key = top_key.copy_with(subproject='') + sub_key = top_key.copy_with(subproject=sub_name) + sub2_key = top_key.copy_with(subproject=sub2_name) co = UserComboOption(name, 'C++ language standard to use', ['c++98', 'c++11', 'c++14', 'c++17', 'c++20', 'c++23'], top_value) optstore.add_system_option(name, co) - self.assertEqual(optstore.get_value_for(name), top_value) - self.assertEqual(optstore.get_value_for(name, sub_name), top_value) - self.assertEqual(optstore.get_value_for(name, sub2_name), top_value) + self.assertEqual(optstore.get_value_for(top_key), top_value) + self.assertEqual(optstore.get_value_for(sub_key), top_value) + self.assertEqual(optstore.get_value_for(sub2_key), top_value) # First augment a subproject optstore.set_from_configure_command([], [f'{sub_name}:{name}={aug_value}'], []) - self.assertEqual(optstore.get_value_for(name), top_value) - self.assertEqual(optstore.get_value_for(name, sub_name), aug_value) - self.assertEqual(optstore.get_value_for(name, sub2_name), top_value) + self.assertEqual(optstore.get_value_for(top_key), top_value) + self.assertEqual(optstore.get_value_for(sub_key), aug_value) + self.assertEqual(optstore.get_value_for(sub2_key), top_value) optstore.set_from_configure_command([], [], [f'{sub_name}:{name}']) - self.assertEqual(optstore.get_value_for(name), top_value) - self.assertEqual(optstore.get_value_for(name, sub_name), top_value) - self.assertEqual(optstore.get_value_for(name, sub2_name), top_value) + self.assertEqual(optstore.get_value_for(top_key), top_value) + self.assertEqual(optstore.get_value_for(sub_key), top_value) + self.assertEqual(optstore.get_value_for(sub2_key), top_value) # And now augment the top level option optstore.set_from_configure_command([], [f':{name}={aug_value}'], []) - self.assertEqual(optstore.get_value_for(name, None), top_value) - self.assertEqual(optstore.get_value_for(name, ''), aug_value) - self.assertEqual(optstore.get_value_for(name, sub_name), top_value) - self.assertEqual(optstore.get_value_for(name, sub2_name), top_value) + self.assertEqual(optstore.get_value_for(top_key), top_value) + self.assertEqual(optstore.get_value_for(topsub_key), aug_value) + self.assertEqual(optstore.get_value_for(sub_key), top_value) + self.assertEqual(optstore.get_value_for(sub2_key), top_value) optstore.set_from_configure_command([], [], [f':{name}']) - self.assertEqual(optstore.get_value_for(name), top_value) - self.assertEqual(optstore.get_value_for(name, sub_name), top_value) - self.assertEqual(optstore.get_value_for(name, sub2_name), top_value) + self.assertEqual(optstore.get_value_for(top_key), top_value) + self.assertEqual(optstore.get_value_for(sub_key), top_value) + self.assertEqual(optstore.get_value_for(sub2_key), top_value) def test_augment_set_sub(self): optstore = OptionStore() name = 'cpp_std' sub_name = 'sub' - sub2_name = 'sub2' top_value = 'c++11' aug_value = 'c++23' set_value = 'c++20' + top_key = OptionParts(name=name) + sub_key = top_key.copy_with(subproject=sub_name) co = UserComboOption(name, 'C++ language standard to use', @@ -143,8 +156,8 @@ def test_augment_set_sub(self): optstore.add_system_option(name, co) optstore.set_from_configure_command([], [f'{sub_name}:{name}={aug_value}'], []) optstore.set_from_configure_command([f'{sub_name}:{name}={set_value}'], [], []) - self.assertEqual(optstore.get_value_for(name), top_value) - self.assertEqual(optstore.get_value_for(name, sub_name), set_value) + self.assertEqual(optstore.get_value_for(top_key), top_value) + self.assertEqual(optstore.get_value_for(sub_key), set_value) def test_subproject_call_options(self): optstore = OptionStore() @@ -160,10 +173,12 @@ def test_subproject_call_options(self): default_value) optstore.add_system_option(name, co) optstore.set_subproject_options(subproject, [f'cpp_std={override_value}'], [f'cpp_std={unused_value}']) - self.assertEqual(optstore.get_value_for(name), default_value) - self.assertEqual(optstore.get_value_for(name, subproject), override_value) + k = OptionParts(name) + sub_k = k.copy_with(subproject=subproject) + self.assertEqual(optstore.get_value_for(k), default_value) + self.assertEqual(optstore.get_value_for(sub_k), override_value) # Trying again should change nothing optstore.set_subproject_options(subproject, [f'cpp_std={unused_value}'], [f'cpp_std={unused_value}']) - self.assertEqual(optstore.get_value_for(name), default_value) - self.assertEqual(optstore.get_value_for(name, subproject), override_value) + self.assertEqual(optstore.get_value_for(k), default_value) + self.assertEqual(optstore.get_value_for(sub_k), override_value)