aboutsummaryrefslogtreecommitdiff
path: root/mesonbuild/options.py
diff options
context:
space:
mode:
Diffstat (limited to 'mesonbuild/options.py')
-rw-r--r--mesonbuild/options.py498
1 files changed, 250 insertions, 248 deletions
diff --git a/mesonbuild/options.py b/mesonbuild/options.py
index 62413b1..317acbd 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}.')
@@ -796,21 +805,19 @@ class OptionStore:
def __init__(self, is_cross: bool) -> None:
self.options: T.Dict['OptionKey', 'AnyOptionType'] = {}
+ self.subprojects: T.Set[str] = set()
self.project_options: T.Set[OptionKey] = set()
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] = {}
-
- def clear_pending(self) -> None:
- self.pending_options = {}
+ # Pending options are configuration dependent options that could be
+ # initialized later, such as compiler options
+ self.pending_options: OptionDict = {}
+ # Subproject options from toplevel project()
+ self.pending_subproject_options: OptionDict = {}
def ensure_and_validate_key(self, key: T.Union[OptionKey, str]) -> OptionKey:
if isinstance(key, str):
@@ -829,13 +836,19 @@ 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
def __len__(self) -> int:
return len(self.options)
- def get_value_object_for(self, key: 'T.Union[OptionKey, str]') -> AnyOptionType:
+ def get_key_and_value_object_for(self, key: 'T.Union[OptionKey, str]') -> T.Tuple[OptionKey, AnyOptionType]:
key = self.ensure_and_validate_key(key)
potential = self.options.get(key, None)
if self.is_project_option(key):
@@ -848,33 +861,41 @@ class OptionStore:
# Subproject is set to yield, but top level
# project does not have an option of the same
# name. Return the subproject option.
- return potential
+ return key, potential
# If parent object has different type, do not yield.
# This should probably be an error.
if type(parent_option) is type(potential):
- return parent_option
- return potential
+ return parent_key, parent_option
+ return key, potential
if potential is None:
raise KeyError(f'Tried to access nonexistant project option {key}.')
- return potential
+ return key, potential
else:
if potential is None:
parent_key = OptionKey(key.name, subproject=None, machine=key.machine)
if parent_key not in self.options:
raise KeyError(f'Tried to access nonexistant project parent option {parent_key}.')
- return self.options[parent_key]
- return potential
+ # This is a global option but it can still have per-project
+ # augment, so return the subproject key.
+ return key, self.options[parent_key]
+ return key, potential
+
+ def get_value_object_for(self, key: 'T.Union[OptionKey, str]') -> AnyOptionType:
+ return self.get_key_and_value_object_for(key)[1]
def get_value_object_and_value_for(self, key: OptionKey) -> T.Tuple[AnyOptionType, ElementaryOptionValues]:
assert isinstance(key, OptionKey)
- vobject = self.get_value_object_for(key)
+ _, vobject = self.get_key_and_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:
+ assert key.subproject is not None
+ computed_value = self.augments[key]
return (vobject, computed_value)
+ def option_has_value(self, key: OptionKey, value: ElementaryOptionValues) -> bool:
+ vobject, current_value = self.get_value_object_and_value_for(key)
+ return vobject.validate_value(value) == current_value
+
def get_value_for(self, name: 'T.Union[OptionKey, str]', subproject: T.Optional[str] = None) -> ElementaryOptionValues:
if isinstance(name, str):
key = OptionKey(name, subproject)
@@ -897,16 +918,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 +1006,11 @@ class OptionStore:
return value.as_posix()
def set_option(self, key: OptionKey, new_value: ElementaryOptionValues, first_invocation: bool = False) -> bool:
+ changed = False
+ 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)
@@ -994,84 +1020,107 @@ class OptionStore:
new_value = self.sanitize_dir_option_value(prefix, key, new_value)
try:
- opt = self.get_value_object_for(key)
+ actual_key, opt = self.get_key_and_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)
- return dirty
+ changed |= self.set_option(key.evolve(name=opt.deprecated), new_value, first_invocation)
- old_value = opt.value
- changed = opt.set_value(new_value)
+ new_value = opt.validate_value(new_value)
+ if key in self.options:
+ if actual_key.subproject == key.subproject:
+ old_value = opt.value
+ opt.set_value(new_value)
+ else:
+ # the key must have pointed to a yielding option;
+ # do not overwrite the global value in that case
+ return changed
+ else:
+ assert key.subproject is not None
+ old_value = self.augments.get(key, opt.value)
+ self.augments[key] = new_value
+ changed |= old_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')
optkey = key.evolve(name='optimization')
- self.options[dkey].set_value(debug)
- self.options[optkey].set_value(optimization)
+ self.set_option(dkey, debug, first_invocation)
+ self.set_option(optkey, optimization, first_invocation)
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_user_option(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 ''
+ #
+ # 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)
+
+ # could also be an augment...
+ global_option = o.evolve(subproject=None)
+ if o.subproject is not None and global_option in self.options:
+ 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
+ elif o.subproject is None:
+ o = o.as_root()
+ return self.set_option(o, new_value, first_invocation)
+ else:
+ raise MesonException(f'Unknown option: "{o}".')
def set_from_configure_command(self, D_args: T.List[str], U_args: T.List[str]) -> bool:
dirty = False
D_args = [] if D_args is None else D_args
- (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)
- 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 = True
- else:
- self.augments[keystr] = valstr
- dirty = True
- for delete in U_args:
- if delete in self.augments:
- del self.augments[delete]
+ for key, valstr in self.parse_D_arguments(D_args):
+ dirty |= self.set_user_option(key, valstr)
+ for keystr in U_args:
+ key = OptionKey.from_string(keystr)
+ if key in self.augments:
+ del self.augments[key]
dirty = True
return dirty
@@ -1193,64 +1242,33 @@ class OptionStore:
def is_module_option(self, key: OptionKey) -> bool:
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]]]:
- global_options = []
- project_options = []
- perproject_global_options = []
+ def parse_D_arguments(self, D: T.List[str]) -> T.List[T.Tuple[OptionKey, str]]:
+ options = []
for setval in D:
keystr, valstr = setval.split('=', 1)
key = OptionKey.from_string(keystr)
- valuetuple = (key, valstr)
- if self.is_project_option(key):
- project_options.append(valuetuple)
- 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)
- 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]]:
+ options.append((key, valstr))
+ return options
+
+ 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: T.Union[T.List[str], 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,145 +1299,129 @@ 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: T.Union[T.List[str], 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:
- self.pending_options[key] = valstr
- elif key in self.options:
- self.set_option(key, valstr, first_invocation)
+ # Subproject options from toplevel project() have low priority
+ # and will be processed when the subproject is found
+ self.pending_subproject_options[key] = valstr
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_user_option(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:
- # 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):
- raise MesonException(f'Unknown options: "{keystr}"')
- self.pending_options[key] = valstr
-
- 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
+ if not key.subproject:
+ self.set_user_option(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_user_option(key, valstr, True)
+
+ def accept_as_pending_option(self, key: OptionKey, first_invocation: bool = False) -> bool:
+ # Some base options (sanitizers etc) might get added later.
+ # Permitting them all is not strictly correct.
+ 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 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 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)
+ spcall_default_options: OptionDict,
+ project_default_options: OptionDict,
+ cmd_line_options: OptionDict,
+ machine_file_options: OptionDict) -> None:
+
+ options: OptionDict = {}
+
+ # project() default_options
+ for key, valstr in project_default_options.items():
+ if 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}"')
+
+ if key.subproject is None:
+ key = key.evolve(subproject=subproject)
+ options[key] = valstr
+
+ # augments from the toplevel project() default_options
+ for key, valstr in self.pending_subproject_options.items():
+ if key.subproject == subproject:
+ options[key] = valstr
+
+ # subproject() default_options
+ for key, valstr in spcall_default_options.items():
+ if 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}"')
+
+ if key.subproject is None:
+ key = key.evolve(subproject=subproject)
+ options[key] = valstr
+
+ # then global settings from machine file and command line
+ # **but not if they are toplevel project options**
+ for key, valstr in itertools.chain(machine_file_options.items(), cmd_line_options.items()):
+ if key.subproject is None and not self.is_project_option(key.as_root()):
+ subp_key = key.evolve(subproject=subproject)
+ self.pending_subproject_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():
if key.subproject != subproject:
+ if key.subproject in self.subprojects and not self.option_has_value(key, valstr):
+ mlog.warning('option {key} is set in subproject {subproject} but has already been processed')
+ continue
+
+ # Subproject options from project() will be processed when the subproject is found
+ self.pending_subproject_options[key] = valstr
continue
+
+ self.pending_subproject_options.pop(key, None)
self.pending_options.pop(key, None)
- if key in self.options:
- self.set_option(key, valstr, is_first_invocation)
- else:
- self.augments[str(key)] = valstr
+ if key not in self.augments:
+ self.set_user_option(key, valstr, True)
+
+ self.subprojects.add(subproject)
def update_project_options(self, project_options: MutableKeyedOptionDictType, subproject: SubProject) -> None:
for key, value in project_options.items():