aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDylan Baker <dylan@pnwbakers.com>2020-09-15 09:23:05 -0700
committerDylan Baker <dylan@pnwbakers.com>2021-01-04 12:15:41 -0800
commit983380d5ce9a33f2528202cc3d112c4109bf2c84 (patch)
treed386cdb4f0d1f89ca23ca10a813624f8b8f68e4b
parentfa9c1a7a727321b73da35b670d0fdae7fb4d494f (diff)
downloadmeson-983380d5ce9a33f2528202cc3d112c4109bf2c84.zip
meson-983380d5ce9a33f2528202cc3d112c4109bf2c84.tar.gz
meson-983380d5ce9a33f2528202cc3d112c4109bf2c84.tar.bz2
coredata: Add OptionKey type
This is a complex key that can store multiple bits of data in a single place. It can be generated from a command line formatted string, and it's str method returns it to that form. It's intentionally immutable, use the evolve() method to create variations of an existing key.
-rw-r--r--mesonbuild/coredata.py169
1 files changed, 169 insertions, 0 deletions
diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py
index 310174f..8b272e8 100644
--- a/mesonbuild/coredata.py
+++ b/mesonbuild/coredata.py
@@ -47,6 +47,175 @@ default_yielding = False
# Can't bind this near the class method it seems, sadly.
_T = T.TypeVar('_T')
+
+class ArgumentGroup(enum.Enum):
+
+ """Enum used to specify what kind of argument a thing is."""
+
+ BUILTIN = 0
+ BASE = 1
+ COMPILER = 2
+ USER = 3
+ BACKEND = 4
+
+
+def classify_argument(key: 'OptionKey') -> ArgumentGroup:
+ """Classify arguments into groups so we know which dict to assign them to."""
+
+ from .compilers import all_languages, base_options
+ all_builtins = set(BUILTIN_OPTIONS) | set(BUILTIN_OPTIONS_PER_MACHINE) | set(builtin_dir_noprefix_options)
+ lang_prefixes = tuple('{}_'.format(l) for l in all_languages)
+
+ if key.name in base_options:
+ assert key.machine is MachineChoice.HOST
+ return ArgumentGroup.BASE
+ elif key.name.startswith(lang_prefixes):
+ return ArgumentGroup.COMPILER
+ elif key.name in all_builtins:
+ # if for_machine is MachineChoice.BUILD:
+ # if option in BUILTIN_OPTIONS_PER_MACHINE:
+ # return ArgumentGroup.BUILTIN
+ # raise MesonException('Argument {} is not allowed per-machine'.format(option))
+ return ArgumentGroup.BUILTIN
+ elif key.name.startswith('backend_'):
+ return ArgumentGroup.BACKEND
+ else:
+ assert key.machine is MachineChoice.HOST
+ return ArgumentGroup.USER
+
+
+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']
+
+ name: str
+ subproject: str
+ machine: MachineChoice
+ lang: T.Optional[str]
+
+ def __init__(self, name: str, subproject: str = '',
+ machine: MachineChoice = MachineChoice.HOST,
+ lang: T.Optional[str] = None):
+ object.__setattr__(self, 'name', name)
+ object.__setattr__(self, 'subproject', subproject)
+ object.__setattr__(self, 'machine', machine)
+ object.__setattr__(self, 'lang', lang)
+ object.__setattr__(self, '_hash', hash((name, subproject, machine, lang)))
+
+ 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,
+ }
+
+ 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
+ unsplatted to the initializer.
+ """
+ self.__init__(**state)
+
+ def __hash__(self) -> int:
+ return self._hash
+
+ def __eq__(self, other: object) -> bool:
+ if isinstance(other, OptionKey):
+ return (
+ self.name == other.name and
+ self.subproject == other.subproject and
+ self.machine is other.machine and
+ self.lang == other.lang)
+ 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.subproject:
+ out = f'{self.subproject}:{out}'
+ return out
+
+ def __repr__(self) -> str:
+ return f'OptionKey({repr(self.name)}, {repr(self.subproject)}, {repr(self.machine)}, {repr(self.lang)})'
+
+ @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.
+ """
+ from .compilers import all_languages
+ if any(raw.startswith(f'{l}_') for l in all_languages):
+ lang, raw2 = raw.split('_', 1)
+ else:
+ lang, raw2 = None, raw
+
+ try:
+ subproject, raw3 = raw2.split(':')
+ except ValueError:
+ subproject, raw3 = '', raw2
+
+ if raw3.startswith('build.'):
+ opt = raw3.lstrip('build.')
+ for_machine = MachineChoice.BUILD
+ else:
+ opt = raw3
+ for_machine = MachineChoice.HOST
+ assert ':' not in opt
+ assert 'build.' not in opt
+
+ return cls(opt, subproject, for_machine, lang)
+
+ def evolve(self, name: T.Optional[str] = None, subproject: T.Optional[str] = None,
+ machine: T.Optional[MachineChoice] = None, lang: T.Optional[str] = '') -> 'OptionKey':
+ """Create a new copy of this key, but with alterted 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,
+ )
+
+ 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=MachinceChoice.BUILD)."""
+ return self.evolve(machine=MachineChoice.BUILD)
+
+ def as_host(self) -> 'OptionKey':
+ """Convenience method for key.evolve(machine=MachinceChoice.HOST)."""
+ return self.evolve(machine=MachineChoice.HOST)
+
+
class MesonVersionMismatchException(MesonException):
'''Build directory generated with Meson version is incompatible with current version'''
def __init__(self, old_version: str, current_version: str) -> None: