diff options
Diffstat (limited to 'mesonbuild/machinefile.py')
-rw-r--r-- | mesonbuild/machinefile.py | 117 |
1 files changed, 117 insertions, 0 deletions
diff --git a/mesonbuild/machinefile.py b/mesonbuild/machinefile.py new file mode 100644 index 0000000..afeb4d0 --- /dev/null +++ b/mesonbuild/machinefile.py @@ -0,0 +1,117 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2013-2024 Contributors to the The Meson project + +import typing as T +import configparser +import os + +from . import mparser + +from .mesonlib import MesonException + +if T.TYPE_CHECKING: + from .compilers import Compiler + from .coredata import StrOrBytesPath + + CompilersDict = T.Dict[str, Compiler] + + +class CmdLineFileParser(configparser.ConfigParser): + def __init__(self) -> None: + # We don't want ':' as key delimiter, otherwise it would break when + # storing subproject options like "subproject:option=value" + super().__init__(delimiters=['='], interpolation=None) + + def read(self, filenames: T.Union['StrOrBytesPath', T.Iterable['StrOrBytesPath']], encoding: T.Optional[str] = 'utf-8') -> T.List[str]: + return super().read(filenames, encoding) + + def optionxform(self, optionstr: str) -> str: + # Don't call str.lower() on keys + return optionstr + + +class MachineFileParser(): + def __init__(self, filenames: T.List[str], sourcedir: str) -> None: + self.parser = CmdLineFileParser() + self.constants: T.Dict[str, T.Union[str, bool, int, T.List[str]]] = {'True': True, 'False': False} + self.sections: T.Dict[str, T.Dict[str, T.Union[str, bool, int, T.List[str]]]] = {} + + for fname in filenames: + try: + with open(fname, encoding='utf-8') as f: + content = f.read() + except UnicodeDecodeError as e: + raise MesonException(f'Malformed machine file {fname!r} failed to parse as unicode: {e}') + + content = content.replace('@GLOBAL_SOURCE_ROOT@', sourcedir) + content = content.replace('@DIRNAME@', os.path.dirname(fname)) + try: + self.parser.read_string(content, fname) + except configparser.Error as e: + raise MesonException(f'Malformed machine file: {e}') + + # Parse [constants] first so they can be used in other sections + if self.parser.has_section('constants'): + self.constants.update(self._parse_section('constants')) + + for s in self.parser.sections(): + if s == 'constants': + continue + self.sections[s] = self._parse_section(s) + + def _parse_section(self, s: str) -> T.Dict[str, T.Union[str, bool, int, T.List[str]]]: + self.scope = self.constants.copy() + section: T.Dict[str, T.Union[str, bool, int, T.List[str]]] = {} + for entry, value in self.parser.items(s): + if ' ' in entry or '\t' in entry or "'" in entry or '"' in entry: + raise MesonException(f'Malformed variable name {entry!r} in machine file.') + # Windows paths... + value = value.replace('\\', '\\\\') + try: + ast = mparser.Parser(value, 'machinefile').parse() + if not ast.lines: + raise MesonException('value cannot be empty') + res = self._evaluate_statement(ast.lines[0]) + except MesonException as e: + raise MesonException(f'Malformed value in machine file variable {entry!r}: {str(e)}.') + except KeyError as e: + raise MesonException(f'Undefined constant {e.args[0]!r} in machine file variable {entry!r}.') + section[entry] = res + self.scope[entry] = res + return section + + def _evaluate_statement(self, node: mparser.BaseNode) -> T.Union[str, bool, int, T.List[str]]: + if isinstance(node, (mparser.StringNode)): + return node.value + elif isinstance(node, mparser.BooleanNode): + return node.value + elif isinstance(node, mparser.NumberNode): + return node.value + elif isinstance(node, mparser.ParenthesizedNode): + return self._evaluate_statement(node.inner) + elif isinstance(node, mparser.ArrayNode): + # TODO: This is where recursive types would come in handy + return [self._evaluate_statement(arg) for arg in node.args.arguments] + elif isinstance(node, mparser.IdNode): + return self.scope[node.value] + elif isinstance(node, mparser.ArithmeticNode): + l = self._evaluate_statement(node.left) + r = self._evaluate_statement(node.right) + if node.operation == 'add': + if (isinstance(l, str) and isinstance(r, str)) or \ + (isinstance(l, list) and isinstance(r, list)): + return l + r + elif node.operation == 'div': + if isinstance(l, str) and isinstance(r, str): + return os.path.join(l, r) + raise MesonException('Unsupported node type') + +def parse_machine_files(filenames: T.List[str], sourcedir: str): + parser = MachineFileParser(filenames, sourcedir) + return parser.sections + + +class MachineFileStore: + def __init__(self, native_files, cross_files, source_dir): + self.native = MachineFileParser(native_files if native_files is not None else [], source_dir).sections + self.cross = MachineFileParser(cross_files if cross_files is not None else [], source_dir).sections |