1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
|
# 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)
|