aboutsummaryrefslogtreecommitdiff
path: root/mesonbuild/options.py
diff options
context:
space:
mode:
Diffstat (limited to 'mesonbuild/options.py')
-rw-r--r--mesonbuild/options.py387
1 files changed, 176 insertions, 211 deletions
diff --git a/mesonbuild/options.py b/mesonbuild/options.py
index 3b7d8b2..bc4d79f 100644
--- a/mesonbuild/options.py
+++ b/mesonbuild/options.py
@@ -310,7 +310,7 @@ class OptionKey:
return self.machine is MachineChoice.BUILD
if T.TYPE_CHECKING:
- OptionStringLikeDict: TypeAlias = T.Dict[T.Union[OptionKey, str], str]
+ OptionDict: TypeAlias = T.Dict[OptionKey, ElementaryOptionValues]
@dataclasses.dataclass
class UserOption(T.Generic[_T], HoldableObject):
@@ -327,7 +327,13 @@ class UserOption(T.Generic[_T], HoldableObject):
# Final isn't technically allowed in a __post_init__ method
self.default: Final[_T] = self.value # type: ignore[misc]
- def listify(self, value: T.Any) -> T.List[T.Any]:
+ def listify(self, value: ElementaryOptionValues) -> T.List[str]:
+ if isinstance(value, list):
+ return value
+ if isinstance(value, bool):
+ return ['true'] if value else ['false']
+ if isinstance(value, int):
+ return [str(value)]
return [value]
def printable_value(self) -> ElementaryOptionValues:
@@ -340,10 +346,10 @@ class UserOption(T.Generic[_T], HoldableObject):
# 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:
+ def validate_value(self, value: object) -> _T:
raise RuntimeError('Derived option class did not override validate_value.')
- def set_value(self, newvalue: T.Any) -> bool:
+ def set_value(self, newvalue: object) -> bool:
oldvalue = self.value
self.value = self.validate_value(newvalue)
return self.value != oldvalue
@@ -361,7 +367,7 @@ class EnumeratedUserOption(UserOption[_T]):
class UserStringOption(UserOption[str]):
- def validate_value(self, value: T.Any) -> str:
+ def validate_value(self, value: object) -> str:
if not isinstance(value, str):
raise MesonException(f'The value of option "{self.name}" is "{value}", which is not a string.')
return value
@@ -374,7 +380,7 @@ class UserBooleanOption(EnumeratedUserOption[bool]):
def __bool__(self) -> bool:
return self.value
- def validate_value(self, value: T.Any) -> bool:
+ def validate_value(self, value: object) -> bool:
if isinstance(value, bool):
return value
if not isinstance(value, str):
@@ -406,7 +412,7 @@ class _UserIntegerBase(UserOption[_T]):
def printable_choices(self) -> T.Optional[T.List[str]]:
return [self.__choices]
- def validate_value(self, value: T.Any) -> _T:
+ def validate_value(self, value: object) -> _T:
if isinstance(value, str):
value = T.cast('_T', self.toint(value))
if not isinstance(value, int):
@@ -450,7 +456,7 @@ class UserUmaskOption(_UserIntegerBase[T.Union["Literal['preserve']", OctalInt]]
return format(self.value, '04o')
return self.value
- def validate_value(self, value: T.Any) -> T.Union[Literal['preserve'], OctalInt]:
+ def validate_value(self, value: object) -> T.Union[Literal['preserve'], OctalInt]:
if value == 'preserve':
return 'preserve'
return OctalInt(super().validate_value(value))
@@ -465,7 +471,7 @@ class UserUmaskOption(_UserIntegerBase[T.Union["Literal['preserve']", OctalInt]]
@dataclasses.dataclass
class UserComboOption(EnumeratedUserOption[str]):
- def validate_value(self, value: T.Any) -> str:
+ def validate_value(self, value: object) -> str:
if value not in self.choices:
if isinstance(value, bool):
_type = 'boolean'
@@ -503,13 +509,13 @@ class UserArrayOption(UserOption[T.List[_T]]):
@dataclasses.dataclass
class UserStringArrayOption(UserArrayOption[str]):
- def listify(self, value: T.Any) -> T.List[T.Any]:
+ def listify(self, value: object) -> T.List[str]:
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]:
+ def validate_value(self, value: object) -> T.List[str]:
newvalue = self.listify(value)
if not self.allow_dups and len(set(newvalue)) != len(newvalue):
@@ -606,11 +612,14 @@ class UserStdOption(UserComboOption):
else:
self.choices += gnu_stds_map.keys()
- def validate_value(self, value: T.Union[str, T.List[str]]) -> str:
+ def validate_value(self, value: object) -> str:
try:
candidates = listify_array_value(value)
except MesonException as e:
raise MesonException(f'error in option "{self.name}": {e!s}')
+ for std in candidates:
+ if not isinstance(std, str):
+ raise MesonException(f'String array element "{candidates!s}" for option "{self.name}" is not a string.')
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}.')
@@ -800,14 +809,13 @@ class OptionStore:
self.module_options: T.Set[OptionKey] = set()
from .compilers import all_languages
self.all_languages = set(all_languages)
- self.project_options = set()
- self.augments: T.Dict[str, str] = {}
+ self.augments: OptionDict = {}
self.is_cross = is_cross
# Pending options are options that need to be initialized later, either
# configuration dependent options like compiler options, or options for
# a different subproject
- self.pending_options: T.Dict[OptionKey, ElementaryOptionValues] = {}
+ self.pending_options: OptionDict = {}
def clear_pending(self) -> None:
self.pending_options = {}
@@ -829,6 +837,12 @@ class OptionStore:
key = key.as_host()
return key
+ def get_pending_value(self, key: T.Union[OptionKey, str], default: T.Optional[ElementaryOptionValues] = None) -> ElementaryOptionValues:
+ key = self.ensure_and_validate_key(key)
+ if key in self.options:
+ return self.options[key].value
+ return self.pending_options.get(key, default)
+
def get_value(self, key: T.Union[OptionKey, str]) -> ElementaryOptionValues:
return self.get_value_object(key).value
@@ -870,9 +884,8 @@ class OptionStore:
vobject = self.get_value_object_for(key)
computed_value = vobject.value
if key.subproject is not None:
- keystr = str(key)
- if keystr in self.augments:
- computed_value = vobject.validate_value(self.augments[keystr])
+ if key in self.augments:
+ computed_value = vobject.validate_value(self.augments[key])
return (vobject, computed_value)
def get_value_for(self, name: 'T.Union[OptionKey, str]', subproject: T.Optional[str] = None) -> ElementaryOptionValues:
@@ -897,16 +910,16 @@ class OptionStore:
if key in self.options:
return
- self.options[key] = valobj
pval = self.pending_options.pop(key, None)
if key.subproject:
proj_key = key.evolve(subproject=None)
self.add_system_option_internal(proj_key, valobj)
- if pval is None:
- pval = self.options[proj_key].value
-
- if pval is not None:
- self.set_option(key, pval)
+ if pval is not None:
+ self.augments[key] = pval
+ else:
+ self.options[key] = valobj
+ if pval is not None:
+ self.set_option(key, pval)
def add_compiler_option(self, language: str, key: T.Union[OptionKey, str], valobj: AnyOptionType) -> None:
key = self.ensure_and_validate_key(key)
@@ -985,6 +998,10 @@ class OptionStore:
return value.as_posix()
def set_option(self, key: OptionKey, new_value: ElementaryOptionValues, first_invocation: bool = False) -> bool:
+ error_key = key
+ if error_key.subproject == '':
+ error_key = error_key.evolve(subproject=None)
+
if key.name == 'prefix':
assert isinstance(new_value, str), 'for mypy'
new_value = self.sanitize_prefix(new_value)
@@ -996,26 +1013,26 @@ class OptionStore:
try:
opt = self.get_value_object_for(key)
except KeyError:
- raise MesonException(f'Unknown options: "{key!s}" not found.')
+ raise MesonException(f'Unknown option: "{error_key}".')
if opt.deprecated is True:
- mlog.deprecation(f'Option {key.name!r} is deprecated')
+ mlog.deprecation(f'Option "{error_key}" is deprecated')
elif isinstance(opt.deprecated, list):
for v in opt.listify(new_value):
if v in opt.deprecated:
- mlog.deprecation(f'Option {key.name!r} value {v!r} is deprecated')
+ mlog.deprecation(f'Option "{error_key}" value {v!r} is deprecated')
elif isinstance(opt.deprecated, dict):
- def replace(v: T.Any) -> T.Any:
+ def replace(v: str) -> str:
assert isinstance(opt.deprecated, dict) # No, Mypy can not tell this from two lines above
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}')
+ mlog.deprecation(f'Option "{error_key}" value {v!r} is replaced by {newvalue!r}')
return newvalue
return v
valarr = [replace(v) for v in opt.listify(new_value)]
new_value = ','.join(valarr)
elif isinstance(opt.deprecated, str):
- mlog.deprecation(f'Option {key.name!r} is replaced by {opt.deprecated!r}')
+ mlog.deprecation(f'Option "{error_key}" is replaced by {opt.deprecated!r}')
# Change both this aption and the new one pointed to.
dirty = self.set_option(key.evolve(name=opt.deprecated), new_value)
dirty |= opt.set_value(new_value)
@@ -1025,14 +1042,14 @@ class OptionStore:
changed = opt.set_value(new_value)
if opt.readonly and changed and not first_invocation:
- raise MesonException(f'Tried to modify read only option {str(key)!r}')
+ raise MesonException(f'Tried to modify read only option "{error_key}"')
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 and key.name == 'buildtype':
+ if changed and key.name == 'buildtype' and new_value != 'custom':
assert isinstance(new_value, str), 'for mypy'
optimization, debug = self.DEFAULT_DEPENDENTS[new_value]
dkey = key.evolve(name='debug')
@@ -1042,15 +1059,30 @@ class OptionStore:
return changed
- def set_option_from_string(self, keystr: T.Union[OptionKey, str], new_value: str) -> bool:
- if isinstance(keystr, OptionKey):
- o = keystr
- else:
- o = OptionKey.from_string(keystr)
+ def set_option_maybe_root(self, o: OptionKey, new_value: ElementaryOptionValues, first_invocation: bool = False) -> bool:
+ if not self.is_cross and o.is_for_build():
+ return False
+
+ # This is complicated by the fact that a string can have two meanings:
+ #
+ # default_options: 'foo=bar'
+ #
+ # can be either
+ #
+ # A) a system option in which case the subproject is None
+ # B) a project option, in which case the subproject is '' (this method is only called from top level)
+ #
+ # The key parsing function can not handle the difference between the two
+ # and defaults to A.
if o in self.options:
- return self.set_option(o, new_value)
- o = o.as_root()
- return self.set_option(o, new_value)
+ return self.set_option(o, new_value, first_invocation)
+ if self.accept_as_pending_option(o, first_invocation=first_invocation):
+ old_value = self.pending_options.get(o, None)
+ self.pending_options[o] = new_value
+ return old_value is None or str(old_value) == new_value
+ else:
+ o = o.as_root()
+ return self.set_option(o, new_value, first_invocation)
def set_from_configure_command(self, D_args: T.List[str], U_args: T.List[str]) -> bool:
dirty = False
@@ -1058,20 +1090,21 @@ class OptionStore:
(global_options, perproject_global_options, project_options) = self.classify_D_arguments(D_args)
U_args = [] if U_args is None else U_args
for key, valstr in global_options:
- dirty |= self.set_option_from_string(key, valstr)
+ dirty |= self.set_option_maybe_root(key, valstr)
for key, valstr in project_options:
- dirty |= self.set_option_from_string(key, valstr)
- for keystr, valstr in perproject_global_options:
- if keystr in self.augments:
- if self.augments[keystr] != valstr:
- self.augments[keystr] = valstr
+ dirty |= self.set_option_maybe_root(key, valstr)
+ for key, valstr in perproject_global_options:
+ if key in self.augments:
+ if self.augments[key] != valstr:
+ self.augments[key] = valstr
dirty = True
else:
- self.augments[keystr] = valstr
+ self.augments[key] = valstr
dirty = True
- for delete in U_args:
- if delete in self.augments:
- del self.augments[delete]
+ for keystr in U_args:
+ key = OptionKey.from_string(keystr)
+ if key in self.augments:
+ del self.augments[key]
dirty = True
return dirty
@@ -1194,7 +1227,7 @@ class OptionStore:
return key in self.module_options
def classify_D_arguments(self, D: T.List[str]) -> T.Tuple[T.List[T.Tuple[OptionKey, str]],
- T.List[T.Tuple[str, str]],
+ T.List[T.Tuple[OptionKey, str]],
T.List[T.Tuple[OptionKey, str]]]:
global_options = []
project_options = []
@@ -1208,49 +1241,28 @@ class OptionStore:
elif key.subproject is None:
global_options.append(valuetuple)
else:
- # FIXME, augments are currently stored as strings, not OptionKeys
- strvaluetuple = (keystr, valstr)
- perproject_global_options.append(strvaluetuple)
+ perproject_global_options.append(valuetuple)
return (global_options, perproject_global_options, project_options)
- def optlist2optdict(self, optlist: T.List[str]) -> T.Dict[str, str]:
- optdict = {}
- for p in optlist:
- k, v = p.split('=', 1)
- optdict[k] = v
- return optdict
-
- def prefix_split_options(self, coll: T.Union[T.List[str], OptionStringLikeDict]) -> T.Tuple[str, T.Union[T.List[str], OptionStringLikeDict]]:
+ def prefix_split_options(self, coll: OptionDict) -> T.Tuple[T.Optional[str], OptionDict]:
prefix = None
- if isinstance(coll, list):
- others: T.List[str] = []
- for e in coll:
- if e.startswith('prefix='):
- prefix = e.split('=', 1)[1]
- else:
- others.append(e)
- return (prefix, others)
- else:
- others_d: OptionStringLikeDict = {}
- for k, v in coll.items():
- if isinstance(k, OptionKey) and k.name == 'prefix':
- prefix = v
- elif k == 'prefix':
- prefix = v
- else:
- others_d[k] = v
- return (prefix, others_d)
+ others_d: OptionDict = {}
+ for k, v in coll.items():
+ if k.name == 'prefix':
+ if not isinstance(v, str):
+ raise MesonException('Incorrect type for prefix option (expected string)')
+ prefix = v
+ else:
+ others_d[k] = v
+ return (prefix, others_d)
def first_handle_prefix(self,
- project_default_options: T.Union[T.List[str], OptionStringLikeDict],
- cmd_line_options: OptionStringLikeDict,
- machine_file_options: T.Mapping[OptionKey, ElementaryOptionValues]) \
- -> T.Tuple[T.Union[T.List[str], OptionStringLikeDict],
- T.Union[T.List[str], OptionStringLikeDict],
- T.MutableMapping[OptionKey, ElementaryOptionValues]]:
+ project_default_options: OptionDict,
+ cmd_line_options: OptionDict,
+ machine_file_options: OptionDict) \
+ -> T.Tuple[OptionDict, OptionDict, OptionDict]:
# Copy to avoid later mutation
- nopref_machine_file_options = T.cast(
- 'T.MutableMapping[OptionKey, ElementaryOptionValues]', copy.copy(machine_file_options))
+ nopref_machine_file_options = copy.copy(machine_file_options)
prefix = None
(possible_prefix, nopref_project_default_options) = self.prefix_split_options(project_default_options)
@@ -1281,157 +1293,110 @@ class OptionStore:
self.options[OptionKey('prefix')].set_value(prefix)
def initialize_from_top_level_project_call(self,
- project_default_options_in: T.Union[T.List[str], OptionStringLikeDict],
- cmd_line_options_in: OptionStringLikeDict,
- machine_file_options_in: T.Mapping[OptionKey, ElementaryOptionValues]) -> None:
- first_invocation = True
+ project_default_options_in: OptionDict,
+ cmd_line_options_in: OptionDict,
+ machine_file_options_in: OptionDict) -> None:
(project_default_options, cmd_line_options, machine_file_options) = self.first_handle_prefix(project_default_options_in,
cmd_line_options_in,
machine_file_options_in)
- if isinstance(project_default_options, str):
- project_default_options = [project_default_options]
- if isinstance(project_default_options, list):
- project_default_options = self.optlist2optdict(project_default_options) # type: ignore [assignment]
- if project_default_options is None:
- project_default_options = {}
- assert isinstance(machine_file_options, dict)
- for keystr, valstr in machine_file_options.items():
- if isinstance(keystr, str):
- # FIXME, standardise on Key or string.
- key = OptionKey.from_string(keystr)
- else:
- key = keystr
- # Due to backwards compatibility we ignore all build-machine options
- # when building natively.
- if not self.is_cross and key.is_for_build():
- continue
- if key.subproject:
- augstr = str(key)
- self.augments[augstr] = valstr
- elif key in self.options:
- self.set_option(key, valstr, first_invocation)
- else:
- proj_key = key.as_root()
- if proj_key in self.options:
- self.set_option(proj_key, valstr, first_invocation)
- else:
- self.pending_options[key] = valstr
- assert isinstance(project_default_options, dict)
- for keystr, valstr in project_default_options.items():
- # Ths is complicated by the fact that a string can have two meanings:
- #
- # default_options: 'foo=bar'
- #
- # can be either
- #
- # A) a system option in which case the subproject is None
- # B) a project option, in which case the subproject is '' (this method is only called from top level)
- #
- # The key parsing function can not handle the difference between the two
- # and defaults to A.
- if isinstance(keystr, str):
- key = OptionKey.from_string(keystr)
- else:
- key = keystr
+ for key, valstr in project_default_options.items():
# Due to backwards compatibility we ignore build-machine options
# when building natively.
if not self.is_cross and key.is_for_build():
continue
if key.subproject:
+ # do apply project() default_options for subprojects here, because
+ # they have low priority
self.pending_options[key] = valstr
- elif key in self.options:
- self.set_option(key, valstr, first_invocation)
else:
- # Setting a project option with default_options.
- # Argubly this should be a hard error, the default
+ # Setting a project option with default_options
+ # should arguably be a hard error; the default
# value of project option should be set in the option
# file, not in the project call.
- proj_key = key.as_root()
- if self.is_project_option(proj_key):
- self.set_option(proj_key, valstr)
- else:
- self.pending_options[key] = valstr
- assert isinstance(cmd_line_options, dict)
- for keystr, valstr in cmd_line_options.items():
- if isinstance(keystr, str):
- key = OptionKey.from_string(keystr)
- else:
- key = keystr
+ self.set_option_maybe_root(key, valstr, True)
+
+ # ignore subprojects for now for machine file and command line
+ # options; they are applied later
+ for key, valstr in machine_file_options.items():
# Due to backwards compatibility we ignore all build-machine options
# when building natively.
if not self.is_cross and key.is_for_build():
continue
- if key.subproject:
- self.pending_options[key] = valstr
- elif key in self.options:
- self.set_option(key, valstr, True)
- else:
- proj_key = key.as_root()
- if proj_key in self.options:
- self.set_option(proj_key, valstr, True)
- else:
- self.pending_options[key] = valstr
+ if not key.subproject:
+ self.set_option_maybe_root(key, valstr, True)
+ for key, valstr in cmd_line_options.items():
+ # Due to backwards compatibility we ignore all build-machine options
+ # when building natively.
+ if not self.is_cross and key.is_for_build():
+ continue
+ if not key.subproject:
+ self.set_option_maybe_root(key, valstr, True)
- def validate_cmd_line_options(self, cmd_line_options: OptionStringLikeDict) -> None:
+ def accept_as_pending_option(self, key: OptionKey, known_subprojects: T.Optional[T.Container[str]] = None,
+ first_invocation: bool = False) -> bool:
+ # Fail on unknown options that we can know must exist at this point in time.
+ # Subproject and compiler options are resolved later.
+ #
+ # Some base options (sanitizers etc) might get added later.
+ # Permitting them all is not strictly correct.
+ if key.subproject:
+ if known_subprojects is None or key.subproject not in known_subprojects:
+ return True
+ if self.is_compiler_option(key):
+ return True
+ if first_invocation and self.is_backend_option(key):
+ return True
+ return (self.is_base_option(key) and
+ key.evolve(subproject=None, machine=MachineChoice.HOST) in COMPILER_BASE_OPTIONS)
+
+ def validate_cmd_line_options(self, cmd_line_options: OptionDict) -> None:
unknown_options = []
- for keystr, valstr in cmd_line_options.items():
- if isinstance(keystr, str):
- key = OptionKey.from_string(keystr)
- else:
- key = keystr
- # Fail on unknown options that we can know must exist at this point in time.
- # Subproject and compiler options are resolved later.
- #
- # Some base options (sanitizers etc) might get added later.
- # Permitting them all is not strictly correct.
- if key.subproject is None and not self.is_compiler_option(key) and not self.is_base_option(key) and \
- key in self.pending_options:
+ for key, valstr in cmd_line_options.items():
+ if key in self.pending_options and not self.accept_as_pending_option(key):
unknown_options.append(f'"{key}"')
if unknown_options:
keys = ', '.join(unknown_options)
raise MesonException(f'Unknown options: {keys}')
- def hacky_mchackface_back_to_list(self, optdict: T.Dict[str, str]) -> T.List[str]:
- if isinstance(optdict, dict):
- return [f'{k}={v}' for k, v in optdict.items()]
- return optdict
-
def initialize_from_subproject_call(self,
subproject: str,
- spcall_default_options: T.Union[T.List[str], OptionStringLikeDict],
- project_default_options: T.Union[T.List[str], OptionStringLikeDict],
- cmd_line_options: T.Union[T.List[str], OptionStringLikeDict]) -> None:
- is_first_invocation = True
- spcall_default_options = self.hacky_mchackface_back_to_list(spcall_default_options) # type: ignore [arg-type]
- project_default_options = self.hacky_mchackface_back_to_list(project_default_options) # type: ignore [arg-type]
- if isinstance(spcall_default_options, str):
- spcall_default_options = [spcall_default_options]
- for o in itertools.chain(project_default_options, spcall_default_options):
- keystr, valstr = o.split('=', 1)
- key = OptionKey.from_string(keystr)
- assert key.subproject is None
- key = key.evolve(subproject=subproject)
- # 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_option(key, valstr, is_first_invocation)
- else:
- self.pending_options.pop(key, None)
- aug_str = f'{subproject}:{keystr}'
- self.augments[aug_str] = valstr
- # Check for pending options
- assert isinstance(cmd_line_options, dict)
- for key, valstr in cmd_line_options.items(): # type: ignore [assignment]
- if not isinstance(key, OptionKey):
- key = OptionKey.from_string(key)
- if key.subproject != subproject:
- continue
+ spcall_default_options: OptionDict,
+ project_default_options: OptionDict,
+ cmd_line_options: OptionDict,
+ machine_file_options: OptionDict) -> None:
+ # pick up pending per-project settings from the toplevel project() invocation
+ options = {k: v for k, v in self.pending_options.items() if k.subproject == subproject}
+
+ # apply project() and subproject() default_options
+ for key, valstr in itertools.chain(project_default_options.items(), spcall_default_options.items()):
+ if key.subproject is None:
+ key = key.evolve(subproject=subproject)
+ elif key.subproject == subproject:
+ without_subp = key.evolve(subproject=None)
+ raise MesonException(f'subproject name not needed in default_options; use "{without_subp}" instead of "{key}"')
+ options[key] = valstr
+
+ # then global settings from machine file and command line
+ for key, valstr in itertools.chain(machine_file_options.items(), cmd_line_options.items()):
+ if key.subproject is None:
+ subp_key = key.evolve(subproject=subproject)
+ self.pending_options.pop(subp_key, None)
+ options.pop(subp_key, None)
+
+ # then finally per project augments from machine file and command line
+ for key, valstr in itertools.chain(machine_file_options.items(), cmd_line_options.items()):
+ if key.subproject == subproject:
+ options[key] = valstr
+
+ # merge everything that has been computed above, while giving self.augments priority
+ for key, valstr in options.items():
self.pending_options.pop(key, None)
- if key in self.options:
- self.set_option(key, valstr, is_first_invocation)
+ valstr = self.augments.pop(key, valstr)
+ if key in self.project_options:
+ self.set_option(key, valstr, True)
else:
- self.augments[str(key)] = valstr
+ self.augments[key] = valstr
def update_project_options(self, project_options: MutableKeyedOptionDictType, subproject: SubProject) -> None:
for key, value in project_options.items():