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.
This commit is contained in:
parent
fa9c1a7a72
commit
983380d5ce
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue