| # 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 |