@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
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"""Index of the non-ECUC ARXML (System / ECU extract, SWCs, BSWMD) for cross-checks.
|
|
2
|
+
|
|
3
|
+
One expat pass per file. Every identifiable (element with a SHORT-NAME) becomes an Ident with its tag,
|
|
4
|
+
file, line, the identifiable children, and the leaf values *of its own* (descendants that are not
|
|
5
|
+
inside a nested identifiable): e.g. an I-SIGNAL-TO-I-PDU-MAPPING gets {"I-SIGNAL-REF": [...],
|
|
6
|
+
"START-POSITION": ["16"], "PACKING-BYTE-ORDER": [...]}. References keep their DEST type.
|
|
7
|
+
Nothing is keyed by project-specific names; rules navigate by AUTOSAR metaclass and reference.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import xml.parsers.expat as expat
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from typing import Optional
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class Ident:
|
|
18
|
+
path: str
|
|
19
|
+
tag: str
|
|
20
|
+
file: str
|
|
21
|
+
line: int
|
|
22
|
+
values: dict = field(default_factory=dict) # leaf tag -> [text]
|
|
23
|
+
dests: dict = field(default_factory=dict) # ref tag -> [DEST]
|
|
24
|
+
children: list = field(default_factory=list) # child identifiable paths
|
|
25
|
+
parent: Optional[str] = None
|
|
26
|
+
|
|
27
|
+
def one(self, tag: str) -> Optional[str]:
|
|
28
|
+
v = self.values.get(tag)
|
|
29
|
+
return v[0] if v else None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class SystemIndex:
|
|
33
|
+
def __init__(self):
|
|
34
|
+
self.by_path: dict = {}
|
|
35
|
+
self.by_tag: dict = {}
|
|
36
|
+
self._refs_to: Optional[dict] = None
|
|
37
|
+
|
|
38
|
+
def add_file(self, path: str):
|
|
39
|
+
try:
|
|
40
|
+
with open(path, "rb") as fh:
|
|
41
|
+
data = fh.read()
|
|
42
|
+
except OSError:
|
|
43
|
+
return
|
|
44
|
+
p = expat.ParserCreate()
|
|
45
|
+
p.buffer_text = True
|
|
46
|
+
stack: list = [] # [tag, Ident|None, text parts, attrs, line]
|
|
47
|
+
idents: list = [] # current chain of Idents
|
|
48
|
+
|
|
49
|
+
def start(name, attrs):
|
|
50
|
+
stack.append([name.rsplit(":", 1)[-1], None, [], attrs, p.CurrentLineNumber])
|
|
51
|
+
|
|
52
|
+
def chars(t):
|
|
53
|
+
if stack:
|
|
54
|
+
stack[-1][2].append(t)
|
|
55
|
+
|
|
56
|
+
def end(name):
|
|
57
|
+
tag, ident, text, attrs, line = stack.pop()
|
|
58
|
+
if tag == "SHORT-NAME" and stack:
|
|
59
|
+
owner = stack[-1]
|
|
60
|
+
if owner[1] is None:
|
|
61
|
+
parent = idents[-1] if idents else None
|
|
62
|
+
sn = "".join(text).strip()
|
|
63
|
+
ip = f"{parent.path if parent else ''}/{sn}"
|
|
64
|
+
owner[1] = Ident(ip, owner[0], path, owner[4], parent=parent.path if parent else None)
|
|
65
|
+
if parent is not None:
|
|
66
|
+
parent.children.append(ip)
|
|
67
|
+
idents.append(owner[1])
|
|
68
|
+
self.by_path.setdefault(ip, owner[1])
|
|
69
|
+
self.by_tag.setdefault(owner[0], []).append(owner[1])
|
|
70
|
+
return
|
|
71
|
+
if ident is not None:
|
|
72
|
+
idents.pop()
|
|
73
|
+
return
|
|
74
|
+
if idents: # a leaf value of the innermost enclosing identifiable
|
|
75
|
+
txt = "".join(text).strip()
|
|
76
|
+
if txt:
|
|
77
|
+
cur = idents[-1]
|
|
78
|
+
cur.values.setdefault(tag, []).append(txt)
|
|
79
|
+
if "DEST" in attrs:
|
|
80
|
+
cur.dests.setdefault(tag, []).append(attrs["DEST"])
|
|
81
|
+
|
|
82
|
+
p.StartElementHandler = start
|
|
83
|
+
p.EndElementHandler = end
|
|
84
|
+
p.CharacterDataHandler = chars
|
|
85
|
+
try:
|
|
86
|
+
p.Parse(data, True)
|
|
87
|
+
except expat.ExpatError:
|
|
88
|
+
pass
|
|
89
|
+
self._refs_to = None
|
|
90
|
+
|
|
91
|
+
def get(self, path: Optional[str]) -> Optional[Ident]:
|
|
92
|
+
return self.by_path.get(path.strip()) if path else None
|
|
93
|
+
|
|
94
|
+
def tags(self, tag: str) -> list:
|
|
95
|
+
return self.by_tag.get(tag, [])
|
|
96
|
+
|
|
97
|
+
def refs_to(self, path: str) -> list:
|
|
98
|
+
"""Idents that reference ``path`` (any *-REF leaf)."""
|
|
99
|
+
if self._refs_to is None:
|
|
100
|
+
idx: dict = {}
|
|
101
|
+
for i in self.by_path.values():
|
|
102
|
+
for t, vals in i.values.items():
|
|
103
|
+
if t.endswith("-REF") or t.endswith("-TREF"):
|
|
104
|
+
for v in vals:
|
|
105
|
+
idx.setdefault(v, []).append(i)
|
|
106
|
+
self._refs_to = idx
|
|
107
|
+
return self._refs_to.get(path, [])
|
|
108
|
+
|
|
109
|
+
def foreign(self) -> dict:
|
|
110
|
+
return {p: (i.tag, i.line, i.file) for p, i in self.by_path.items()}
|
|
111
|
+
|
|
112
|
+
# ---- navigation helpers (by metaclass, not by name) ----
|
|
113
|
+
def ipdus_of_triggering(self, path: Optional[str]) -> list:
|
|
114
|
+
"""A PDU-TRIGGERING or FRAME-TRIGGERING (any bus) -> system I-PDU paths it carries."""
|
|
115
|
+
t = self.get(path)
|
|
116
|
+
if t is None:
|
|
117
|
+
return []
|
|
118
|
+
out = []
|
|
119
|
+
for ref in t.values.get("I-PDU-REF", []):
|
|
120
|
+
out.append(ref)
|
|
121
|
+
for pt in t.values.get("PDU-TRIGGERING-REF", []):
|
|
122
|
+
out.extend(self.ipdus_of_triggering(pt))
|
|
123
|
+
if not out:
|
|
124
|
+
fr = self.get(t.one("FRAME-REF"))
|
|
125
|
+
if fr is not None:
|
|
126
|
+
for m in fr.children:
|
|
127
|
+
mi = self.get(m)
|
|
128
|
+
if mi is not None:
|
|
129
|
+
out.extend(mi.values.get("PDU-REF", []))
|
|
130
|
+
seen, res = set(), []
|
|
131
|
+
for x in out:
|
|
132
|
+
if x not in seen:
|
|
133
|
+
seen.add(x)
|
|
134
|
+
res.append(x)
|
|
135
|
+
return res
|
|
136
|
+
|
|
137
|
+
def channel_of(self, path: Optional[str]) -> Optional[str]:
|
|
138
|
+
"""Physical channel (the identifiable holding the triggering) of a PDU/FRAME-TRIGGERING."""
|
|
139
|
+
t = self.get(path)
|
|
140
|
+
return t.parent if t is not None else None
|
|
141
|
+
|
|
142
|
+
def signals_of_ipdu(self, ipdu: str) -> list:
|
|
143
|
+
"""I-SIGNAL paths mapped into a system I-PDU (via its I-SIGNAL-TO-I-PDU-MAPPING children)."""
|
|
144
|
+
i = self.get(ipdu)
|
|
145
|
+
if i is None:
|
|
146
|
+
return []
|
|
147
|
+
out = []
|
|
148
|
+
for c in i.children:
|
|
149
|
+
ci = self.get(c)
|
|
150
|
+
if ci is not None and ci.values.get("I-SIGNAL-REF"):
|
|
151
|
+
out.extend(ci.values["I-SIGNAL-REF"])
|
|
152
|
+
return sorted(set(out))
|
|
@@ -64,7 +64,8 @@ def _resolve_def(defs: D.DefIndex, ref: Optional[str]) -> Optional[D.DefNode]:
|
|
|
64
64
|
return None
|
|
65
65
|
|
|
66
66
|
|
|
67
|
-
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:
|
|
68
69
|
defs = project.defs
|
|
69
70
|
dg = Diags(project)
|
|
70
71
|
warned_modules = set()
|
|
@@ -211,6 +212,18 @@ def validate(project: Project, files: Optional[list] = None, module: Optional[st
|
|
|
211
212
|
|
|
212
213
|
from .rules import run_rules
|
|
213
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))
|
|
214
227
|
|
|
215
228
|
items = dg.items
|
|
216
229
|
loaded = sorted(project.files)
|
|
@@ -228,7 +241,7 @@ def validate(project: Project, files: Optional[list] = None, module: Optional[st
|
|
|
228
241
|
f"file(s) [{sample or 'none'}]; NOTHING was validated for it. Pass a file, "
|
|
229
242
|
f"directory or glob relative to {project.root}, or omit 'files'."))
|
|
230
243
|
want = {project.rel(f) for f in matched}
|
|
231
|
-
items = [i for i in items if i["file"] in want]
|
|
244
|
+
items = [i for i in items if i["file"] in want or not i["file"].lower().endswith(".arxml")]
|
|
232
245
|
scope["filter"] = {"files": list(files), "matched": sorted(want)[:200], "matchedCount": len(want)}
|
|
233
246
|
if module:
|
|
234
247
|
names = {m.name for m in project.modules} | {(m.def_ref or "").rsplit("/", 1)[-1] for m in project.modules}
|
|
@@ -144,3 +144,29 @@ def from_json(d: Optional[DefNode], v) -> str:
|
|
|
144
144
|
if d is not None and d.kind == "BOOLEAN" and s.lower() in ("true", "false"):
|
|
145
145
|
s = s.lower()
|
|
146
146
|
return s
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def hex_str(v: int) -> str:
|
|
150
|
+
return f"-0x{-v:X}" if v < 0 else f"0x{v:X}"
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
DEST_KIND = {"ECUC-INTEGER-PARAM-DEF": "INTEGER", "ECUC-FLOAT-PARAM-DEF": "FLOAT", "ECUC-BOOLEAN-PARAM-DEF": "BOOLEAN"}
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def int_forms(d: Optional[DefNode], raw: Optional[str], is_ref: bool, numerical: bool,
|
|
157
|
+
dest: Optional[str] = None) -> Optional[dict]:
|
|
158
|
+
"""{"dec": int, "hex": "0x..."} for an integer-typed value (def kind INTEGER; without loaded defs the
|
|
159
|
+
DEFINITION-REF DEST, else a numerical value whose text is an integer), else None. Computed here so answers
|
|
160
|
+
never convert bases by hand."""
|
|
161
|
+
if raw is None or is_ref:
|
|
162
|
+
return None
|
|
163
|
+
k = d.kind if d is not None else DEST_KIND.get(dest or "") or ("NUMBER" if numerical else None)
|
|
164
|
+
if k not in ("INTEGER", "NUMBER"):
|
|
165
|
+
return None
|
|
166
|
+
t = raw.strip()
|
|
167
|
+
if t.lower() in ("true", "false"):
|
|
168
|
+
return None
|
|
169
|
+
v = parse_int(t)
|
|
170
|
+
if v is None:
|
|
171
|
+
return None
|
|
172
|
+
return {"dec": v, "hex": hex_str(v)}
|