@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.
- package/README.md +11 -1
- package/agents/autosar-integrator.md +45 -2
- package/commands/autosar-trace.md +2 -2
- package/package.json +4 -1
- package/plugins/index.js +55 -23
- package/python/ecuc_engine/__init__.py +1 -1
- package/python/ecuc_engine/add_signal.py +351 -0
- package/python/ecuc_engine/arxml_edit.py +387 -0
- package/python/ecuc_engine/audit.py +352 -50
- package/python/ecuc_engine/cli.py +125 -5
- package/python/ecuc_engine/codegen/__init__.py +233 -0
- package/python/ecuc_engine/codegen/binding.py +305 -0
- package/python/ecuc_engine/codegen/bindings/sip_standin.json +378 -0
- package/python/ecuc_engine/codegen/cdiff.py +395 -0
- package/python/ecuc_engine/codegen/cwriter.py +150 -0
- package/python/ecuc_engine/codegen/emit.py +463 -0
- package/python/ecuc_engine/codegen/handles.py +176 -0
- package/python/ecuc_engine/codegen/mcal/tc4d.json +60 -0
- package/python/ecuc_engine/codegen/model.py +264 -0
- package/python/ecuc_engine/codegen/modules/__init__.py +7 -0
- package/python/ecuc_engine/codegen/modules/com.py +145 -0
- package/python/ecuc_engine/codegen/modules/comstack.py +14 -0
- package/python/ecuc_engine/codegen/modules/dem.py +22 -0
- package/python/ecuc_engine/codegen/modules/mcal.py +170 -0
- package/python/ecuc_engine/codegen/modules/memstack.py +24 -0
- package/python/ecuc_engine/codegen/registry.py +208 -0
- package/python/ecuc_engine/codegen/schm_memmap.py +159 -0
- package/python/ecuc_engine/context.py +481 -0
- package/python/ecuc_engine/csrc.py +189 -0
- package/python/ecuc_engine/defs.py +30 -0
- package/python/ecuc_engine/ecucjson.py +75 -22
- package/python/ecuc_engine/find.py +164 -0
- package/python/ecuc_engine/gen.py +105 -0
- package/python/ecuc_engine/history.py +146 -0
- package/python/ecuc_engine/model.py +4 -1
- package/python/ecuc_engine/probe.py +229 -0
- package/python/ecuc_engine/rte_api.py +935 -0
- package/python/ecuc_engine/rules/__init__.py +5 -3
- package/python/ecuc_engine/rules/mem.py +84 -0
- package/python/ecuc_engine/rules/nvm.py +131 -0
- package/python/ecuc_engine/rules/pdur.py +87 -0
- package/python/ecuc_engine/rules/system.py +162 -0
- package/python/ecuc_engine/specids.py +163 -0
- package/python/ecuc_engine/sysmodel.py +152 -0
- package/python/ecuc_engine/validate.py +15 -2
- package/python/ecuc_engine/values.py +26 -0
- package/python/ecuc_engine/verify_rte.py +445 -0
- package/python/pyproject.toml +1 -1
- package/skills/autosar/SKILL.md +78 -6
- package/skills/autosar/references/codegen.md +58 -0
- package/skills/autosar/references/ecuc-model.md +4 -1
- package/skills/autosar/references/project-layout.md +10 -1
- 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
|
|
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
|
-
|
|
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
|
|
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
|