Skip to content

Commit 70ebe33

Browse files
bonzinijpakkane
authored andcommitted
options: handle augments in OptionStore.set_option
Remove all the special casing and late validation now that early augments are stored in pending_subproject_options until the subproject is found. As a result, this makes the buildtype special case operate on subprojects as well. It also simplifies set_from_configure_command(), which does not have to treat various kinds of options in different ways. Fixes: #14729 Signed-off-by: Paolo Bonzini <[email protected]>
1 parent ffa2685 commit 70ebe33

File tree

2 files changed

+66
-49
lines changed

2 files changed

+66
-49
lines changed

mesonbuild/options.py

Lines changed: 39 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -885,9 +885,9 @@ def get_value_object_and_value_for(self, key: OptionKey) -> T.Tuple[AnyOptionTyp
885885
assert isinstance(key, OptionKey)
886886
vobject = self.get_value_object_for(key)
887887
computed_value = vobject.value
888-
if key.subproject is not None:
889-
if key in self.augments:
890-
computed_value = vobject.validate_value(self.augments[key])
888+
if key in self.augments:
889+
assert key.subproject is not None
890+
computed_value = self.augments[key]
891891
return (vobject, computed_value)
892892

893893
def option_has_value(self, key: OptionKey, value: ElementaryOptionValues) -> bool:
@@ -1004,6 +1004,7 @@ def sanitize_dir_option_value(self, prefix: str, option: OptionKey, value: T.Any
10041004
return value.as_posix()
10051005

10061006
def set_option(self, key: OptionKey, new_value: ElementaryOptionValues, first_invocation: bool = False) -> bool:
1007+
changed = False
10071008
error_key = key
10081009
if error_key.subproject == '':
10091010
error_key = error_key.evolve(subproject=None)
@@ -1040,13 +1041,18 @@ def replace(v: str) -> str:
10401041
elif isinstance(opt.deprecated, str):
10411042
mlog.deprecation(f'Option "{error_key}" is replaced by {opt.deprecated!r}')
10421043
# Change both this aption and the new one pointed to.
1043-
dirty = self.set_option(key.evolve(name=opt.deprecated), new_value)
1044-
dirty |= opt.set_value(new_value)
1045-
return dirty
1044+
changed |= self.set_option(key.evolve(name=opt.deprecated), new_value, first_invocation)
10461045

1047-
old_value = opt.value
1048-
changed = opt.set_value(new_value)
1046+
new_value = opt.validate_value(new_value)
1047+
if key in self.options:
1048+
old_value = opt.value
1049+
opt.set_value(new_value)
1050+
else:
1051+
assert key.subproject is not None
1052+
old_value = self.augments.get(key, opt.value)
1053+
self.augments[key] = new_value
10491054

1055+
changed |= old_value != new_value
10501056
if opt.readonly and changed and not first_invocation:
10511057
raise MesonException(f'Tried to modify read only option "{error_key}"')
10521058

@@ -1060,12 +1066,12 @@ def replace(v: str) -> str:
10601066
optimization, debug = self.DEFAULT_DEPENDENTS[new_value]
10611067
dkey = key.evolve(name='debug')
10621068
optkey = key.evolve(name='optimization')
1063-
self.options[dkey].set_value(debug)
1064-
self.options[optkey].set_value(optimization)
1069+
self.set_option(dkey, debug, first_invocation)
1070+
self.set_option(optkey, optimization, first_invocation)
10651071

10661072
return changed
10671073

1068-
def set_option_maybe_root(self, o: OptionKey, new_value: ElementaryOptionValues, first_invocation: bool = False) -> bool:
1074+
def set_user_option(self, o: OptionKey, new_value: ElementaryOptionValues, first_invocation: bool = False) -> bool:
10691075
if not self.is_cross and o.is_for_build():
10701076
return False
10711077

@@ -1076,37 +1082,34 @@ def set_option_maybe_root(self, o: OptionKey, new_value: ElementaryOptionValues,
10761082
# can be either
10771083
#
10781084
# A) a system option in which case the subproject is None
1079-
# B) a project option, in which case the subproject is '' (this method is only called from top level)
1085+
# B) a project option, in which case the subproject is ''
10801086
#
10811087
# The key parsing function can not handle the difference between the two
10821088
# and defaults to A.
10831089
if o in self.options:
10841090
return self.set_option(o, new_value, first_invocation)
1091+
1092+
# could also be an augment...
1093+
global_option = o.evolve(subproject=None)
1094+
if o.subproject is not None and global_option in self.options:
1095+
return self.set_option(o, new_value, first_invocation)
1096+
10851097
if self.accept_as_pending_option(o, first_invocation=first_invocation):
10861098
old_value = self.pending_options.get(o, None)
10871099
self.pending_options[o] = new_value
10881100
return old_value is None or str(old_value) != new_value
1089-
else:
1101+
elif o.subproject is None:
10901102
o = o.as_root()
10911103
return self.set_option(o, new_value, first_invocation)
1104+
else:
1105+
raise MesonException(f'Unknown option: "{o}".')
10921106

10931107
def set_from_configure_command(self, D_args: T.List[str], U_args: T.List[str]) -> bool:
10941108
dirty = False
10951109
D_args = [] if D_args is None else D_args
1096-
(global_options, perproject_global_options, project_options) = self.classify_D_arguments(D_args)
10971110
U_args = [] if U_args is None else U_args
1098-
for key, valstr in global_options:
1099-
dirty |= self.set_option_maybe_root(key, valstr)
1100-
for key, valstr in project_options:
1101-
dirty |= self.set_option_maybe_root(key, valstr)
1102-
for key, valstr in perproject_global_options:
1103-
if key in self.augments:
1104-
if self.augments[key] != valstr:
1105-
self.augments[key] = valstr
1106-
dirty = True
1107-
else:
1108-
self.augments[key] = valstr
1109-
dirty = True
1111+
for key, valstr in self.parse_D_arguments(D_args):
1112+
dirty |= self.set_user_option(key, valstr)
11101113
for keystr in U_args:
11111114
key = OptionKey.from_string(keystr)
11121115
if key in self.augments:
@@ -1232,23 +1235,13 @@ def is_compiler_option(self, key: OptionKey) -> bool:
12321235
def is_module_option(self, key: OptionKey) -> bool:
12331236
return key in self.module_options
12341237

1235-
def classify_D_arguments(self, D: T.List[str]) -> T.Tuple[T.List[T.Tuple[OptionKey, str]],
1236-
T.List[T.Tuple[OptionKey, str]],
1237-
T.List[T.Tuple[OptionKey, str]]]:
1238-
global_options = []
1239-
project_options = []
1240-
perproject_global_options = []
1238+
def parse_D_arguments(self, D: T.List[str]) -> T.List[T.Tuple[OptionKey, str]]:
1239+
options = []
12411240
for setval in D:
12421241
keystr, valstr = setval.split('=', 1)
12431242
key = OptionKey.from_string(keystr)
1244-
valuetuple = (key, valstr)
1245-
if self.is_project_option(key):
1246-
project_options.append(valuetuple)
1247-
elif key.subproject is None:
1248-
global_options.append(valuetuple)
1249-
else:
1250-
perproject_global_options.append(valuetuple)
1251-
return (global_options, perproject_global_options, project_options)
1243+
options.append((key, valstr))
1244+
return options
12521245

12531246
def prefix_split_options(self, coll: OptionDict) -> T.Tuple[T.Optional[str], OptionDict]:
12541247
prefix = None
@@ -1319,7 +1312,7 @@ def initialize_from_top_level_project_call(self,
13191312
# should arguably be a hard error; the default
13201313
# value of project option should be set in the option
13211314
# file, not in the project call.
1322-
self.set_option_maybe_root(key, valstr, True)
1315+
self.set_user_option(key, valstr, True)
13231316

13241317
# ignore subprojects for now for machine file and command line
13251318
# options; they are applied later
@@ -1329,14 +1322,14 @@ def initialize_from_top_level_project_call(self,
13291322
if not self.is_cross and key.is_for_build():
13301323
continue
13311324
if not key.subproject:
1332-
self.set_option_maybe_root(key, valstr, True)
1325+
self.set_user_option(key, valstr, True)
13331326
for key, valstr in cmd_line_options.items():
13341327
# Due to backwards compatibility we ignore all build-machine options
13351328
# when building natively.
13361329
if not self.is_cross and key.is_for_build():
13371330
continue
13381331
if not key.subproject:
1339-
self.set_option_maybe_root(key, valstr, True)
1332+
self.set_user_option(key, valstr, True)
13401333

13411334
def accept_as_pending_option(self, key: OptionKey, first_invocation: bool = False) -> bool:
13421335
# Some base options (sanitizers etc) might get added later.
@@ -1401,11 +1394,9 @@ def initialize_from_subproject_call(self,
14011394
continue
14021395

14031396
self.pending_subproject_options.pop(key, None)
1404-
valstr = self.augments.pop(key, valstr)
1405-
if key in self.project_options:
1406-
self.set_option(key, valstr, True)
1407-
else:
1408-
self.augments[key] = valstr
1397+
self.pending_options.pop(key, None)
1398+
if key not in self.augments:
1399+
self.set_user_option(key, valstr, True)
14091400

14101401
self.subprojects.add(subproject)
14111402

unittests/optiontests.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,33 @@ def test_subproject_cmdline_override_toplevel(self):
314314
optstore.initialize_from_top_level_project_call(toplevel_proj_default, cmd_line, {})
315315
optstore.initialize_from_subproject_call(subp, {}, subp_proj_default, cmd_line, {})
316316
self.assertEqual(optstore.get_value_for(name, subp), subp_value)
317-
self.assertEqual(optstore.get_value_for(name), toplevel_value)
317+
self.assertEqual(optstore.get_value_for(name, ''), toplevel_value)
318+
319+
def test_subproject_buildtype(self):
320+
subp = 'subp'
321+
main1 = {OptionKey('buildtype'): 'release'}
322+
main2 = {OptionKey('optimization'): '3', OptionKey('debug'): 'false'}
323+
sub1 = {OptionKey('buildtype'): 'debug'}
324+
sub2 = {OptionKey('optimization'): '0', OptionKey('debug'): 'true'}
325+
326+
for mainopt, subopt in ((main1, sub1),
327+
(main2, sub2),
328+
({**main1, **main2}, {**sub1, **sub2})):
329+
optstore = OptionStore(False)
330+
prefix = UserStringOption('prefix', 'This is needed by OptionStore', '/usr')
331+
optstore.add_system_option('prefix', prefix)
332+
o = UserComboOption('buildtype', 'Build type to use', 'debug', choices=['plain', 'debug', 'debugoptimized', 'release', 'minsize', 'custom'])
333+
optstore.add_system_option(o.name, o)
334+
o = UserComboOption('optimization', 'Optimization level', '0', choices=['plain', '0', 'g', '1', '2', '3', 's'])
335+
optstore.add_system_option(o.name, o)
336+
o = UserBooleanOption('debug', 'Enable debug symbols and other information', True)
337+
optstore.add_system_option(o.name, o)
338+
339+
optstore.initialize_from_top_level_project_call(mainopt, {}, {})
340+
optstore.initialize_from_subproject_call(subp, {}, subopt, {}, {})
341+
self.assertEqual(optstore.get_value_for('buildtype', subp), 'debug')
342+
self.assertEqual(optstore.get_value_for('optimization', subp), '0')
343+
self.assertEqual(optstore.get_value_for('debug', subp), True)
318344

319345
def test_deprecated_nonstring_value(self):
320346
# TODO: add a lot more deprecated option tests

0 commit comments

Comments
 (0)