aboutsummaryrefslogtreecommitdiff
path: root/mesonbuild/mesonlib.py
diff options
context:
space:
mode:
Diffstat (limited to 'mesonbuild/mesonlib.py')
-rw-r--r--mesonbuild/mesonlib.py272
1 files changed, 259 insertions, 13 deletions
diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py
index f73778e..2c1727b 100644
--- a/mesonbuild/mesonlib.py
+++ b/mesonbuild/mesonlib.py
@@ -14,13 +14,13 @@
"""A library of random helper functionality."""
from pathlib import Path
+import enum
import sys
import stat
import time
import platform, subprocess, operator, os, shlex, shutil, re
import collections
-from enum import IntEnum
-from functools import lru_cache, wraps
+from functools import lru_cache, wraps, total_ordering
from itertools import tee, filterfalse
from tempfile import TemporaryDirectory
import typing as T
@@ -31,7 +31,7 @@ from mesonbuild import mlog
if T.TYPE_CHECKING:
from .build import ConfigurationData
- from .coredata import OptionDictType, UserOption
+ from .coredata import KeyedOptionDictType, UserOption
from .compilers.compilers import CompilerType
from .interpreterbase import ObjectHolder
@@ -347,7 +347,7 @@ def classify_unity_sources(compilers: T.Iterable['CompilerType'], sources: T.Ite
return compsrclist
-class MachineChoice(IntEnum):
+class MachineChoice(enum.IntEnum):
"""Enum class representing one of the two abstract machine names used in
most places: the build, and host, machines.
@@ -1616,7 +1616,7 @@ def relative_to_if_possible(path: Path, root: Path, resolve: bool = False) -> Pa
except ValueError:
return path
-class LibType(IntEnum):
+class LibType(enum.IntEnum):
"""Enumeration for library types."""
@@ -1738,8 +1738,13 @@ def run_once(func: T.Callable[..., _T]) -> T.Callable[..., _T]:
class OptionProxy(T.Generic[_T]):
- def __init__(self, value: _T):
+ def __init__(self, value: _T, choices: T.Optional[T.List[str]] = None):
self.value = value
+ self.choices = choices
+
+ def set_value(self, v: _T) -> None:
+ # XXX: should this be an error
+ self.value = v
class OptionOverrideProxy(collections.abc.MutableMapping):
@@ -1751,27 +1756,27 @@ class OptionOverrideProxy(collections.abc.MutableMapping):
# TODO: the typing here could be made more explicit using a TypeDict from
# python 3.8 or typing_extensions
- def __init__(self, overrides: T.Dict[str, T.Any], *options: 'OptionDictType'):
+ def __init__(self, overrides: T.Dict['OptionKey', T.Any], *options: 'KeyedOptionDictType'):
self.overrides = overrides.copy()
- self.options = {} # type: T.Dict[str, UserOption]
+ self.options: T.Dict['OptionKey', UserOption] = {}
for o in options:
self.options.update(o)
- def __getitem__(self, key: str) -> T.Union['UserOption', OptionProxy]:
+ def __getitem__(self, key: 'OptionKey') -> T.Union['UserOption', OptionProxy]:
if key in self.options:
opt = self.options[key]
if key in self.overrides:
- return OptionProxy(opt.validate_value(self.overrides[key]))
+ return OptionProxy(opt.validate_value(self.overrides[key]), getattr(opt, 'choices', None))
return opt
raise KeyError('Option not found', key)
- def __setitem__(self, key: str, value: T.Union['UserOption', OptionProxy]) -> None:
+ def __setitem__(self, key: 'OptionKey', value: T.Union['UserOption', OptionProxy]) -> None:
self.overrides[key] = value.value
- def __delitem__(self, key: str) -> None:
+ def __delitem__(self, key: 'OptionKey') -> None:
del self.overrides[key]
- def __iter__(self) -> T.Iterator[str]:
+ def __iter__(self) -> T.Iterator['OptionKey']:
return iter(self.options)
def __len__(self) -> int:
@@ -1779,3 +1784,244 @@ class OptionOverrideProxy(collections.abc.MutableMapping):
def copy(self) -> 'OptionOverrideProxy':
return OptionOverrideProxy(self.overrides.copy(), self.options.copy())
+
+
+class OptionType(enum.Enum):
+
+ """Enum used to specify what kind of argument a thing is."""
+
+ BUILTIN = 0
+ BASE = 1
+ COMPILER = 2
+ PROJECT = 3
+ BACKEND = 4
+
+# 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',
+ 'libexecdir',
+ 'localedir',
+ 'localstatedir',
+ 'mandir',
+ 'sbindir',
+ 'sharedstatedir',
+ 'sysconfdir',
+ 'auto_features',
+ 'backend',
+ 'buildtype',
+ 'debug',
+ 'default_library',
+ 'errorlogs',
+ 'install_umask',
+ 'layout',
+ 'optimization',
+ 'stdsplit',
+ 'strip',
+ 'unity',
+ 'unity_size',
+ 'warning_level',
+ 'werror',
+ 'wrap_mode',
+ 'force_fallback_for',
+ 'pkg_config_path',
+ 'cmake_prefix_path',
+}
+
+
+def _classify_argument(key: 'OptionKey') -> OptionType:
+ """Classify arguments into groups so we know which dict to assign them to."""
+
+ if key.name.startswith('b_'):
+ assert key.machine is MachineChoice.HOST, str(key)
+ return OptionType.BASE
+ elif key.lang is not None:
+ return OptionType.COMPILER
+ elif key.name in _BUILTIN_NAMES:
+ 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
+
+
+@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']
+
+ name: str
+ subproject: str
+ machine: MachineChoice
+ lang: T.Optional[str]
+ _hash: int
+ type: OptionType
+
+ def __init__(self, name: str, subproject: str = '',
+ machine: MachineChoice = MachineChoice.HOST,
+ lang: 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, '_hash', hash((name, subproject, machine, lang)))
+ 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,
+ }
+
+ 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 __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 __lt__(self, other: object) -> bool:
+ if isinstance(other, OptionKey):
+ return (
+ self.name < other.name and
+ self.subproject < other.subproject and
+ self.machine < 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.
+ """
+
+ try:
+ subproject, raw2 = raw.split(':')
+ except ValueError:
+ subproject, raw2 = '', raw
+
+ if raw2.startswith('build.'):
+ raw3 = raw2.lstrip('build.')
+ for_machine = MachineChoice.BUILD
+ else:
+ raw3 = raw2
+ for_machine = MachineChoice.HOST
+
+ 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 '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)
+
+ def is_backend(self) -> bool:
+ """Convenience method to check if this is a backend option."""
+ return self.type is OptionType.BACKEND
+
+ def is_builtin(self) -> bool:
+ """Convenience method to check if this is a builtin option."""
+ return self.type is OptionType.BUILTIN
+
+ def is_compiler(self) -> bool:
+ """Convenience method to check if this is a builtin option."""
+ return self.type is OptionType.COMPILER
+
+ def is_project(self) -> bool:
+ """Convenience method to check if this is a project option."""
+ return self.type is OptionType.PROJECT
+
+ def is_base(self) -> bool:
+ """Convenience method to check if this is a base option."""
+ return self.type is OptionType.BASE \ No newline at end of file