@h2loop/autosar 0.1.2 → 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.
- package/README.md +4 -0
- package/agents/autosar-integrator.md +34 -2
- package/package.json +4 -1
- package/plugins/index.js +53 -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 +372 -69
- package/python/ecuc_engine/cli.py +119 -10
- package/python/ecuc_engine/codegen/__init__.py +185 -0
- package/python/ecuc_engine/codegen/binding.py +210 -0
- package/python/ecuc_engine/codegen/bindings/sip_standin.json +111 -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 +395 -0
- package/python/ecuc_engine/codegen/handles.py +89 -0
- package/python/ecuc_engine/codegen/mcal/tc4d.json +60 -0
- package/python/ecuc_engine/codegen/model.py +259 -0
- package/python/ecuc_engine/codegen/modules/__init__.py +7 -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 +133 -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/diff.py +13 -5
- package/python/ecuc_engine/find.py +162 -0
- package/python/ecuc_engine/gen.py +105 -0
- package/python/ecuc_engine/history.py +146 -0
- package/python/ecuc_engine/model.py +33 -29
- package/python/ecuc_engine/paths.py +221 -0
- 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/sysmodel.py +152 -0
- package/python/ecuc_engine/validate.py +52 -3
- package/python/ecuc_engine/verify_rte.py +445 -0
- package/python/pyproject.toml +1 -1
- package/skills/autosar/SKILL.md +57 -1
- package/skills/autosar/references/codegen.md +30 -0
- package/skills/autosar/references/project-layout.md +11 -2
- package/skills/autosar/references/runnable-code.md +50 -0
|
@@ -17,6 +17,8 @@ Rules:
|
|
|
17
17
|
ECUC-REF-UNRESOLVED foreign reference target not in any loaded file (warning)
|
|
18
18
|
ECUC-DUP-NAME two containers with the same AUTOSAR path
|
|
19
19
|
ECUC-OBSOLETE value for an obsolete def (warning)
|
|
20
|
+
H2L-FILTER-NOMATCH a files/module filter matched nothing that was loaded (error, never a silent "clean")
|
|
21
|
+
H2L-DISCOVERY discovery notes: hidden dir included, symlink cycle skipped, walk cap (info)
|
|
20
22
|
Hand-written semantic rules (ecuc_engine/rules, ids H2L-*) run afterwards, with or without defs.
|
|
21
23
|
"""
|
|
22
24
|
from __future__ import annotations
|
|
@@ -24,6 +26,7 @@ from __future__ import annotations
|
|
|
24
26
|
from typing import Optional
|
|
25
27
|
|
|
26
28
|
from . import defs as D
|
|
29
|
+
from . import paths
|
|
27
30
|
from .model import Project
|
|
28
31
|
from .values import check_value, expected_value_tag
|
|
29
32
|
|
|
@@ -61,7 +64,8 @@ def _resolve_def(defs: D.DefIndex, ref: Optional[str]) -> Optional[D.DefNode]:
|
|
|
61
64
|
return None
|
|
62
65
|
|
|
63
66
|
|
|
64
|
-
def validate(project: Project, files: Optional[list] = None, module: Optional[str] = None
|
|
67
|
+
def validate(project: Project, files: Optional[list] = None, module: Optional[str] = None,
|
|
68
|
+
check_generated: Optional[bool] = None, code: bool = True) -> dict:
|
|
65
69
|
defs = project.defs
|
|
66
70
|
dg = Diags(project)
|
|
67
71
|
warned_modules = set()
|
|
@@ -208,11 +212,50 @@ def validate(project: Project, files: Optional[list] = None, module: Optional[st
|
|
|
208
212
|
|
|
209
213
|
from .rules import run_rules
|
|
210
214
|
dg.items.extend(run_rules(project))
|
|
215
|
+
from .probe import load_cfg
|
|
216
|
+
cfg = load_cfg(project.root)
|
|
217
|
+
if code: # configuration completeness seen from the C code: handles used but not configured
|
|
218
|
+
from .audit import audit
|
|
219
|
+
for f in audit(project).get("findings", []):
|
|
220
|
+
if f["rule"] == "H2L-API-002":
|
|
221
|
+
dg.items.append({"file": f["file"], "line": f["line"], "path": "", "rule": f["rule"],
|
|
222
|
+
"severity": f["severity"], "message": f"{f['call']}: {f['message']} [{f['cite']}]",
|
|
223
|
+
"defRef": None})
|
|
224
|
+
from . import gen
|
|
225
|
+
if check_generated if check_generated is not None else gen.default_on(project, cfg):
|
|
226
|
+
dg.items.extend(gen.check(project, cfg))
|
|
211
227
|
|
|
212
228
|
items = dg.items
|
|
229
|
+
loaded = sorted(project.files)
|
|
230
|
+
by_file: dict = {}
|
|
231
|
+
for m in project.modules:
|
|
232
|
+
by_file.setdefault(project.rel(m.file), set()).add(m.name)
|
|
233
|
+
sample = ", ".join(project.rel(f) for f in loaded[:8]) + (f", … (+{len(loaded) - 8})" if len(loaded) > 8 else "")
|
|
234
|
+
scope: dict = {"root": project.root, "configFiles": len(loaded), "paramDefFiles": len(project.sources.defs),
|
|
235
|
+
"inputFiles": len(project.sources.input)}
|
|
236
|
+
nomatch = []
|
|
213
237
|
if files:
|
|
214
|
-
|
|
215
|
-
|
|
238
|
+
matched, empty = paths.select(project.root, files, loaded)
|
|
239
|
+
for raw in empty:
|
|
240
|
+
nomatch.append(_nomatch(project, f"files filter {raw!r} matched none of the {len(loaded)} loaded config "
|
|
241
|
+
f"file(s) [{sample or 'none'}]; NOTHING was validated for it. Pass a file, "
|
|
242
|
+
f"directory or glob relative to {project.root}, or omit 'files'."))
|
|
243
|
+
want = {project.rel(f) for f in matched}
|
|
244
|
+
items = [i for i in items if i["file"] in want or not i["file"].lower().endswith(".arxml")]
|
|
245
|
+
scope["filter"] = {"files": list(files), "matched": sorted(want)[:200], "matchedCount": len(want)}
|
|
246
|
+
if module:
|
|
247
|
+
names = {m.name for m in project.modules} | {(m.def_ref or "").rsplit("/", 1)[-1] for m in project.modules}
|
|
248
|
+
if module not in names:
|
|
249
|
+
nomatch.append(_nomatch(project, f"module filter {module!r} matches no loaded module "
|
|
250
|
+
f"({', '.join(sorted(names - {''})) or 'none loaded'}); NOTHING was validated."))
|
|
251
|
+
scope["module"] = module
|
|
252
|
+
info = [{"file": "", "line": 0, "path": "", "rule": "H2L-DISCOVERY", "severity": "info", "message": n, "defRef": None}
|
|
253
|
+
for n in project.sources.notes]
|
|
254
|
+
items = nomatch + items + info
|
|
255
|
+
shown = sorted(by_file.items())
|
|
256
|
+
scope["files"] = [{"file": f, "modules": sorted(ms)} for f, ms in shown[:100]]
|
|
257
|
+
if len(shown) > 100:
|
|
258
|
+
scope["filesTruncated"] = len(shown) - 100
|
|
216
259
|
items.sort(key=lambda i: (i["file"], i["line"], SEV_ORDER.get(i["severity"], 9), i["rule"]))
|
|
217
260
|
summary: dict = {}
|
|
218
261
|
for i in items:
|
|
@@ -223,5 +266,11 @@ def validate(project: Project, files: Optional[list] = None, module: Optional[st
|
|
|
223
266
|
"defsLoaded": len(defs.modules),
|
|
224
267
|
"modules": sorted({m.name for m in project.modules}),
|
|
225
268
|
"summary": summary,
|
|
269
|
+
"scope": scope,
|
|
226
270
|
"diagnostics": items,
|
|
227
271
|
}
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def _nomatch(project: Project, msg: str) -> dict:
|
|
275
|
+
return {"file": "", "line": 0, "path": project.root, "rule": "H2L-FILTER-NOMATCH", "severity": "error",
|
|
276
|
+
"message": msg, "defRef": None}
|
|
@@ -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]
|
package/python/pyproject.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "ecuc-engine"
|
|
3
|
-
version = "
|
|
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.
|
package/skills/autosar/SKILL.md
CHANGED
|
@@ -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
|
-
|
|
|
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,30 @@ 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
|
+
|
|
128
|
+
## Scoping and empty results
|
|
129
|
+
- Path arguments (`ecuc_validate files`, `autosar_audit paths`, `arxml_diff a/b`, `.hydron/autosar.json`
|
|
130
|
+
globs) take files, directories (recursive) or globs (`**/Com*.arxml`), relative to the project root or
|
|
131
|
+
absolute. Project layouts vary: never assume `Config/`, `Input/` or `Source/` exist — the tools find
|
|
132
|
+
ARXML and C anywhere and classify by content.
|
|
133
|
+
- A filter that matches nothing is an ERROR (`H2L-FILTER-NOMATCH`), never "clean". Prefer validating
|
|
134
|
+
unfiltered, then narrowing.
|
|
135
|
+
- **If validate reports nothing on a project you expect to have issues, run it unfiltered and check the
|
|
136
|
+
loaded-file list** printed at the end of the output (files and modules actually validated).
|
|
137
|
+
|
|
82
138
|
## Reading diagnostics
|
|
83
139
|
`file:line SEVERITY RULE path — message [def DEFINITION-REF]`. Rules: ECUC-MISSING-PARAM,
|
|
84
140
|
ECUC-MULT-CONTAINER/-PARAM, ECUC-CHOICE, ECUC-RANGE, ECUC-ENUM, ECUC-TYPE, ECUC-STRING,
|