blob: 0e89e6fd67154ad571a46a0179d96e1eaacac657 [file] [log] [blame] [edit]
# Copyright 2023 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""A parser for make.defaults files, from portage profiles.
Note: This is just a parser for a single make.defaults file, not a full profile
evaluator. To fully evaluate profile variables in the context of their parent
profiles, you'll want to use lib/build_query.py.
"""
import re
from typing import Dict, Iterator
# This regex is used to do lexical analysis on make.defaults files.
_lex_re = re.compile(
r"""
( (?P<comment>\#[^\n]*(?:\n|$))
| (?P<ifs>[ \t\n]+)
| (?P<dqstr>"(?:\\.|[^\\"])*")
| (?P<qstr>'(?:\\.|[^\\'])*')
| (?P<word>[^ \t\n\#"']+)
)""",
re.VERBOSE | re.DOTALL,
)
# This regex matches a variable substitution in make.defaults (either $var or
# ${var}).
_var_re = re.compile(
r"""
(?<!\\) # A backslash should not precede the dollar sign.
\$
(?:
\{([^}]+)\} # Either surrounded by braces.
| ([A-Za-z_]+) # Or not.
)""",
re.VERBOSE,
)
def _lex(contents: str) -> Iterator[str]:
"""Do lexical analysis on a make.defaults file.
This breaks the file contents down into a list of tokens that we can act
upon at a higher level in parse_make_defaults.
Args:
contents: The file contents of a make.defaults file.
Yields:
Strings, each representing a "word" to be acted upon.
"""
word = ""
for m in _lex_re.finditer(contents):
d = {k: v for k, v in m.groupdict().items() if v is not None}
typename, token = d.popitem()
if typename in ("comment", "ifs"):
if word:
yield word
word = ""
elif typename == "dqstr":
word += token[1:-1]
elif typename == "word":
word += token
elif typename == "qstr":
word += "".join(f"\\{x}" for x in token[1:-1])
if word:
yield word
def parse(contents: str) -> Dict[str, str]:
"""Parse the contents of a make.defaults file.
This parses a very limited subset of shell-like contents typically found in
make.defaults. See the Gentoo Package Manager Specification for the subset
we need to support:
https://projects.gentoo.org/pms/7/pms.html#x1-470005.2.4
Args:
contents: The file contents of a make.defaults file.
Returns:
A dictionary, mapping variable names to their values.
"""
variables = {}
def _var_sub_fn(m):
var_name = m.group(1) or m.group(2)
return variables.get(var_name, "")
for token in _lex(contents):
token = _var_re.sub(_var_sub_fn, token)
token = token.replace("\\", "")
var_name, sep, value = token.partition("=")
if sep:
variables[var_name] = value
return variables