@h2loop/autosar 1.0.1 → 1.1.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 (47) hide show
  1. package/README.md +4 -0
  2. package/agents/autosar-integrator.md +34 -2
  3. package/package.json +4 -1
  4. package/plugins/index.js +53 -23
  5. package/python/ecuc_engine/__init__.py +1 -1
  6. package/python/ecuc_engine/add_signal.py +351 -0
  7. package/python/ecuc_engine/arxml_edit.py +387 -0
  8. package/python/ecuc_engine/audit.py +328 -48
  9. package/python/ecuc_engine/cli.py +114 -5
  10. package/python/ecuc_engine/codegen/__init__.py +185 -0
  11. package/python/ecuc_engine/codegen/binding.py +210 -0
  12. package/python/ecuc_engine/codegen/bindings/sip_standin.json +111 -0
  13. package/python/ecuc_engine/codegen/cdiff.py +395 -0
  14. package/python/ecuc_engine/codegen/cwriter.py +150 -0
  15. package/python/ecuc_engine/codegen/emit.py +395 -0
  16. package/python/ecuc_engine/codegen/handles.py +89 -0
  17. package/python/ecuc_engine/codegen/mcal/tc4d.json +60 -0
  18. package/python/ecuc_engine/codegen/model.py +259 -0
  19. package/python/ecuc_engine/codegen/modules/__init__.py +7 -0
  20. package/python/ecuc_engine/codegen/modules/comstack.py +14 -0
  21. package/python/ecuc_engine/codegen/modules/dem.py +22 -0
  22. package/python/ecuc_engine/codegen/modules/mcal.py +170 -0
  23. package/python/ecuc_engine/codegen/modules/memstack.py +24 -0
  24. package/python/ecuc_engine/codegen/registry.py +133 -0
  25. package/python/ecuc_engine/codegen/schm_memmap.py +159 -0
  26. package/python/ecuc_engine/context.py +481 -0
  27. package/python/ecuc_engine/csrc.py +189 -0
  28. package/python/ecuc_engine/defs.py +30 -0
  29. package/python/ecuc_engine/find.py +162 -0
  30. package/python/ecuc_engine/gen.py +105 -0
  31. package/python/ecuc_engine/history.py +146 -0
  32. package/python/ecuc_engine/model.py +4 -1
  33. package/python/ecuc_engine/probe.py +229 -0
  34. package/python/ecuc_engine/rte_api.py +935 -0
  35. package/python/ecuc_engine/rules/__init__.py +5 -3
  36. package/python/ecuc_engine/rules/mem.py +84 -0
  37. package/python/ecuc_engine/rules/nvm.py +131 -0
  38. package/python/ecuc_engine/rules/pdur.py +87 -0
  39. package/python/ecuc_engine/rules/system.py +162 -0
  40. package/python/ecuc_engine/sysmodel.py +152 -0
  41. package/python/ecuc_engine/validate.py +15 -2
  42. package/python/ecuc_engine/verify_rte.py +445 -0
  43. package/python/pyproject.toml +1 -1
  44. package/skills/autosar/SKILL.md +47 -1
  45. package/skills/autosar/references/codegen.md +30 -0
  46. package/skills/autosar/references/project-layout.md +7 -1
  47. package/skills/autosar/references/runnable-code.md +50 -0
@@ -0,0 +1,445 @@
1
+ """Verify Rte_* calls in C against the SW-C's expected RTE API (rte_api), per runnable.
2
+
3
+ Each call (found with the audit's call scanner) is mapped to its enclosing function, then to every runnable whose
4
+ entry point (the RUNNABLE-ENTITY SYMBOL) reaches that function through the local call graph, and classified:
5
+ OK the runnable has the access point for the API
6
+ NOT-ALLOWED the API exists for the SW-C (port/element/IRV/... declared) but this runnable has no access point
7
+ INVENTED no such API for the SW-C (unknown port, element, runnable, wrong direction/semantics, ...)
8
+ For NOT-ALLOWED and INVENTED it proposes the exact ARXML change as arxml_edit ops (checked with a dry run).
9
+ """
10
+ from __future__ import annotations
11
+
12
+ import os
13
+ import re
14
+ import subprocess
15
+ from typing import Optional
16
+
17
+ from . import paths
18
+ from .csrc import CIndex
19
+ from .rte_api import (COLLECTION, PER_RUNNABLE, ApiEntry, ArIndex, SwcModel, build_all, find_swc, needed_role,
20
+ split_name)
21
+
22
+ PROVIDE_KINDS = {"Write", "Send", "Invalidate", "Feedback", "IWrite", "IWriteRef", "IInvalidate", "Switch", "SwitchAck",
23
+ "Trigger"}
24
+ REQUIRE_KINDS = {"Read", "Receive", "DRead", "IsUpdated", "IRead", "IReadRef", "IStatus", "Call", "Result", "Prm", "Mode"}
25
+ SR_KINDS = {"Write", "Send", "Invalidate", "Feedback", "IWrite", "IWriteRef", "IInvalidate", "Read", "Receive", "DRead",
26
+ "IsUpdated", "IRead", "IReadRef", "IStatus"}
27
+ KIND_ROLE = {"Write": "dataSendPoint", "Send": "dataSendPoint", "Read": "dataReceivePointByArgument",
28
+ "Receive": "dataReceivePointByArgument", "DRead": "dataReceivePointByValue", "IRead": "dataReadAccess",
29
+ "IReadRef": "dataReadAccess", "IWrite": "dataWriteAccess", "IWriteRef": "dataWriteAccess",
30
+ "Call": "serverCallPoint", "Mode": "modeAccessPoint", "Switch": "modeSwitchPoint", "Prm": "parameterAccess",
31
+ "IrvRead": "readLocalVariable", "IrvIRead": "readLocalVariable", "IrvIReadRef": "readLocalVariable",
32
+ "IrvWrite": "writtenLocalVariable", "IrvIWrite": "writtenLocalVariable",
33
+ "IrvIWriteRef": "writtenLocalVariable"}
34
+ PREFIX = {"dataSendPoint": "SEND", "dataReceivePointByArgument": "REC", "dataReceivePointByValue": "RBV",
35
+ "dataReadAccess": "IREAD", "dataWriteAccess": "IWRITE", "serverCallPoint": "SCP",
36
+ "modeAccessPoint": "MAP", "modeSwitchPoint": "MSP", "parameterAccess": "PA", "readLocalVariable": "IRVR",
37
+ "writtenLocalVariable": "IRVW"}
38
+ # Not SW-C APIs: RTE lifecycle / BSW-facing / callbacks (SWS_RTE 5.8, 5.9) — left to the integration code
39
+ LIFECYCLE = re.compile(r"^Rte_(Start|Stop|Init|StartTiming|PartitionTerminated|PartitionRestarting|RestartPartition|"
40
+ r"COMCbk|ComCbk|SetMetaDataItem|GetMetaDataItem|GetMetaDataLength|MainFunction|"
41
+ r"ComDataMainFunction|Ports|NPorts|Port)\w*$")
42
+
43
+
44
+ # ---------------------------------------------------------------- git helpers
45
+ def changed_lines(root: str, ref: str) -> tuple:
46
+ """{abs file: set(lines) | None (=whole file)} of C changes in the work tree since ``ref``; error text."""
47
+ try:
48
+ r = subprocess.run(["git", "-C", root, "diff", "-U0", "--no-color", "--no-ext-diff", ref, "--"],
49
+ capture_output=True, text=True, timeout=120)
50
+ except (OSError, subprocess.TimeoutExpired) as e:
51
+ return {}, f"git diff failed: {e}"
52
+ if r.returncode != 0:
53
+ return {}, f"git diff {ref} failed: {r.stderr.strip()[:300]}"
54
+ top = subprocess.run(["git", "-C", root, "rev-parse", "--show-toplevel"], capture_output=True, text=True).stdout.strip() or root
55
+ out: dict = {}
56
+ cur = None
57
+ for ln in r.stdout.splitlines():
58
+ if ln.startswith("+++ "):
59
+ p = ln[4:].strip()
60
+ cur = None if p == "/dev/null" else os.path.join(top, p[2:] if p.startswith("b/") else p)
61
+ if cur and paths.is_c(cur):
62
+ out.setdefault(cur, set())
63
+ else:
64
+ cur = None
65
+ elif ln.startswith("@@") and cur:
66
+ m = re.search(r"\+(\d+)(?:,(\d+))?", ln)
67
+ if m:
68
+ a, n = int(m.group(1)), int(m.group(2) or 1)
69
+ out[cur].update(range(a, a + n))
70
+ u = subprocess.run(["git", "-C", root, "ls-files", "--others", "--exclude-standard"], capture_output=True, text=True)
71
+ for p in u.stdout.splitlines():
72
+ if paths.is_c(p):
73
+ out[os.path.join(root, p)] = None
74
+ return out, None
75
+
76
+
77
+ # ---------------------------------------------------------------- suggestions
78
+ class Suggest:
79
+ def __init__(self, ix: ArIndex, models: list):
80
+ self.ix, self.models = ix, models
81
+
82
+ def _type_ref(self, element: Optional[str]) -> tuple:
83
+ """A type for a new data element: the type of a same-named element elsewhere, else the platform uint8."""
84
+ if element:
85
+ for n in self.ix.of_tag("VARIABLE-DATA-PROTOTYPE", "PARAMETER-DATA-PROTOTYPE"):
86
+ if n.short_name == element and n.child_text("TYPE-TREF"):
87
+ return n.child_text("TYPE-TREF"), False
88
+ for n in self.ix.of_tag("IMPLEMENTATION-DATA-TYPE"):
89
+ if n.short_name == "uint8":
90
+ return self.ix.path(n), True
91
+ idts = self.ix.of_tag("IMPLEMENTATION-DATA-TYPE")
92
+ return (self.ix.path(idts[0]) if idts else "/AUTOSAR_Platform/ImplementationDataTypes/uint8"), True
93
+
94
+ def _unique(self, parent: str, base: str) -> str:
95
+ name, i = base, 2
96
+ while self.ix.get(f"{parent}/{name}") is not None:
97
+ name, i = f"{base}_{i}", i + 1
98
+ return name
99
+
100
+ def access_op(self, m: SwcModel, runnable: str, role: str, port_path: Optional[str], target: str,
101
+ port_name: str = "", element: str = "") -> dict:
102
+ r = m.runnable(runnable)
103
+ coll = COLLECTION.get(role)
104
+ if role in ("readLocalVariable", "writtenLocalVariable"):
105
+ values = {"ACCESSED-VARIABLE": {"LOCAL-VARIABLE-REF": "->" + target}}
106
+ tag = "VARIABLE-ACCESS"
107
+ elif role == "serverCallPoint":
108
+ tag, values = "SYNCHRONOUS-SERVER-CALL-POINT", {"OPERATION-IREF": {"CONTEXT-R-PORT-REF": "->" + port_path,
109
+ "TARGET-REQUIRED-OPERATION-REF": "->" + target}}
110
+ elif role == "modeAccessPoint":
111
+ prov = any(p["path"] == port_path and p["direction"] == "P" for p in m.ports)
112
+ side = "P" if prov else "R"
113
+ tag, values = "MODE-ACCESS-POINT", {"MODE-GROUP-IREF": {f"{side}-MODE-GROUP-IN-ATOMIC-SWC-INSTANCE-REF": {
114
+ f"CONTEXT-{side}-PORT-REF": "->" + port_path, "TARGET-MODE-GROUP-REF": "->" + target}}}
115
+ elif role == "modeSwitchPoint":
116
+ tag, values = "MODE-SWITCH-POINT", {"MODE-GROUP-IREF": {"CONTEXT-P-PORT-REF": "->" + port_path,
117
+ "TARGET-MODE-GROUP-REF": "->" + target}}
118
+ elif role == "parameterAccess":
119
+ tag = "PARAMETER-ACCESS"
120
+ values = {"ACCESSED-PARAMETER": ({"AUTOSAR-PARAMETER-IREF": {"PORT-PROTOTYPE-REF": "->" + port_path,
121
+ "TARGET-DATA-PROTOTYPE-REF": "->" + target}}
122
+ if port_path else {"LOCAL-PARAMETER-REF": "->" + target})}
123
+ else:
124
+ tag, values = "VARIABLE-ACCESS", {"ACCESSED-VARIABLE": {"AUTOSAR-VARIABLE-IREF": {
125
+ "PORT-PROTOTYPE-REF": "->" + port_path, "TARGET-DATA-PROTOTYPE-REF": "->" + target}}}
126
+ op = {"op": "add", "parent_path": r.path, "element": tag, "collection": coll, "values": values}
127
+ if tag != "MODE-ACCESS-POINT": # ModeAccessPoint is not identifiable
128
+ base = "_".join(x for x in (PREFIX.get(role, "AP"), port_name, element or target.rsplit("/", 1)[-1]) if x)
129
+ op["short_name"] = self._unique(r.path, base)
130
+ return op
131
+
132
+ def for_entry(self, m: SwcModel, e: ApiEntry, runnable: str) -> tuple:
133
+ role = needed_role(e)
134
+ if e.api in ("Enter", "Exit"):
135
+ r = m.runnable(runnable)
136
+ return [{"op": "add", "parent_path": r.path, "element": "CAN-ENTER-EXCLUSIVE-AREA-REF",
137
+ "collection": "CAN-ENTER-EXCLUSIVE-AREA-REFS", "values": "->" + e.target}], None
138
+ if role not in COLLECTION:
139
+ return [], f"add a {role} for {e.target} to runnable {runnable}"
140
+ hint = None
141
+ if e.runnable and e.runnable != runnable: # Rte_IWrite_<other re>_...: per-runnable APIs name the caller
142
+ fixed = e.name.replace(f"_{e.runnable}_", f"_{runnable}_", 1)
143
+ hint = f"{e.name} belongs to runnable {e.runnable}; from {runnable} call {fixed} (after adding the access point)"
144
+ return [self.access_op(m, runnable, role, e.port_path, e.target, e.port or "", e.element or "")], hint
145
+
146
+ def interfaces_with(self, kind: str, element: str) -> list:
147
+ tag, coll = {"sr": ("SENDER-RECEIVER-INTERFACE", "DATA-ELEMENTS"), "cs": ("CLIENT-SERVER-INTERFACE", "OPERATIONS"),
148
+ "mode": ("MODE-SWITCH-INTERFACE", "MODE-GROUP"), "prm": ("PARAMETER-INTERFACE", "PARAMETERS")}[kind]
149
+ out = []
150
+ for itf in self.ix.of_tag(tag):
151
+ holder = itf.child(coll)
152
+ kids = (holder.children if holder is not None and holder.tag == coll and coll != "MODE-GROUP"
153
+ else [holder] if holder is not None else [])
154
+ if any(k.short_name == element for k in kids):
155
+ out.append(self.ix.path(itf))
156
+ return out
157
+
158
+ def users_of(self, itf: str) -> list:
159
+ return sorted({f"{m.name}.{p['name']}" for m in self.models for p in m.ports if p["interface"] == itf})
160
+
161
+ def for_invented(self, m: SwcModel, name: str, runnable: Optional[str]) -> tuple:
162
+ """-> (ops, hint). ops create the missing port / element / IRV and the access point."""
163
+ kind, rest = split_name(name)
164
+ if kind is None:
165
+ return [], "not an RTE API name; use one of the allowed APIs listed by autosar_build_context"
166
+ run = m.runnable(runnable) if runnable else None
167
+ if kind in PER_RUNNABLE:
168
+ rn = next((r.name for r in sorted(m.runnables, key=lambda r: -len(r.name)) if rest.startswith(r.name + "_")), None)
169
+ if rn is None:
170
+ return [], f"Rte_{kind} must name the calling runnable: Rte_{kind}_{run.name if run else '<re>'}_..."
171
+ if run is not None and rn != run.name:
172
+ fixed = f"Rte_{kind}_{run.name}_{rest[len(rn) + 1:]}"
173
+ return [], f"names runnable {rn} but is called from {run.name}; per-runnable APIs use the caller: {fixed}"
174
+ rest = rest[len(rn) + 1:]
175
+ if run is None:
176
+ return [], "call site is not reachable from a runnable of this SW-C; cannot place an access point"
177
+ if kind.startswith("Irv"):
178
+ if m.behavior is None:
179
+ return [], f"{m.name} has no SWC-INTERNAL-BEHAVIOR"
180
+ implicit = kind.startswith("IrvI")
181
+ coll = "IMPLICIT-INTER-RUNNABLE-VARIABLES" if implicit else "EXPLICIT-INTER-RUNNABLE-VARIABLES"
182
+ tref, assumed = self._type_ref(rest)
183
+ irv = f"{m.behavior}/{rest}"
184
+ ops = [{"op": "add", "parent_path": m.behavior, "element": "VARIABLE-DATA-PROTOTYPE", "collection": coll,
185
+ "short_name": rest, "values": {"TYPE-TREF": "->" + tref}},
186
+ self.access_op(m, run.name, KIND_ROLE[kind], None, irv, "", rest)]
187
+ return ops, ("type assumed: " + tref + "; adjust TYPE-TREF" if assumed else None)
188
+ if kind in ("Enter", "Exit"):
189
+ ea = f"{m.behavior}/{rest}"
190
+ return [{"op": "add", "parent_path": m.behavior, "element": "EXCLUSIVE-AREA", "collection": "EXCLUSIVE-AREAS",
191
+ "short_name": rest},
192
+ {"op": "add", "parent_path": run.path, "element": "CAN-ENTER-EXCLUSIVE-AREA-REF",
193
+ "collection": "CAN-ENTER-EXCLUSIVE-AREA-REFS", "values": "->" + ea}], None
194
+ if kind in ("Pim", "CData", "IrTrigger", "Trigger", "SwitchAck", "Feedback", "Invalidate", "IInvalidate",
195
+ "IsUpdated", "IStatus", "Result"):
196
+ return [], explain(m, name)
197
+ role = KIND_ROLE.get(kind)
198
+ ports = [p for p in m.ports if rest.startswith(p["name"] + "_")]
199
+ port = max(ports, key=lambda p: len(p["name"])) if ports else None
200
+ if port is not None:
201
+ el = rest[len(port["name"]) + 1:]
202
+ elem = next((e for e in port["elements"] if e["name"] == el), None)
203
+ want_dir = "P" if kind in PROVIDE_KINDS else "R"
204
+ if elem is not None: # element exists: direction or semantics do not fit
205
+ alt = {"Write": "Read", "Read": "Write", "Send": "Receive", "Receive": "Send", "IWrite": "IRead",
206
+ "IRead": "IWrite"}.get(kind)
207
+ if want_dir not in port["direction"]:
208
+ return [], (f"port {port['name']} is a {port['direction']}-port; Rte_{kind} needs a "
209
+ f"{want_dir}-port" + (f" (on this port use Rte_{alt}_{port['name']}_{el})" if alt else ""))
210
+ sem = elem.get("semantics")
211
+ if sem == "data" and kind in ("Send", "Receive"):
212
+ return [], f"{el} has data semantics: use Rte_{'Write' if kind == 'Send' else 'Read'}_{port['name']}_{el}"
213
+ if sem == "event" and kind in ("Write", "Read", "IWrite", "IRead", "DRead"):
214
+ return [], f"{el} is queued (event semantics): use Rte_{'Send' if 'Write' in kind else 'Receive'}_{port['name']}_{el}"
215
+ return [], explain(m, name)
216
+ ik = port["interfaceKind"]
217
+ want = "sr" if kind in SR_KINDS else "cs" if kind == "Call" else "mode" if kind in ("Mode", "Switch") else "prm"
218
+ have = {"SENDER-RECEIVER-INTERFACE": "sr", "NV-DATA-INTERFACE": "sr", "CLIENT-SERVER-INTERFACE": "cs",
219
+ "MODE-SWITCH-INTERFACE": "mode", "PARAMETER-INTERFACE": "prm"}.get(ik)
220
+ if want != have or want not in ("sr", "prm"):
221
+ return [], f"port {port['name']} ({ik}) has no element '{el}'; Rte_{kind} needs a {want} element"
222
+ ops = self.new_element(port["interface"], want, el, kind)
223
+ target = f"{port['interface']}/{el}"
224
+ ops.append(self.access_op(m, run.name, role, port["path"], target, port["name"], el))
225
+ users = self.users_of(port["interface"])
226
+ hint = (f"adds element {el} to interface {port['interface']}, which is also used by: "
227
+ f"{', '.join(u for u in users if not u.startswith(m.name + '.')) or 'no other port'}")
228
+ return ops, hint
229
+ # unknown port: Rte_<k>_<port>_<element>, split at the last underscore
230
+ if "_" not in rest:
231
+ return [], f"Rte_{kind}_<port>_<element> expected, got '{rest}'"
232
+ pname, el = rest.rsplit("_", 1)
233
+ want = "sr" if kind in SR_KINDS else "cs" if kind == "Call" else "mode" if kind in ("Mode", "Switch") else "prm"
234
+ if role is None:
235
+ return [], explain(m, name)
236
+ cands = self.interfaces_with(want, el)
237
+ ops = []
238
+ notes = []
239
+ if cands:
240
+ itf = sorted(cands, key=lambda i: (-len(self.users_of(i)), i))[0]
241
+ notes.append(f"reuses existing interface {itf} (has {el}; used by {', '.join(self.users_of(itf)) or 'no port'})")
242
+ elif want in ("sr", "prm"):
243
+ pkg = self.interface_package(m)
244
+ iname = self._unique(pkg, f"{pname}_If")
245
+ itf = f"{pkg}/{iname}"
246
+ tref, assumed = self._type_ref(el)
247
+ elem = self.element_values(want, el, tref, kind)
248
+ ops.append({"op": "add", "parent_path": pkg, "element": "SENDER-RECEIVER-INTERFACE" if want == "sr"
249
+ else "PARAMETER-INTERFACE", "short_name": iname,
250
+ "values": ({"IS-SERVICE": False, "DATA-ELEMENTS": {"VARIABLE-DATA-PROTOTYPE": elem}} if want == "sr"
251
+ else {"PARAMETERS": {"PARAMETER-DATA-PROTOTYPE": elem}})})
252
+ if assumed:
253
+ notes.append(f"new interface {itf}: element type assumed {tref}; adjust TYPE-TREF")
254
+ else:
255
+ return [], f"no {want} interface with element '{el}' exists; create the interface first"
256
+ ptag = "P-PORT-PROTOTYPE" if kind in PROVIDE_KINDS else "R-PORT-PROTOTYPE"
257
+ ops.append({"op": "add", "parent_path": m.path, "element": ptag, "collection": "PORTS", "short_name": pname,
258
+ "values": {("PROVIDED-INTERFACE-TREF" if ptag.startswith("P") else "REQUIRED-INTERFACE-TREF"): "->" + itf}})
259
+ ops.append(self.access_op(m, run.name, role, f"{m.path}/{pname}", f"{itf}/{el}", pname, el))
260
+ notes.append(f"split '{rest}' as port '{pname}' + element '{el}'; connect the new port in the composition "
261
+ "and map it to a signal if it is inter-ECU")
262
+ return ops, "; ".join(notes)
263
+
264
+ def element_values(self, want: str, el: str, tref: str, kind: str) -> dict:
265
+ v = {"SHORT-NAME": el, "TYPE-TREF": "->" + tref}
266
+ if kind in ("Send", "Receive"):
267
+ v["SW-DATA-DEF-PROPS"] = {"SW-DATA-DEF-PROPS-VARIANTS": {"SW-DATA-DEF-PROPS-CONDITIONAL": {
268
+ "SW-IMPL-POLICY": "QUEUED"}}}
269
+ return v
270
+
271
+ def new_element(self, itf: str, want: str, el: str, kind: str) -> list:
272
+ tref, _assumed = self._type_ref(el)
273
+ v = self.element_values(want, el, tref, kind)
274
+ sn = v.pop("SHORT-NAME")
275
+ return [{"op": "add", "parent_path": itf, "element": "VARIABLE-DATA-PROTOTYPE" if want == "sr"
276
+ else "PARAMETER-DATA-PROTOTYPE", "collection": "DATA-ELEMENTS" if want == "sr" else "PARAMETERS",
277
+ "short_name": sn, "values": v}]
278
+
279
+ def interface_package(self, m: SwcModel) -> str:
280
+ for p in m.ports:
281
+ if p.get("interface") and self.ix.get(p["interface"]) is not None:
282
+ return p["interface"].rsplit("/", 1)[0]
283
+ return m.path.rsplit("/", 1)[0]
284
+
285
+
286
+ def explain(m: SwcModel, name: str) -> str:
287
+ from .rte_api import explain_unknown
288
+ return explain_unknown(m, name)
289
+
290
+
291
+ # ---------------------------------------------------------------- verify
292
+ def _swc_by_include(models: list, includes: list) -> Optional[SwcModel]:
293
+ inc = {os.path.basename(i).lower() for i in includes}
294
+ hits = [m for m in models if f"rte_{m.name}.h".lower() in inc]
295
+ return hits[0] if len(hits) == 1 else None
296
+
297
+
298
+ def verify(root: str, files: Optional[list] = None, diff_against: Optional[str] = None, swc: Optional[str] = None,
299
+ runnable: Optional[str] = None, check_ops: bool = True, project=None) -> dict:
300
+ notes: list = []
301
+ ix = ArIndex(root)
302
+ models = build_all(root, ix)
303
+ if not models:
304
+ return {"error": "no SW-COMPONENT-TYPE found in the project ARXML; nothing to verify against"}
305
+ only = find_swc(models, swc) if swc else None
306
+ if swc and only is None:
307
+ return {"error": f"no SW-COMPONENT-TYPE {swc!r}; known: {', '.join(m.name for m in models)}"}
308
+ if only and runnable and only.runnable(runnable) is None:
309
+ return {"error": f"{only.name} has no runnable {runnable!r}; runnables: {', '.join(r.name for r in only.runnables)}"}
310
+ cx = CIndex(root)
311
+ lines_filter: dict = {}
312
+ if diff_against:
313
+ ch, err = changed_lines(root, diff_against)
314
+ if err:
315
+ return {"error": err}
316
+ scan = sorted(f for f in ch if f.lower().endswith(".c"))
317
+ lines_filter = ch
318
+ if not scan:
319
+ notes.append(f"no C changes since {diff_against}")
320
+ elif files:
321
+ scan = sorted(f for f in paths.expand(root, files, paths.is_c) if f.lower().endswith(".c"))
322
+ if not scan:
323
+ return {"error": f"paths {files} matched no .c file under {root}"}
324
+ else:
325
+ scan = [f for f in cx.cfiles if f not in cx.generated]
326
+ for f in scan: # files outside the default index (explicit generated files, new files)
327
+ if f not in cx.src:
328
+ from .csrc import functions
329
+ fs, raw = functions(f)
330
+ cx.src[f] = raw
331
+ cx.includes[f] = re.findall(r'^\s*#\s*include\s*[<"]([^>"]+)[>"]', raw, re.M)
332
+ for fn in fs:
333
+ cx.funcs.setdefault(fn.name, []).append(fn)
334
+
335
+ # function -> [(model, runnable)] by reachability from runnable entry points
336
+ reach: dict = {}
337
+ entry: dict = {}
338
+ for m in models:
339
+ for r in m.runnables:
340
+ fn = cx.get(r.symbol)
341
+ entry[(m.name, r.name)] = fn
342
+ if fn is None:
343
+ continue
344
+ for g in cx.reachable(fn):
345
+ reach.setdefault(id(g), []).append((m, r))
346
+ sugg = Suggest(ix, models)
347
+ findings, other = [], []
348
+ counts = {"OK": 0, "NOT-ALLOWED": 0, "INVENTED": 0}
349
+ for f in scan:
350
+ lf = lines_filter.get(f, None) if diff_against else None
351
+ for fn in sorted((g for lst in cx.funcs.values() for g in lst if g.file == f), key=lambda g: g.line):
352
+ owners = reach.get(id(fn), [])
353
+ for name, line, args in fn.rte:
354
+ if diff_against and lf is not None and line not in lf:
355
+ continue
356
+ cands = owners
357
+ if only is not None:
358
+ cands = [(m, r) for m, r in owners if m is only] or ([(only, None)] if not owners else [])
359
+ if not cands:
360
+ continue # reachable only from other SW-Cs
361
+ if runnable:
362
+ cands = [(m, r) for m, r in cands if r is not None and runnable in (r.name, r.symbol)]
363
+ if not cands:
364
+ continue
365
+ if not cands:
366
+ m = _swc_by_include(models, cx.includes.get(f, [])) or next(
367
+ (x for x in models if name in x.apis), None)
368
+ if m is None:
369
+ if LIFECYCLE.match(name):
370
+ other.append({"call": name, "at": f"{cx.rel(f)}:{line}", "function": fn.name,
371
+ "why": "RTE lifecycle/BSW API, not a SW-C port API"})
372
+ continue
373
+ if len(models) != 1:
374
+ findings.append(_finding(cx, f, line, fn, name, args, None, None, "INVENTED",
375
+ "no SW-C in the project declares this API, and the calling "
376
+ "function belongs to no SW-C (no runnable reaches it, no "
377
+ "Rte_<Swc>.h include)", None))
378
+ counts["INVENTED"] += 1
379
+ continue
380
+ m = models[0]
381
+ cands = [(m, None)]
382
+ for m, r in cands:
383
+ status, e, why = m.classify(name, r.name if r else None)
384
+ if r is None:
385
+ why += f"; {fn.name} is not reached from any runnable of {m.name}, checked at SW-C level"
386
+ d = _finding(cx, f, line, fn, name, args, m, r, status, why, e)
387
+ if status != "OK" and r is not None:
388
+ ops, hint = (sugg.for_entry(m, e, r.name) if e is not None else sugg.for_invented(m, name, r.name))
389
+ if ops:
390
+ d["arxml_edit"] = ops
391
+ if hint:
392
+ d["hint"] = hint
393
+ elif status == "INVENTED":
394
+ _ops, hint = sugg.for_invented(m, name, None)
395
+ if hint:
396
+ d["hint"] = hint
397
+ counts[status] += 1
398
+ findings.append(d)
399
+ if check_ops:
400
+ _check_ops(root, findings, project)
401
+ unmapped = [{"swc": k[0], "runnable": k[1], "symbol": next(r.symbol for m in models if m.name == k[0]
402
+ for r in m.runnables if r.name == k[1])}
403
+ for k, v in entry.items() if v is None and (only is None or k[0] == only.name)]
404
+ res = {"scanned": {"files": [cx.rel(f) for f in scan], "functions": sum(len(v) for v in cx.funcs.values()),
405
+ "calls": sum(counts.values())},
406
+ "counts": counts, "findings": findings,
407
+ "swcs": [m.name for m in ([only] if only else models)]}
408
+ if other:
409
+ res["notChecked"] = other
410
+ if unmapped:
411
+ res["runnablesWithoutCode"] = unmapped
412
+ if notes:
413
+ res["notes"] = notes
414
+ return res
415
+
416
+
417
+ def _finding(cx, f, line, fn, name, args, m, r, status, why, e) -> dict:
418
+ d = {"status": status, "call": f"{name}({', '.join(args)})", "api": name, "at": f"{cx.rel(f)}:{line}",
419
+ "function": fn.name, "swc": m.name if m else None, "runnable": r.name if r else None, "why": why}
420
+ if e is not None:
421
+ d["expected"] = e.signature
422
+ d["cite"] = e.cite
423
+ return d
424
+
425
+
426
+ def _check_ops(root: str, findings: list, project=None):
427
+ """Dry-run each finding's ops with arxml_edit so only ready-to-run suggestions are labelled as such."""
428
+ todo = [d for d in findings if d.get("arxml_edit")]
429
+ if not todo:
430
+ return
431
+ try:
432
+ from .arxml_edit import arxml_edit
433
+ from .model import Project
434
+ p = project or Project(root, load_input=True)
435
+ except Exception as ex: # noqa: BLE001
436
+ for d in todo:
437
+ d["opsChecked"] = f"not checked: {ex}"
438
+ return
439
+ seen: dict = {}
440
+ for d in todo[:50]:
441
+ key = repr(d["arxml_edit"])
442
+ if key not in seen:
443
+ r = arxml_edit(p, d["arxml_edit"], dry_run=True)
444
+ seen[key] = True if r.get("ok") else (r.get("error") or "") + (" " + "; ".join(r.get("problems", [])) if r.get("problems") else "")
445
+ d["opsChecked"] = seen[key]
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ecuc-engine"
3
- version = "1.0.1"
3
+ version = "1.2.0-pre"
4
4
  description = "Load, read, edit in place, validate and diff AUTOSAR ECUC ARXML (Hydron AUTOSAR pack)"
5
5
  requires-python = ">=3.10"
6
6
  # The core is stdlib-only; autosar-data adds a strict schema check of every edited file.
@@ -35,6 +35,10 @@ in `Output/` is derived from it; in-house code in `Source/` calls the generated
35
35
  |---|---|
36
36
  | What modules/files/defs exist | `ecuc_read` (no path) |
37
37
  | Values of a module/container | `ecuc_read path=… depth=…` |
38
+ | All containers matching a condition / referencing X / referenced by Y | `ecuc_find` |
39
+ | Cross-check one module against the extract, C types and memory | `ecuc_crosscheck module=…` |
40
+ | Edit System extract / SWC / interface XML | `arxml_edit` (DEST inferred, schema-ordered, refs checked) |
41
+ | Add a signal end to end (ECUC + extract + DBC) | `autosar_add_signal` |
38
42
  | What may be set/added here (types, ranges, literals, multiplicity) | `ecuc_read path=… schema=true`, or a def path `/AUTOSAR/EcucDefs/<Mod>/…` |
39
43
  | Change config | `ecuc_edit ops=[…]` (`dry_run` first for large edits) |
40
44
  | Check config | `ecuc_validate` |
@@ -42,14 +46,38 @@ in `Output/` is derived from it; in-house code in `Source/` calls the generated
42
46
  | Which C symbols does this config produce, who calls them / where does this symbol come from | `autosar_trace query=…` |
43
47
  | Refs in/out, children of an element in the ingested repo | `autosar_config_lookup path=…` |
44
48
  | Review C code that calls Com/NvM/Dem/PduR/RTE APIs | `autosar_audit` (first), then confirm with `autosar_trace` / `ecuc_read` |
45
- | Regenerate code | `autosar_generate` (configured command, asks permission) |
49
+ | Context for implementing / changing a runnable (allowed RTE API, code, callees, config, similar code/changes) | `autosar_build_context swc=… runnable=… task=…` |
50
+ | Check that C only calls the RTE APIs its runnable may call (OK / NOT-ALLOWED / INVENTED + ARXML fix ops) | `autosar_verify_rte` (`diff_against=<ref>` for a change) |
51
+ | Generate BSW config code ourselves (NvM/Fee/MemIf/Dem/PduR/CanIf/Port/Dio) | `autosar_codegen`, then `autosar_codegen_diff reference_dir=Output` (see `references/codegen.md`) |
52
+ | Regenerate code with the vendor tool | `autosar_generate` (configured command, asks permission) |
46
53
  | Compile/link | `autosar_build` |
47
54
  | Spec questions (SWS/TPS requirement text) | the project's ingested docs search tools |
48
55
 
56
+ ## Workflow (validation and fix tasks)
57
+ 1. `ecuc_validate` unfiltered (all rules). It is **necessary but not sufficient**.
58
+ 2. Make a todo checklist with one item per module present (Com, PduR, CanIf, EcuC, NvM/Fee/Ea, Dem, Os, Rte, …)
59
+ and run `ecuc_crosscheck` for each; read its NOT CHECKED notes and close those gaps by hand.
60
+ 3. `autosar_audit` for how the C code uses the configuration (handles, directions, widths, RAM blocks).
61
+ 4. Fix only with `ecuc_edit` / `arxml_edit` / `autosar_add_signal`; then `autosar_generate`, `autosar_build`, and
62
+ re-run `ecuc_validate` (+ `checkGenerated: true` to prove `Output/` is not stale).
63
+ 5. **Never conclude "no faults" from a single validate call**, and never finish with a failing validation: after
64
+ ANY edit run `ecuc_validate`; if it fails, fix it before answering.
65
+
49
66
  ## The loop
50
67
  `ecuc_read (schema) → ecuc_edit → ecuc_validate → autosar_generate → arxml_diff / inspect Output → autosar_build → autosar_trace (impact on Source/)`.
51
68
  Details and failure handling: `references/workflow.md`.
52
69
 
70
+ ## Implement or change a runnable (SW-C application code)
71
+ 1. `autosar_build_context swc=<Swc> runnable=<Runnable> task="<the task verbatim>"` FIRST. Section 3 lists the RTE APIs
72
+ the runnable is ALLOWED to call, with the exact declarations from `Rte_<Swc>.h` (SWS_RTE naming and signatures);
73
+ sections 4–6 hold its code, callees and the Com/NvM config behind its ports; 7–8 are fuzzy hints only.
74
+ 2. Write the code using **only** the allowed APIs and their exact signatures (value vs pointer per SWS_Rte_01017/01019).
75
+ Never invent an `Rte_*` name; never call an API that belongs to another runnable.
76
+ 3. `autosar_verify_rte runnable=<Runnable>` (or `diff_against=<ref>` for the whole change).
77
+ 4. On INVENTED / NOT-ALLOWED: rewrite with an allowed API; or, only when the behaviour truly needs new data, apply the
78
+ returned `arxml_edit` ops (access point / port / data element), run `autosar_generate`, and verify again.
79
+ 5. `autosar_build`. Never finish with a failing verify or build. Details: `references/runnable-code.md`.
80
+
53
81
  ## Integration review recipe (C code against the configuration)
54
82
  1. `autosar_audit` (scope with `paths` if asked about specific files). It resolves every BSW call's handle
55
83
  through the ECUC and reports raw numeric handles, unknown handles, direction mismatches, C-width vs
@@ -61,6 +89,12 @@ Details and failure handling: `references/workflow.md`.
61
89
  4. Report: file:line, the call, the configured facts (AUTOSAR paths + values), the spec ID, the fix.
62
90
 
63
91
  Facts that decide common review calls:
92
+ - A Com signal's layout must match the System extract it came from (ISignalToIPduMapping startPosition /
93
+ packingByteOrder, ISignal length) and an EcuC Pdu's PduLength the system I-PDU length (H2L-SYS-001/002).
94
+ - A PduR routing path forwards one I-PDU: source and destination must stand for the same system I-PDU (or the same
95
+ I-SIGNAL content) — a gateway destination carrying a different I-PDU is wrong (H2L-PDUR-001).
96
+ - NvMNvBlockLength is the NV *data* length and must equal sizeof(the RAM block object); the Fee/Ea block must hold it
97
+ (H2L-NVM-001, sizeof measured by compiling a probe with the project's compiler and flags).
64
98
  - **Direction lives on the I-PDU**, not the signal: a ComSignal is TX or RX through the `ComIPduDirection`
65
99
  of the ComIPdu that references it (`ComIPduSignalRef`). Sending an RX signal (or receiving a TX one)
66
100
  fails at runtime (ECUC_Com_00493, SWS_Com_00197/00198).
@@ -77,8 +111,20 @@ Facts that decide common review calls:
77
111
  - `references/symbolic-names.md` — the symbolic-name rule (TPS_ECUC_02108) that links config to generated `#define`s, handle IDs.
78
112
  - `references/config-classes.md` — pre-compile / link-time / post-build config classes, variants, post-build loadable/selectable.
79
113
  - `references/project-layout.md` — Config / Input / Output / Source / Build / SIP and `.hydron/autosar.json`.
114
+ - `references/codegen.md` — deterministic code generation, handle-ID write-back, bindings, reading the semantic diff, MCAL hook.
115
+ - `references/runnable-code.md` — RTE API tiers (allowed / generated / derivable), naming and signature rules, verify statuses and fixes, context sections and budget.
80
116
  - `references/workflow.md` — the edit → validate → generate → build → trace loop, diagnostics rules, and what to do when a step fails.
81
117
 
118
+ ## Impact questions
119
+ Include, besides the trace to code: the **storage capacity** check (H2L-MEM-001, NvM/Fee/Ea vs MemAcc/Mem area) and
120
+ **NvM stored-data compatibility** — a changed RAM block size or layout makes existing NV data unreadable unless the
121
+ block is migrated (NvMResistantToChangedSw, NvMStaticBlockIDCheck, a new block ID or a data-version field).
122
+
123
+ ## MCAL questions
124
+ Public AUTOSAR documents define the MCAL interfaces and parameter definitions; vendor-specific configuration details
125
+ (register mapping, clock trees, pin multiplexing) live in the silicon vendor's MCAL user manuals, often under NDA. When
126
+ the ingested docs do not cover a detail, say so and point to the vendor MCAL manual instead of guessing.
127
+
82
128
  ## Scoping and empty results
83
129
  - Path arguments (`ecuc_validate files`, `autosar_audit paths`, `arxml_diff a/b`, `.hydron/autosar.json`
84
130
  globs) take files, directories (recursive) or globs (`**/Com*.arxml`), relative to the project root or
@@ -0,0 +1,30 @@
1
+ # Deterministic config code generation (`autosar_codegen`, `autosar_codegen_diff`)
2
+
3
+ Generated C is never written by hand or by the model: run `autosar_codegen`, review with
4
+ `autosar_codegen_diff`, then `autosar_build`. Same ECUC + param defs -> byte-identical files.
5
+
6
+ ## What is generated, from where
7
+ | Output | Source |
8
+ |---|---|
9
+ | `<Mip>_Cfg.h` | pre-compile params of singleton containers as `<MIP>_<PARAM>` (booleans STD_ON/STD_OFF), `<MIP>_CFG_AR_RELEASE_*` (from the schema: AUTOSAR_00054 = R4.11.0), every symbolicNameValue param as `<Mip>Conf_<ContainerDef>_<Name>` (TPS_ECUC_02108) |
10
+ | `<Mip>_Cfg.c` / `_Lcfg.c` / `_PBcfg.c` | one const table per multiplicity-n container def, placed by the most dynamic config class of its fields for the module's IMPLEMENTATION-CONFIG-VARIANT |
11
+ | binding tables (e.g. `NvM_BlockDescriptorTable`, `PduR_Config`) | `codegen/bindings/*.json` (or `.hydron/codegen/bindings/`): the static code's struct layout, selected when its `detect` header is in the project |
12
+ | `SchM_<Mip>.h` | exclusive areas the static code uses (`SchM_Enter_<Mip>_<EA>` found in non-generated C) + main functions |
13
+ | `<Mip>_MemMap.h` | SWS_MemMap_00032 keyword file; pragmas from `codegen.memmap.pragmas` |
14
+
15
+ Handle IDs: missing symbolic values are assigned (lowest free >= def MIN, existing values kept, duplicates
16
+ are errors) and written back into the ARXML in place (TPS_ECUC_02107); `write_back=false` keeps them in memory.
17
+
18
+ Output goes to `.hydron/codegen/out` unless `out_dir` is given (`project` = `codegen.outDir`, default `Output`).
19
+
20
+ ## Reading a diff
21
+ - *missing*: in the reference only (e.g. a symbol whose ECUC container was deleted: stale Output, not a generator bug).
22
+ - *extra*: generated only (generic def-driven macros/tables the reference generator does not emit).
23
+ - *mismatch*: same name, different value. Typical causes: enum encoding (we use the def literal order unless the
24
+ binding maps it), the configured value verbatim (`&Ram` vs `Ram`).
25
+
26
+ ## MCAL
27
+ Port/Dio come from the standard R25-11 defs. Register-level output needs a silicon mapping
28
+ (`codegen.mcal.silicon`, e.g. `tc4d`) plus a project pin map (`codegen.mcal.pinMap`). The bundled TC4Dx file
29
+ has public user-manual data only (status `skeleton`); its `needsVendorData` lists what Infineon's VSMD/BSWMD
30
+ and MCAL static code must supply. Never invent register values.
@@ -23,10 +23,16 @@ Optional; pins what the tools load and how to generate/build:
23
23
  "config": ["Config/**/*.arxml"],
24
24
  "input": ["Input/**/*.arxml"],
25
25
  "generate": {"command": "<vendor-cli> -p Config/Ecu.dpa --generate {module}", "cwd": ".", "timeoutSec": 3600},
26
- "build": {"command": "make -C Build -j8 all", "timeoutSec": 3600}
26
+ "build": {"command": "make -C Build -j8 all", "timeoutSec": 3600},
27
+ "probe": {"cc": "tricore-elf-gcc", "flags": ["-I", "SIP/include", "-DCPU=TC4D"]},
28
+ "checkGenerated": false
27
29
  }
28
30
  ```
29
31
  Without it: param defs = every `.arxml` holding `ECUC-MODULE-DEF` (else `$AUTOSAR_PARAMDEFS`, a
30
32
  path list), config = every file holding `ECUC-MODULE-CONFIGURATION-VALUES`, input = all other
31
33
  `.arxml`. Use the param defs of the release and vendor the project actually targets; the vendor
32
34
  BSWMD defs take precedence for vendor-specific parameters.
35
+
36
+ `probe` sets the compiler/flags for the compile-only sizeof probe (H2L-NVM-001; default: the Makefile's CC/CFLAGS,
37
+ else host `cc` with the discovered header dirs). `checkGenerated` turns the stale-Output check (H2L-GEN-001, run in a
38
+ scratch copy) on/off; by default it is on when a `generate` command exists and the configuration is under 5 MB.