summaryrefslogtreecommitdiff
path: root/mesonbuild/options.py
diff options
context:
space:
mode:
Diffstat (limited to 'mesonbuild/options.py')
-rw-r--r--mesonbuild/options.py238
1 files changed, 236 insertions, 2 deletions
diff --git a/mesonbuild/options.py b/mesonbuild/options.py
index 631868628..f7195173f 100644
--- a/mesonbuild/options.py
+++ b/mesonbuild/options.py
@@ -3,12 +3,12 @@
from collections import OrderedDict
from itertools import chain
+from functools import total_ordering
import argparse
+import enum
from .mesonlib import (
HoldableObject,
- OptionKey,
- OptionType,
default_prefix,
default_datadir,
default_includedir,
@@ -21,6 +21,7 @@ from .mesonlib import (
default_sysconfdir,
MesonException,
listify_array_value,
+ MachineChoice,
)
from . import mlog
@@ -38,6 +39,239 @@ genvslitelist = ['vs2022']
buildtypelist = ['plain', 'debug', 'debugoptimized', 'release', 'minsize', 'custom']
+class OptionType(enum.IntEnum):
+
+ """Enum used to specify what kind of argument a thing is."""
+
+ BUILTIN = 0
+ BACKEND = 1
+ BASE = 2
+ COMPILER = 3
+ PROJECT = 4
+
+def _classify_argument(key: 'OptionKey') -> OptionType:
+ """Classify arguments into groups so we know which dict to assign them to."""
+
+ if key.name.startswith('b_'):
+ return OptionType.BASE
+ elif key.lang is not None:
+ return OptionType.COMPILER
+ elif key.name in _BUILTIN_NAMES or key.module:
+ return OptionType.BUILTIN
+ elif key.name.startswith('backend_'):
+ assert key.machine is MachineChoice.HOST, str(key)
+ return OptionType.BACKEND
+ else:
+ assert key.machine is MachineChoice.HOST, str(key)
+ return OptionType.PROJECT
+
+# This is copied from coredata. There is no way to share this, because this
+# is used in the OptionKey constructor, and the coredata lists are
+# OptionKeys...
+_BUILTIN_NAMES = {
+ 'prefix',
+ 'bindir',
+ 'datadir',
+ 'includedir',
+ 'infodir',
+ 'libdir',
+ 'licensedir',
+ 'libexecdir',
+ 'localedir',
+ 'localstatedir',
+ 'mandir',
+ 'sbindir',
+ 'sharedstatedir',
+ 'sysconfdir',
+ 'auto_features',
+ 'backend',
+ 'buildtype',
+ 'debug',
+ 'default_library',
+ 'errorlogs',
+ 'genvslite',
+ 'install_umask',
+ 'layout',
+ 'optimization',
+ 'prefer_static',
+ 'stdsplit',
+ 'strip',
+ 'unity',
+ 'unity_size',
+ 'warning_level',
+ 'werror',
+ 'wrap_mode',
+ 'force_fallback_for',
+ 'pkg_config_path',
+ 'cmake_prefix_path',
+ 'vsenv',
+}
+
+@total_ordering
+class OptionKey:
+
+ """Represents an option key in the various option dictionaries.
+
+ This provides a flexible, powerful way to map option names from their
+ external form (things like subproject:build.option) to something that
+ internally easier to reason about and produce.
+ """
+
+ __slots__ = ['name', 'subproject', 'machine', 'lang', '_hash', 'type', 'module']
+
+ name: str
+ subproject: str
+ machine: MachineChoice
+ lang: T.Optional[str]
+ _hash: int
+ type: OptionType
+ module: T.Optional[str]
+
+ def __init__(self, name: str, subproject: str = '',
+ machine: MachineChoice = MachineChoice.HOST,
+ lang: T.Optional[str] = None,
+ module: T.Optional[str] = None,
+ _type: T.Optional[OptionType] = None):
+ # the _type option to the constructor is kinda private. We want to be
+ # able tos ave the state and avoid the lookup function when
+ # pickling/unpickling, but we need to be able to calculate it when
+ # constructing a new OptionKey
+ object.__setattr__(self, 'name', name)
+ object.__setattr__(self, 'subproject', subproject)
+ object.__setattr__(self, 'machine', machine)
+ object.__setattr__(self, 'lang', lang)
+ object.__setattr__(self, 'module', module)
+ object.__setattr__(self, '_hash', hash((name, subproject, machine, lang, module)))
+ if _type is None:
+ _type = _classify_argument(self)
+ object.__setattr__(self, 'type', _type)
+
+ def __setattr__(self, key: str, value: T.Any) -> None:
+ raise AttributeError('OptionKey instances do not support mutation.')
+
+ def __getstate__(self) -> T.Dict[str, T.Any]:
+ return {
+ 'name': self.name,
+ 'subproject': self.subproject,
+ 'machine': self.machine,
+ 'lang': self.lang,
+ '_type': self.type,
+ 'module': self.module,
+ }
+
+ def __setstate__(self, state: T.Dict[str, T.Any]) -> None:
+ """De-serialize the state of a pickle.
+
+ This is very clever. __init__ is not a constructor, it's an
+ initializer, therefore it's safe to call more than once. We create a
+ state in the custom __getstate__ method, which is valid to pass
+ splatted to the initializer.
+ """
+ # Mypy doesn't like this, because it's so clever.
+ self.__init__(**state) # type: ignore
+
+ def __hash__(self) -> int:
+ return self._hash
+
+ def _to_tuple(self) -> T.Tuple[str, OptionType, str, str, MachineChoice, str]:
+ return (self.subproject, self.type, self.lang or '', self.module or '', self.machine, self.name)
+
+ def __eq__(self, other: object) -> bool:
+ if isinstance(other, OptionKey):
+ return self._to_tuple() == other._to_tuple()
+ return NotImplemented
+
+ def __lt__(self, other: object) -> bool:
+ if isinstance(other, OptionKey):
+ return self._to_tuple() < other._to_tuple()
+ return NotImplemented
+
+ def __str__(self) -> str:
+ out = self.name
+ if self.lang:
+ out = f'{self.lang}_{out}'
+ if self.machine is MachineChoice.BUILD:
+ out = f'build.{out}'
+ if self.module:
+ out = f'{self.module}.{out}'
+ if self.subproject:
+ out = f'{self.subproject}:{out}'
+ return out
+
+ def __repr__(self) -> str:
+ return f'OptionKey({self.name!r}, {self.subproject!r}, {self.machine!r}, {self.lang!r}, {self.module!r}, {self.type!r})'
+
+ @classmethod
+ def from_string(cls, raw: str) -> 'OptionKey':
+ """Parse the raw command line format into a three part tuple.
+
+ This takes strings like `mysubproject:build.myoption` and Creates an
+ OptionKey out of them.
+ """
+ try:
+ subproject, raw2 = raw.split(':')
+ except ValueError:
+ subproject, raw2 = '', raw
+
+ module = None
+ for_machine = MachineChoice.HOST
+ try:
+ prefix, raw3 = raw2.split('.')
+ if prefix == 'build':
+ for_machine = MachineChoice.BUILD
+ else:
+ module = prefix
+ except ValueError:
+ raw3 = raw2
+
+ from .compilers import all_languages
+ if any(raw3.startswith(f'{l}_') for l in all_languages):
+ lang, opt = raw3.split('_', 1)
+ else:
+ lang, opt = None, raw3
+ assert ':' not in opt
+ assert '.' not in opt
+
+ return cls(opt, subproject, for_machine, lang, module)
+
+ def evolve(self, name: T.Optional[str] = None, subproject: T.Optional[str] = None,
+ machine: T.Optional[MachineChoice] = None, lang: T.Optional[str] = '',
+ module: T.Optional[str] = '') -> 'OptionKey':
+ """Create a new copy of this key, but with altered members.
+
+ For example:
+ >>> a = OptionKey('foo', '', MachineChoice.Host)
+ >>> b = OptionKey('foo', 'bar', MachineChoice.Host)
+ >>> b == a.evolve(subproject='bar')
+ True
+ """
+ # We have to be a little clever with lang here, because lang is valid
+ # as None, for non-compiler options
+ return OptionKey(
+ name if name is not None else self.name,
+ subproject if subproject is not None else self.subproject,
+ machine if machine is not None else self.machine,
+ lang if lang != '' else self.lang,
+ module if module != '' else self.module
+ )
+
+ def as_root(self) -> 'OptionKey':
+ """Convenience method for key.evolve(subproject='')."""
+ return self.evolve(subproject='')
+
+ def as_build(self) -> 'OptionKey':
+ """Convenience method for key.evolve(machine=MachineChoice.BUILD)."""
+ return self.evolve(machine=MachineChoice.BUILD)
+
+ def as_host(self) -> 'OptionKey':
+ """Convenience method for key.evolve(machine=MachineChoice.HOST)."""
+ return self.evolve(machine=MachineChoice.HOST)
+
+ def is_project_hack_for_optionsview(self) -> bool:
+ """This method will be removed once we can delete OptionsView."""
+ return self.type is OptionType.PROJECT
+
+
class UserOption(T.Generic[_T], HoldableObject):
def __init__(self, name: str, description: str, choices: T.Optional[T.Union[str, T.List[_T]]],
yielding: bool,