parent
4cc2e2171a
commit
41a445c228
7 changed files with 127 additions and 110 deletions
@ -1,15 +1,117 @@ |
|||||||
# SPDX-License-Identifier: Apache-2.0 |
# SPDX-License-Identifier: Apache-2.0 |
||||||
# Copyright 2013-2024 Contributors to the The Meson project |
# Copyright 2013-2024 Contributors to the The Meson project |
||||||
|
|
||||||
from . import mlog |
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 MachineFile: |
|
||||||
def __init__(self, fname): |
|
||||||
with open(fname, encoding='utf-8') as f: |
|
||||||
pass |
|
||||||
self.stuff = None |
|
||||||
|
|
||||||
class MachineFileStore: |
class MachineFileStore: |
||||||
def __init__(self, native_files, cross_files): |
def __init__(self, native_files, cross_files, source_dir): |
||||||
self.native = [MachineFile(x) for x in native_files] |
self.native = MachineFileParser(native_files if native_files is not None else [], source_dir).sections |
||||||
self.cross = [MachineFile(x) for x in cross_files] |
self.cross = MachineFileParser(cross_files if cross_files is not None else [], source_dir).sections |
||||||
|
Loading…
Reference in new issue