| # SPDX-License-Identifier: Apache-2.0 | |
| # Copyright © 2021-2024 Intel Corporation | |
| """Accumulator for p1689r5 module dependencies. | |
| See: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p1689r5.html | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import re | |
| import textwrap | |
| import typing as T | |
| if T.TYPE_CHECKING: | |
| from .depscan import Description, Rule | |
| # The quoting logic has been copied from the ninjabackend to avoid having to | |
| # import half of Meson just to quote outputs, which is a performance problem | |
| _QUOTE_PAT = re.compile(r'[$ :\n]') | |
| def quote(text: str) -> str: | |
| # Fast path for when no quoting is necessary | |
| if not _QUOTE_PAT.search(text): | |
| return text | |
| if '\n' in text: | |
| errmsg = textwrap.dedent(f'''\ | |
| Ninja does not support newlines in rules. The content was: | |
| {text} | |
| Please report this error with a test case to the Meson bug tracker.''') | |
| raise RuntimeError(errmsg) | |
| return _QUOTE_PAT.sub(r'$\g<0>', text) | |
| _PROVIDER_CACHE: T.Dict[str, str] = {} | |
| def get_provider(rules: T.List[Rule], name: str) -> T.Optional[str]: | |
| """Get the object that a module from another Target provides | |
| We must rely on the object file here instead of the module itself, because | |
| the object rule is part of the generated build.ninja, while the module is | |
| only declared inside a dyndep. This creates for the dyndep generator to | |
| depend on previous dyndeps as order deps. Since the module | |
| interface file will be generated when the object is generated we can rely on | |
| that in proxy and simplify generation. | |
| :param rules: The list of rules to check | |
| :param name: The logical-name to look for | |
| :raises RuntimeError: If no provider can be found | |
| :return: The object file of the rule providing the module | |
| """ | |
| # Cache the result for performance reasons | |
| if name in _PROVIDER_CACHE: | |
| return _PROVIDER_CACHE[name] | |
| for r in rules: | |
| for p in r.get('provides', []): | |
| if p['logical-name'] == name: | |
| obj = r['primary-output'] | |
| _PROVIDER_CACHE[name] = obj | |
| return obj | |
| return None | |
| def process_rules(rules: T.List[Rule], | |
| extra_rules: T.List[Rule], | |
| ) -> T.Iterable[T.Tuple[str, T.Optional[T.List[str]], T.List[str]]]: | |
| """Process the rules for this Target | |
| :param rules: the rules for this target | |
| :param extra_rules: the rules for all of the targets this one links with, to use their provides | |
| :yield: A tuple of the output, the exported modules, and the consumed modules | |
| """ | |
| for rule in rules: | |
| prov: T.Optional[T.List[str]] = None | |
| req: T.List[str] = [] | |
| if 'provides' in rule: | |
| prov = [p['compiled-module-path'] for p in rule['provides']] | |
| if 'requires' in rule: | |
| for p in rule['requires']: | |
| modfile = p.get('compiled-module-path') | |
| if modfile is not None: | |
| req.append(modfile) | |
| else: | |
| # We can't error if this is not found because of compiler | |
| # provided modules | |
| found = get_provider(extra_rules, p['logical-name']) | |
| if found: | |
| req.append(found) | |
| yield rule['primary-output'], prov, req | |
| def formatter(files: T.Optional[T.List[str]]) -> str: | |
| if files: | |
| fmt = ' '.join(quote(f) for f in files) | |
| return f'| {fmt}' | |
| return '' | |
| def gen(outfile: str, desc: Description, extra_rules: T.List[Rule]) -> int: | |
| with open(outfile, 'w', encoding='utf-8') as f: | |
| f.write('ninja_dyndep_version = 1\n\n') | |
| for obj, provides, requires in process_rules(desc['rules'], extra_rules): | |
| ins = formatter(requires) | |
| out = formatter(provides) | |
| f.write(f'build {quote(obj)} {out}: dyndep {ins}\n\n') | |
| return 0 | |
| def run(args: T.List[str]) -> int: | |
| assert len(args) >= 2, 'got wrong number of arguments!' | |
| outfile, jsonfile, *jsondeps = args | |
| with open(jsonfile, 'r', encoding='utf-8') as f: | |
| desc: Description = json.load(f) | |
| # All rules, necessary for fulfilling across TU and target boundaries | |
| rules = desc['rules'].copy() | |
| for dep in jsondeps: | |
| with open(dep, encoding='utf-8') as f: | |
| d: Description = json.load(f) | |
| rules.extend(d['rules']) | |
| return gen(outfile, desc, rules) |