aboutsummaryrefslogtreecommitdiff
path: root/mesonbuild/machinefile.py
diff options
context:
space:
mode:
Diffstat (limited to 'mesonbuild/machinefile.py')
-rw-r--r--mesonbuild/machinefile.py117
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