@h2loop/autosar 1.0.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +11 -1
  2. package/agents/autosar-integrator.md +45 -2
  3. package/commands/autosar-trace.md +2 -2
  4. package/package.json +4 -1
  5. package/plugins/index.js +55 -23
  6. package/python/ecuc_engine/__init__.py +1 -1
  7. package/python/ecuc_engine/add_signal.py +351 -0
  8. package/python/ecuc_engine/arxml_edit.py +387 -0
  9. package/python/ecuc_engine/audit.py +352 -50
  10. package/python/ecuc_engine/cli.py +125 -5
  11. package/python/ecuc_engine/codegen/__init__.py +233 -0
  12. package/python/ecuc_engine/codegen/binding.py +305 -0
  13. package/python/ecuc_engine/codegen/bindings/sip_standin.json +378 -0
  14. package/python/ecuc_engine/codegen/cdiff.py +395 -0
  15. package/python/ecuc_engine/codegen/cwriter.py +150 -0
  16. package/python/ecuc_engine/codegen/emit.py +463 -0
  17. package/python/ecuc_engine/codegen/handles.py +176 -0
  18. package/python/ecuc_engine/codegen/mcal/tc4d.json +60 -0
  19. package/python/ecuc_engine/codegen/model.py +264 -0
  20. package/python/ecuc_engine/codegen/modules/__init__.py +7 -0
  21. package/python/ecuc_engine/codegen/modules/com.py +145 -0
  22. package/python/ecuc_engine/codegen/modules/comstack.py +14 -0
  23. package/python/ecuc_engine/codegen/modules/dem.py +22 -0
  24. package/python/ecuc_engine/codegen/modules/mcal.py +170 -0
  25. package/python/ecuc_engine/codegen/modules/memstack.py +24 -0
  26. package/python/ecuc_engine/codegen/registry.py +208 -0
  27. package/python/ecuc_engine/codegen/schm_memmap.py +159 -0
  28. package/python/ecuc_engine/context.py +481 -0
  29. package/python/ecuc_engine/csrc.py +189 -0
  30. package/python/ecuc_engine/defs.py +30 -0
  31. package/python/ecuc_engine/ecucjson.py +75 -22
  32. package/python/ecuc_engine/find.py +164 -0
  33. package/python/ecuc_engine/gen.py +105 -0
  34. package/python/ecuc_engine/history.py +146 -0
  35. package/python/ecuc_engine/model.py +4 -1
  36. package/python/ecuc_engine/probe.py +229 -0
  37. package/python/ecuc_engine/rte_api.py +935 -0
  38. package/python/ecuc_engine/rules/__init__.py +5 -3
  39. package/python/ecuc_engine/rules/mem.py +84 -0
  40. package/python/ecuc_engine/rules/nvm.py +131 -0
  41. package/python/ecuc_engine/rules/pdur.py +87 -0
  42. package/python/ecuc_engine/rules/system.py +162 -0
  43. package/python/ecuc_engine/specids.py +163 -0
  44. package/python/ecuc_engine/sysmodel.py +152 -0
  45. package/python/ecuc_engine/validate.py +15 -2
  46. package/python/ecuc_engine/values.py +26 -0
  47. package/python/ecuc_engine/verify_rte.py +445 -0
  48. package/python/pyproject.toml +1 -1
  49. package/skills/autosar/SKILL.md +78 -6
  50. package/skills/autosar/references/codegen.md +58 -0
  51. package/skills/autosar/references/ecuc-model.md +4 -1
  52. package/skills/autosar/references/project-layout.md +10 -1
  53. package/skills/autosar/references/runnable-code.md +50 -0
@@ -1,4 +1,5 @@
1
- """CLI: python -m ecuc_engine [--root DIR] <info|read|edit|validate|diff> ... --json
1
+ """CLI: python -m ecuc_engine [--root DIR] <info|read|edit|validate|diff|audit|codegen|codegen-diff|rte-api|verify-rte|
2
+ build-context> ... --json
2
3
 
3
4
  Always prints one JSON object on stdout. {"ok": false, "error": ...} on failure (exit code 0 for
4
5
  handled errors, so callers parse stdout rather than the status).
@@ -49,16 +50,73 @@ def main(argv=None) -> int:
49
50
  v = sub.add_parser("validate")
50
51
  v.add_argument("--file", action="append")
51
52
  v.add_argument("--module")
53
+ v.add_argument("--check-generated", dest="check_generated", action="store_true", default=None,
54
+ help="regenerate in a scratch copy and report stale generated files (H2L-GEN-001)")
55
+ v.add_argument("--no-check-generated", dest="check_generated", action="store_false")
56
+ v.add_argument("--no-code", dest="code", action="store_false", help="skip the C-usage completeness check")
52
57
  v.add_argument("--max", type=int, default=0, help="cap the number of diagnostics returned (0 = all)")
53
58
  au = sub.add_parser("audit")
54
59
  au.add_argument("paths", nargs="*", help="C files/dirs to scan (default: audit.paths in .hydron/autosar.json, else Source/)")
55
60
  au.add_argument("--headers", action="append", help="generated header dirs for symbols and RTE macros (default Output/)")
61
+ asg = sub.add_parser("add-signal")
62
+ asg.add_argument("--name", required=True)
63
+ asg.add_argument("--ipdu", required=True)
64
+ asg.add_argument("--bit-size", dest="bit_size", type=int)
65
+ asg.add_argument("--type", dest="signal_type", default="UINT8")
66
+ asg.add_argument("--bit-position", dest="bit_position", type=int)
67
+ asg.add_argument("--endianness", default="LITTLE_ENDIAN")
68
+ asg.add_argument("--init", default="0")
69
+ asg.add_argument("--transfer-property", dest="transfer_property")
70
+ asg.add_argument("--dry-run", action="store_true")
71
+ ae = sub.add_parser("arxml-edit")
72
+ ae.add_argument("--ops", required=True)
73
+ ae.add_argument("--dry-run", action="store_true")
74
+ fd = sub.add_parser("find")
75
+ fd.add_argument("--module")
76
+ fd.add_argument("--container-def", dest="container_def")
77
+ fd.add_argument("--where", help='"Param=Value, Other>3" or a JSON object')
78
+ fd.add_argument("--ref-to", dest="ref_to")
79
+ fd.add_argument("--referenced-by", dest="referenced_by")
80
+ fd.add_argument("--limit", type=int, default=50)
81
+ cx = sub.add_parser("crosscheck")
82
+ cx.add_argument("module")
56
83
  so = sub.add_parser("signal-owners")
57
84
  so.add_argument("paths", nargs="+")
85
+ vs = sub.add_parser("value-sites", help="per AUTOSAR path: each value with its VALUE element file:line, dec/hex")
86
+ vs.add_argument("paths", nargs="+")
87
+ si = sub.add_parser("spec-ids", help="check requirement IDs against the configured spec text docs")
88
+ si.add_argument("ids", nargs="+")
89
+ si.add_argument("--docs", action="append", help="spec text file/dir/glob (default: specDocs / $AUTOSAR_SPEC_DOCS)")
58
90
  d = sub.add_parser("diff")
59
91
  d.add_argument("a")
60
92
  d.add_argument("b")
61
- for p in (r, e, v, d, au, so): # allow --json after the subcommand too
93
+ cg = sub.add_parser("codegen", help="deterministic config code generation")
94
+ cg.add_argument("--module", action="append", help="module to generate (repeatable; default: all registered ones configured)")
95
+ cg.add_argument("--out", help="output dir (default: .hydron/codegen/out scratch; 'project' = codegen.outDir, else Output)")
96
+ cg.add_argument("--no-write-back", action="store_true", help="do not write assigned handle IDs into the ECUC ARXML")
97
+ cg.add_argument("--binding", help="static-code binding name ('none' = generic tables only)")
98
+ cd = sub.add_parser("codegen-diff", help="semantic diff of generated C against a reference dir")
99
+ cd.add_argument("reference")
100
+ cd.add_argument("generated", nargs="?", help="default: the codegen scratch dir")
101
+ cd.add_argument("--module", action="append")
102
+ cd.add_argument("--max", type=int, default=50, help="max element mismatches listed per object")
103
+ ra = sub.add_parser("rte-api", help="expected RTE API per SW-C / runnable (SWS_RTE naming and signatures)")
104
+ ra.add_argument("--swc")
105
+ ra.add_argument("--runnable")
106
+ ra.add_argument("--no-header", action="store_true", help="skip matching against Rte_<Swc>.h")
107
+ vr = sub.add_parser("verify-rte", help="classify Rte_* calls in C per runnable: OK / NOT-ALLOWED / INVENTED")
108
+ vr.add_argument("paths", nargs="*", help="C files/dirs/globs (default: all in-house C)")
109
+ vr.add_argument("--diff-against", dest="diff_against", help="only calls on lines changed since this git ref")
110
+ vr.add_argument("--swc")
111
+ vr.add_argument("--runnable")
112
+ vr.add_argument("--no-check-ops", dest="check_ops", action="store_false")
113
+ bc = sub.add_parser("build-context", help="deterministic context for implementing/changing a runnable")
114
+ bc.add_argument("--swc", required=True)
115
+ bc.add_argument("--runnable")
116
+ bc.add_argument("--task", required=True, help="task text, @file, or - for stdin")
117
+ bc.add_argument("--budget-tokens", dest="budget_tokens", type=int, default=24000)
118
+ bc.add_argument("--no-history", dest="history", action="store_false")
119
+ for p in (r, e, v, d, au, so, vs, si, fd, cx, ae, asg, cg, cd, ra, vr, bc): # allow --json after the subcommand too
62
120
  p.add_argument("--json", action="store_true", help=argparse.SUPPRESS)
63
121
  a = ap.parse_args(argv)
64
122
  t0 = time.time()
@@ -74,7 +132,7 @@ def main(argv=None) -> int:
74
132
 
75
133
 
76
134
  def run(a) -> dict:
77
- if a.cmd == "edit":
135
+ if a.cmd in ("edit", "arxml-edit"):
78
136
  try:
79
137
  a.ops = _ops(a.ops)
80
138
  except (ValueError, OSError) as ex:
@@ -87,8 +145,70 @@ def run(a) -> dict:
87
145
  return diff(a.a, a.b, rel, root)
88
146
  except FileNotFoundError as ex:
89
147
  return {"error": str(ex)}
148
+ if a.cmd == "codegen-diff":
149
+ from .codegen import SCRATCH
150
+ from .codegen.cdiff import diff_dirs
151
+ root = os.path.abspath(a.root)
152
+ ab = lambda p: p if os.path.isabs(p) else os.path.join(root, p) # noqa: E731
153
+ ref, gen = ab(a.reference), ab(a.generated or SCRATCH)
154
+ for p in (ref, gen):
155
+ if not os.path.exists(p):
156
+ return {"error": f"{p} does not exist" + (" (run codegen first)" if p == gen else "")}
157
+ res = diff_dirs(ref, gen, modules=a.module, max_items=a.max)
158
+ res.update({"reference": ref, "generated": gen})
159
+ return res
160
+ if a.cmd == "rte-api":
161
+ from .rte_api import rte_api
162
+ return rte_api(os.path.abspath(a.root), swc=a.swc, runnable=a.runnable, header=not a.no_header)
163
+ if a.cmd == "verify-rte":
164
+ from .verify_rte import verify
165
+ return verify(os.path.abspath(a.root), files=a.paths or None, diff_against=a.diff_against, swc=a.swc,
166
+ runnable=a.runnable, check_ops=a.check_ops)
167
+ if a.cmd == "build-context":
168
+ from .context import build_context
169
+ task = a.task
170
+ if task == "-":
171
+ task = sys.stdin.read()
172
+ elif task.startswith("@"):
173
+ with open(task[1:]) as fh:
174
+ task = fh.read()
175
+ return build_context(os.path.abspath(a.root), a.swc, task, runnable=a.runnable,
176
+ budget_tokens=a.budget_tokens, history=a.history)
177
+ if a.cmd == "spec-ids":
178
+ from .specids import check_ids
179
+ return check_ids(os.path.abspath(a.root), a.ids, docs=a.docs)
90
180
  from .model import Project
91
- project = Project(a.root, config_files=a.config, def_files=a.defs, load_input=a.cmd in ("validate", "edit"))
181
+ project = Project(a.root, config_files=a.config, def_files=a.defs, load_input=a.cmd in ("validate", "edit", "find", "crosscheck", "signal-owners", "arxml-edit", "add-signal"))
182
+ if a.cmd == "add-signal":
183
+ from .add_signal import AddSignalError, add_signal
184
+ try:
185
+ return add_signal(project, a.name, a.ipdu, bit_size=a.bit_size, signal_type=a.signal_type,
186
+ bit_position=a.bit_position, endianness=a.endianness, init=a.init,
187
+ transfer_property=a.transfer_property, dry_run=a.dry_run)
188
+ except AddSignalError as ex:
189
+ return {"error": str(ex), "written": False}
190
+ if a.cmd == "arxml-edit":
191
+ from .arxml_edit import arxml_edit
192
+ return arxml_edit(project, a.ops, dry_run=a.dry_run)
193
+ if a.cmd == "find":
194
+ from .find import find
195
+ where = a.where
196
+ if where and where.strip().startswith("{"):
197
+ where = json.loads(where)
198
+ return find(project, module=a.module, container_def=a.container_def, where=where, ref_to=a.ref_to,
199
+ referenced_by=a.referenced_by, limit=a.limit)
200
+ if a.cmd == "crosscheck":
201
+ from .find import crosscheck
202
+ return crosscheck(project, a.module)
203
+ if a.cmd == "codegen":
204
+ from .codegen import generate
205
+ res = generate(project, modules=a.module, out_dir=a.out, write_back=not a.no_write_back, binding=a.binding)
206
+ if project.warnings:
207
+ res["warnings"] = project.warnings
208
+ return res
209
+ if a.cmd == "value-sites":
210
+ from .ecucjson import value_sites
211
+ return {"sites": value_sites(project, a.paths)}
92
212
  if a.cmd == "signal-owners":
93
213
  from .audit import signal_owners
94
214
  return {"owners": signal_owners(project, a.paths)}
@@ -115,7 +235,7 @@ def run(a) -> dict:
115
235
  return res
116
236
  if a.cmd == "validate":
117
237
  from .validate import validate
118
- res = validate(project, files=a.file, module=a.module)
238
+ res = validate(project, files=a.file, module=a.module, check_generated=a.check_generated, code=a.code)
119
239
  if a.max and len(res["diagnostics"]) > a.max:
120
240
  res["truncated"] = len(res["diagnostics"]) - a.max
121
241
  res["diagnostics"] = res["diagnostics"][: a.max]
@@ -0,0 +1,233 @@
1
+ """Deterministic ECUC -> C configuration code generation.
2
+
3
+ Same ECUC ARXML + same param defs -> byte-identical C. No LLM is involved: every line comes from
4
+ the param defs (generic emitter), a module generator (codegen/modules/, only for what the defs
5
+ cannot express), or a static-code binding (codegen/bindings/*.json, the vendor struct layout).
6
+
7
+ Entry point: generate(project, modules=None, out_dir=None, write_back=True).
8
+ Project options (.hydron/autosar.json, key "codegen"):
9
+ {"outDir": "Output", # the project's generated-code folder (used when out_dir="project")
10
+ "binding": "sip-standin", # force a static-code binding ("none" = generic tables only)
11
+ "mip": {"Eep": "Eep_21_LDExt"}, # module implementation prefixes (vendorId/vendorApiInfix)
12
+ "memmap": {"pragmas": {"CODE": {"start": "#pragma ...", "stop": "#pragma ..."}}},
13
+ "mcal": {"silicon": "tc4d", "pinMap": "mcal/pins.json"}}
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import hashlib
18
+ import json
19
+ import os
20
+ import re
21
+ import time
22
+ from typing import Optional
23
+
24
+ from .. import paths as P
25
+ from ..model import PROJECT_CONFIG, Project
26
+ from . import binding as B
27
+ from . import handles as H
28
+ from . import schm_memmap as S
29
+ from .emit import Ctx
30
+ from .model import CgModel
31
+ from .registry import generator_for, registered
32
+
33
+ SCRATCH = os.path.join(".hydron", "codegen", "out")
34
+
35
+
36
+ def project_options(root: str) -> dict:
37
+ f = os.path.join(root, PROJECT_CONFIG)
38
+ if not os.path.isfile(f):
39
+ return {}
40
+ try:
41
+ with open(f) as fh:
42
+ return json.load(fh).get("codegen", {}) or {}
43
+ except (OSError, ValueError):
44
+ return {}
45
+
46
+
47
+ def _scan_c(root: str, exclude: list) -> tuple:
48
+ """(headers basename->path, source files) of the project's static/in-house C, excluding generator output."""
49
+ from ..audit import is_generated
50
+ ex = [os.path.realpath(e) + os.sep for e in exclude if e]
51
+ w = P.walk(root, lambda n: n.endswith((".h", ".c")))
52
+ headers: dict = {}
53
+ sources: list = []
54
+ for f in w.files:
55
+ rf = os.path.realpath(f)
56
+ if any(rf.startswith(e) for e in ex):
57
+ continue
58
+ if f.endswith(".h"):
59
+ headers.setdefault(os.path.basename(f), f)
60
+ if not is_generated(f):
61
+ sources.append(f)
62
+ return headers, sources
63
+
64
+
65
+ def resolve_out(root: str, out_dir: Optional[str], opts: dict) -> str:
66
+ if not out_dir:
67
+ return os.path.join(root, SCRATCH)
68
+ if out_dir == "project":
69
+ out_dir = opts.get("outDir", "Output")
70
+ return out_dir if os.path.isabs(out_dir) else os.path.join(root, out_dir)
71
+
72
+
73
+ def generate(project: Project, modules: Optional[list] = None, out_dir: Optional[str] = None,
74
+ write_back: bool = True, binding: Optional[str] = None) -> dict:
75
+ t0 = time.time()
76
+ root = project.root
77
+ opts = project_options(root)
78
+ out = resolve_out(root, out_dir, opts)
79
+ binding = binding or opts.get("binding")
80
+ timings: dict = {}
81
+
82
+ cg = CgModel(project, opts.get("mip"))
83
+ configured = sorted(cg.modules)
84
+ if modules:
85
+ want = []
86
+ for m in modules:
87
+ hit = [x for x in configured if x.lower() == m.lower()]
88
+ if not hit:
89
+ return {"ok": False, "error": f"module {m!r} is not configured (configured: {', '.join(configured)})"}
90
+ want.append(hit[0])
91
+ else:
92
+ want = [m for m in registered() if m in cg.modules]
93
+ if not project.defs:
94
+ return {"ok": False, "error": "code generation needs the ECUC param defs (paramDefs in .hydron/autosar.json "
95
+ "or $AUTOSAR_PARAMDEFS): config classes and types come from them"}
96
+ timings["load_ms"] = int((time.time() - t0) * 1000)
97
+
98
+ # 1. handle IDs (TPS_ECUC_02107): assign, write back (or patch in memory only)
99
+ t1 = time.time()
100
+ pl = H.plan(cg, want)
101
+ if pl["errors"]:
102
+ return {"ok": False, "error": "handle-ID conflicts; fix the configuration first", "handleErrors": pl["errors"]}
103
+ wb = H.apply(project, pl["assign"], write=write_back)
104
+ if pl["assign"]:
105
+ cg = CgModel(project, opts.get("mip"))
106
+ # handle layout the static code indexes by (binding "handleLayout"), only for withAuto handle params
107
+ proj_out = resolve_out(root, "project", opts)
108
+ headers, sources = _scan_c(root, [out, proj_out, os.path.join(root, ".hydron")])
109
+ bindings = B.load_all(root)
110
+ lay = H.layout(cg, want, {m: B.select(bindings, m, headers, binding) for m in want})
111
+ if lay["errors"]:
112
+ return {"ok": False, "error": "handle IDs do not fit the static code's table layout", "handleErrors": lay["errors"]}
113
+ if lay["assign"]:
114
+ wb2 = H.apply(project, lay["assign"], write=write_back)
115
+ wb = {"written": wb["written"] or wb2["written"], "changes": wb["changes"] + wb2["changes"]}
116
+ pl["assign"] += lay["assign"]
117
+ cg = CgModel(project, opts.get("mip"))
118
+ pl["notes"] += lay["notes"]
119
+ timings["handles_ms"] = int((time.time() - t1) * 1000)
120
+
121
+ # 2. emit
122
+ t2 = time.time()
123
+ ctx = Ctx(cg=cg, headers=headers, bindings=bindings, binding_name=binding, release=cg.release(),
124
+ sources=sources)
125
+ ctx.options = opts
126
+ ctx.generating = set(want)
127
+ ctx.available = set(headers) | (set(os.listdir(proj_out)) if os.path.isdir(proj_out) else set())
128
+ ctx.out_headers = {f: os.path.join(proj_out, f) for f in sorted(os.listdir(proj_out))
129
+ if f.endswith(".h")} if os.path.isdir(proj_out) else {}
130
+ files: dict = {}
131
+ report: dict = {}
132
+ memmaps = []
133
+ notes = list(pl["notes"])
134
+ errors: dict = {}
135
+ outputs = []
136
+ for name in want:
137
+ mc = cg.module(name)
138
+ gen = generator_for(name)(ctx, mc)
139
+ mo = gen.run()
140
+ if mo.errors:
141
+ errors[name] = mo.errors
142
+ continue
143
+ outputs.append(mo)
144
+ rendered = mo.render()
145
+ n, text = S.schm_header(ctx, mo)
146
+ rendered[n] = text
147
+ mm = f"{mo.mip}_MemMap.h"
148
+ if mm in headers:
149
+ notes.append(f"{name}: project provides {P_rel(root, headers[mm])}; {mm} not generated")
150
+ else:
151
+ n, text, sections = S.module_memmap(ctx, mo, (opts.get("memmap") or {}).get("pragmas", {}))
152
+ rendered[n] = text
153
+ memmaps.append((mo.mip, sections))
154
+ files.update(rendered)
155
+ report[name] = {
156
+ "variant": mc.variant,
157
+ "binding": (mo.binding or {}).get("_binding"),
158
+ "files": sorted(rendered),
159
+ "symbols": len(mo.symbols),
160
+ "notes": mo.notes,
161
+ }
162
+ if memmaps and "MemMap.h" not in headers:
163
+ files["MemMap.h"] = S.aggregate_memmap(ctx, memmaps)
164
+ elif memmaps:
165
+ notes.append(f"project MemMap.h ({P_rel(root, headers['MemMap.h'])}) kept; generated code includes "
166
+ "<Mip>_MemMap.h (SWS_MemMap_00032)")
167
+ timings["emit_ms"] = int((time.time() - t2) * 1000)
168
+ if errors:
169
+ return {"ok": False, "error": "the configuration cannot be represented by the static code's binding; "
170
+ "nothing was written", "moduleErrors": errors, "notes": notes,
171
+ "handles": {"assigned": pl["assign"], "writtenBack": wb["written"], "changes": wb["changes"]}}
172
+ notes += dangling_symbols(ctx, outputs)
173
+
174
+ # 3. write
175
+ os.makedirs(out, exist_ok=True)
176
+ manifest = []
177
+ for name in sorted(files):
178
+ data = files[name].encode("utf-8")
179
+ path = os.path.join(out, name)
180
+ with open(path, "wb") as fh:
181
+ fh.write(data)
182
+ manifest.append({"file": name, "bytes": len(data), "sha256": hashlib.sha256(data).hexdigest()[:16]})
183
+ std = _std_types_release(headers)
184
+ if std and std[:2] != ctx.release[:2]:
185
+ notes.append(f"Std_Types.h in the project is AUTOSAR {std[0]}.{std[1]}.x but the configuration is "
186
+ f"{ctx.release[0]}.{ctx.release[1]}.x (schema); the release check against <Mip>.h may fail")
187
+ timings["total_ms"] = int((time.time() - t0) * 1000)
188
+ return {"ok": True, "outDir": out, "modules": report, "files": manifest,
189
+ "handles": {"assigned": pl["assign"], "writtenBack": wb["written"], "changes": wb["changes"]},
190
+ "notes": notes, "timings": timings, "release": ".".join(map(str, ctx.release))}
191
+
192
+
193
+ def dangling_symbols(ctx, outputs: list) -> list:
194
+ """Symbolic names (<Mip>Conf_<Def>_<Name>) that the project's C uses but the configuration no longer has.
195
+ The old generated header may still define them, so the stale use only shows once this output replaces it."""
196
+ defined = {s for mo in outputs for s, _, _ in mo.symbols}
197
+ rx = re.compile(r"\b(" + "|".join(sorted(re.escape(mo.mip) for mo in outputs)) + r")Conf_\w+") if outputs else None
198
+ out = []
199
+ if rx is None:
200
+ return out
201
+ seen: dict = {}
202
+ for f in ctx.sources:
203
+ try:
204
+ with open(f, errors="replace") as fh:
205
+ text = fh.read()
206
+ except OSError:
207
+ continue
208
+ for m in rx.finditer(text):
209
+ if m.group(0) not in defined:
210
+ seen.setdefault(m.group(0), ctx.cg.p.rel(f))
211
+ for sym in sorted(seen):
212
+ out.append(f"{seen[sym]} uses {sym}, which the configuration does not define (container deleted or "
213
+ "renamed?); the build will fail on it")
214
+ return out
215
+
216
+
217
+ def P_rel(root: str, f: str) -> str:
218
+ r = os.path.relpath(f, root)
219
+ return f if r.startswith("..") else r
220
+
221
+
222
+ def _std_types_release(headers: dict) -> Optional[tuple]:
223
+ f = headers.get("Std_Types.h")
224
+ if not f:
225
+ return None
226
+ try:
227
+ with open(f, errors="replace") as fh:
228
+ t = fh.read()
229
+ except OSError:
230
+ return None
231
+ m1 = re.search(r"STD_TYPES_AR_RELEASE_MAJOR_VERSION\s+\(?(\d+)", t)
232
+ m2 = re.search(r"STD_TYPES_AR_RELEASE_MINOR_VERSION\s+\(?(\d+)", t)
233
+ return (int(m1.group(1)), int(m2.group(1))) if m1 and m2 else None