aboutsummaryrefslogtreecommitdiff
path: root/mesonbuild/machinefile.py
diff options
context:
space:
mode:
authorJussi Pakkanen <jpakkane@gmail.com>2024-06-02 17:19:34 +0300
committerJussi Pakkanen <jpakkane@gmail.com>2024-06-02 17:43:50 +0300
commitcd52250c8d52eae3933e1215eef39c5dea68cf31 (patch)
tree64028b896754ba84cb99afafa4a3367d7fa36718 /mesonbuild/machinefile.py
parent4cc2e2171a7a6452da6ee0ec336ecb0e77f19791 (diff)
downloadmeson-machinerefactor.zip
meson-machinerefactor.tar.gz
meson-machinerefactor.tar.bz2
Extract native file parser to machinefile source file.machinerefactor
Diffstat (limited to 'mesonbuild/machinefile.py')
-rw-r--r--mesonbuild/machinefile.py120
1 files changed, 112 insertions, 8 deletions
diff --git a/mesonbuild/machinefile.py b/mesonbuild/machinefile.py
index 18162ef..436617c 100644
--- a/mesonbuild/machinefile.py
+++ b/mesonbuild/machinefile.py
@@ -1,15 +1,119 @@
# 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 mlog
-class MachineFile:
- def __init__(self, fname):
- with open(fname, encoding='utf-8') as f:
- pass
- self.stuff = None
+from . import mparser
+
+if T.TYPE_CHECKING:
+ from configparser import ConfigParser
+
+ from .compilers import Compiler
+ from .wrap.wrap import Resolver
+
+ 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 EnvironmentException(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 EnvironmentException(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 EnvironmentException(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 EnvironmentException('value cannot be empty')
+ res = self._evaluate_statement(ast.lines[0])
+ except MesonException as e:
+ raise EnvironmentException(f'Malformed value in machine file variable {entry!r}: {str(e)}.')
+ except KeyError as e:
+ raise EnvironmentException(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 EnvironmentException('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):
- self.native = [MachineFile(x) for x in native_files]
- self.cross = [MachineFile(x) for x in cross_files]
+ 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