@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
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"""Compile-only sizeof probes with the project's own compiler and flags.
|
|
2
|
+
|
|
3
|
+
Nothing is executed: each object's size is recovered from a compiler diagnostic
|
|
4
|
+
(`char (*p)[sizeof(x)] = 1;` makes gcc/clang print "char (*)[N]"). clang and gcc >= 14 report that
|
|
5
|
+
int-to-pointer conversion as an error; gcc <= 13 only as a -Wint-conversion *warning*, so warnings must not be
|
|
6
|
+
silenced (no -w) and -Wint-conversion is forced on. The parser accepts ASCII or UTF-8 quotes, "warning:" or
|
|
7
|
+
"error:", absolute or relative file names. Works for cross compilers.
|
|
8
|
+
|
|
9
|
+
Compiler and flags: .hydron/autosar.json "probe": {"cc": "...", "flags": [...]} > a Makefile found in the
|
|
10
|
+
project (CC, CFLAGS/CPPFLAGS/INCLUDES/DEFINES) > host `cc` with -I for every directory holding headers.
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
import os
|
|
16
|
+
import re
|
|
17
|
+
import shlex
|
|
18
|
+
import shutil
|
|
19
|
+
import subprocess
|
|
20
|
+
import tempfile
|
|
21
|
+
from dataclasses import dataclass, field
|
|
22
|
+
from typing import Optional
|
|
23
|
+
|
|
24
|
+
from . import paths
|
|
25
|
+
|
|
26
|
+
KEEP_FLAG = re.compile(r"^(-I|-D|-U|-std=|-m|-f(?!no-?error)|--sysroot|-isystem|-include|-target|--target)")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class Toolchain:
|
|
31
|
+
cc: list
|
|
32
|
+
flags: list
|
|
33
|
+
origin: str
|
|
34
|
+
notes: list = field(default_factory=list)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _make_vars(path: str) -> dict:
|
|
38
|
+
"""Very small Makefile variable reader (=, :=, ?=, +=, line continuations, $(VAR) expansion)."""
|
|
39
|
+
raw: dict = {}
|
|
40
|
+
try:
|
|
41
|
+
lines = open(path, encoding="utf-8", errors="replace").read().replace("\\\n", " ").split("\n")
|
|
42
|
+
except OSError:
|
|
43
|
+
return {}
|
|
44
|
+
rx = re.compile(r"^\s*(?:override\s+|export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*([:?+]?=)\s*(.*)$")
|
|
45
|
+
for ln in lines:
|
|
46
|
+
if ln.startswith("\t"):
|
|
47
|
+
continue
|
|
48
|
+
m = rx.match(ln.split("#", 1)[0])
|
|
49
|
+
if not m:
|
|
50
|
+
continue
|
|
51
|
+
name, op, val = m.group(1), m.group(2), m.group(3).strip()
|
|
52
|
+
if op == "?=" and name in raw:
|
|
53
|
+
continue
|
|
54
|
+
raw[name] = (raw.get(name, "") + " " + val).strip() if op == "+=" else val
|
|
55
|
+
|
|
56
|
+
def expand(v: str, depth=0) -> str:
|
|
57
|
+
if depth > 10:
|
|
58
|
+
return v
|
|
59
|
+
return re.sub(r"\$[({]([A-Za-z_][A-Za-z0-9_]*)[)}]", lambda m: expand(raw.get(m.group(1), os.environ.get(m.group(1), "")), depth + 1), v)
|
|
60
|
+
|
|
61
|
+
return {k: expand(v) for k, v in raw.items()}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _makefiles(root: str) -> list:
|
|
65
|
+
w = paths.walk(root, lambda n: n in ("Makefile", "makefile", "GNUmakefile") or n.endswith(".mk"))
|
|
66
|
+
return w.files
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def toolchain(root: str, cfg: dict) -> Toolchain:
|
|
70
|
+
pc = cfg.get("probe") if isinstance(cfg.get("probe"), dict) else {}
|
|
71
|
+
if pc.get("cc"):
|
|
72
|
+
flags = pc.get("flags") or []
|
|
73
|
+
flags = shlex.split(flags) if isinstance(flags, str) else list(flags)
|
|
74
|
+
return Toolchain(shlex.split(pc["cc"]), flags, ".hydron/autosar.json probe")
|
|
75
|
+
build = cfg.get("build")
|
|
76
|
+
cmd = build.get("command") if isinstance(build, dict) else build if isinstance(build, str) else ""
|
|
77
|
+
mk = []
|
|
78
|
+
m = re.search(r"make\b.*?-C\s+(\S+)", cmd or "")
|
|
79
|
+
if m and os.path.isfile(os.path.join(root, m.group(1), "Makefile")):
|
|
80
|
+
mk = [os.path.join(root, m.group(1), "Makefile")]
|
|
81
|
+
mk = mk or _makefiles(root)
|
|
82
|
+
for f in mk:
|
|
83
|
+
v = _make_vars(f)
|
|
84
|
+
cflags = " ".join(v.get(k, "") for k in ("CPPFLAGS", "CFLAGS"))
|
|
85
|
+
if not cflags.strip() and not v.get("CC"):
|
|
86
|
+
continue
|
|
87
|
+
base = os.path.dirname(f)
|
|
88
|
+
flags = []
|
|
89
|
+
toks = shlex.split(cflags) if cflags else []
|
|
90
|
+
i = 0
|
|
91
|
+
while i < len(toks):
|
|
92
|
+
t = toks[i]
|
|
93
|
+
if t in ("-I", "-isystem", "-include", "-D") and i + 1 < len(toks):
|
|
94
|
+
t = t + toks[i + 1] if t in ("-I", "-D") else t + " " + toks[i + 1]
|
|
95
|
+
i += 1
|
|
96
|
+
if KEEP_FLAG.match(t) and "error" not in t:
|
|
97
|
+
if t.startswith("-I") and not os.path.isabs(t[2:]):
|
|
98
|
+
t = "-I" + os.path.normpath(os.path.join(base, t[2:]))
|
|
99
|
+
flags.extend(t.split(" ", 1) if t.startswith(("-isystem ", "-include ")) else [t])
|
|
100
|
+
i += 1
|
|
101
|
+
cc = shlex.split(v.get("CC") or "cc")
|
|
102
|
+
if not shutil.which(cc[0]):
|
|
103
|
+
cc = ["cc"]
|
|
104
|
+
return Toolchain(cc, flags, os.path.relpath(f, root))
|
|
105
|
+
inc = sorted({os.path.dirname(f) for f in paths.walk(root, lambda n: n.lower().endswith(".h")).files})
|
|
106
|
+
return Toolchain(["cc"], [f"-I{d}" for d in inc], "host cc with discovered header dirs")
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@dataclass
|
|
110
|
+
class Decl:
|
|
111
|
+
symbol: str
|
|
112
|
+
type: str
|
|
113
|
+
text: str # the declaration, without initializer
|
|
114
|
+
file: str
|
|
115
|
+
line: int
|
|
116
|
+
header: Optional[str] # a header declaring the symbol (extern) or defining its type
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def find_decl(symbol: str, cfiles: list, headers: list) -> Optional[Decl]:
|
|
120
|
+
rx = re.compile(rf"^[ \t]*((?:(?:static|extern|volatile|const)\s+)*)([A-Za-z_]\w*(?:\s*\*)?)\s+{re.escape(symbol)}\s*(\[[^\]]*\])?\s*(=|;)", re.M)
|
|
121
|
+
best = None
|
|
122
|
+
for f in headers + cfiles:
|
|
123
|
+
try:
|
|
124
|
+
text = open(f, encoding="utf-8", errors="replace").read()
|
|
125
|
+
except OSError:
|
|
126
|
+
continue
|
|
127
|
+
m = rx.search(text)
|
|
128
|
+
if not m or m.group(2) in ("return", "VAR", "CONST", "P2VAR"):
|
|
129
|
+
continue
|
|
130
|
+
line = text.count("\n", 0, m.start()) + 1
|
|
131
|
+
decl = f"extern {m.group(2)} {symbol}{m.group(3) or ''};"
|
|
132
|
+
d = Decl(symbol, m.group(2).strip(), decl, f, line, f if f.endswith(".h") else None)
|
|
133
|
+
if d.header:
|
|
134
|
+
return d
|
|
135
|
+
best = best or d
|
|
136
|
+
return best
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def type_header(type_name: str, headers: list) -> Optional[str]:
|
|
140
|
+
rx = re.compile(rf"(\}}\s*{re.escape(type_name)}\s*;|typedef\s+[^;]*\b{re.escape(type_name)}\s*;)")
|
|
141
|
+
for f in headers:
|
|
142
|
+
try:
|
|
143
|
+
if rx.search(open(f, encoding="utf-8", errors="replace").read()):
|
|
144
|
+
return f
|
|
145
|
+
except OSError:
|
|
146
|
+
continue
|
|
147
|
+
return None
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def sizes(root: str, cfg: dict, decls: dict, timeout: int = 60) -> tuple:
|
|
151
|
+
"""decls: key -> Decl. Returns ({key: size}, notes, toolchain). Missing keys = not determined."""
|
|
152
|
+
tc = toolchain(root, cfg)
|
|
153
|
+
if not decls:
|
|
154
|
+
return {}, [], tc
|
|
155
|
+
cc = tc.cc
|
|
156
|
+
if not shutil.which(cc[0]):
|
|
157
|
+
return {}, [f"no C compiler '{cc[0]}' on PATH; sizeof probe skipped (set .hydron/autosar.json probe.cc)"], tc
|
|
158
|
+
headers = sorted({d.header for d in decls.values() if d.header})
|
|
159
|
+
head = [f'#include "{h}"' for h in headers]
|
|
160
|
+
keys = list(decls)
|
|
161
|
+
body = []
|
|
162
|
+
line_of: dict = {}
|
|
163
|
+
for i, k in enumerate(keys):
|
|
164
|
+
d = decls[k]
|
|
165
|
+
if not d.header:
|
|
166
|
+
body.append(d.text) # declare it ourselves (type comes from the included headers)
|
|
167
|
+
body.append(f"char (*h2l_probe_{i})[sizeof({d.symbol})] = 1;")
|
|
168
|
+
line_of[i] = len(head) + len(body)
|
|
169
|
+
src = "\n".join(head + body) + "\n"
|
|
170
|
+
with tempfile.TemporaryDirectory(prefix="h2l-probe-") as tmp:
|
|
171
|
+
f = os.path.join(tmp, "h2l_probe.c")
|
|
172
|
+
open(f, "w").write(src)
|
|
173
|
+
kind = _kind(cc)
|
|
174
|
+
syntax = ["-fsyntax-only"] if kind else ["-c", "-o", os.path.join(tmp, "p.o")]
|
|
175
|
+
# never -w: on gcc <= 13 the probe diagnostic is a warning and -w would swallow it
|
|
176
|
+
syntax += {"clang": ["-ferror-limit=0", "-Wint-conversion"], "gcc": ["-fmax-errors=0", "-Wint-conversion"]}.get(kind, [])
|
|
177
|
+
env = dict(os.environ, LC_ALL="C", LANG="C") # ASCII quotes, untranslated messages
|
|
178
|
+
try:
|
|
179
|
+
r = subprocess.run(cc + tc.flags + syntax + [f], capture_output=True, text=True, timeout=timeout, cwd=root, env=env)
|
|
180
|
+
except (OSError, subprocess.TimeoutExpired) as e:
|
|
181
|
+
return {}, [f"sizeof probe could not run {cc[0]}: {e}"], tc
|
|
182
|
+
out = r.stdout + r.stderr
|
|
183
|
+
found = parse_probe_output(out, {line_of[i]: k for i, k in enumerate(keys)})
|
|
184
|
+
notes = []
|
|
185
|
+
fatal = [ln for ln in out.splitlines() if re.search(r"fatal error|error:.*(not found|No such file|unknown type|undeclared)", ln)]
|
|
186
|
+
if fatal and not found:
|
|
187
|
+
notes.append("sizeof probe did not compile with " + " ".join(cc) + f" ({tc.origin}): " + "; ".join(fatal[:3]))
|
|
188
|
+
missing = [k for k in keys if k not in found]
|
|
189
|
+
if missing and not (fatal and not found):
|
|
190
|
+
notes.append(f"compiler output did not reveal sizes for {len(missing)} object(s)"
|
|
191
|
+
+ (f" ({'; '.join(fatal[:2])})" if fatal else "")
|
|
192
|
+
+ "; use .hydron/autosar.json probe.cc with gcc/clang-compatible diagnostics")
|
|
193
|
+
return found, notes, tc
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
PROBE_DIAG = re.compile(r"h2l_probe\.c:(\d+):(?:\d+:)?[^\n]*?char \(\*\)\s*\[(\d+)[uUlL]*\]")
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def parse_probe_output(out: str, key_of_line: dict) -> dict:
|
|
200
|
+
"""{key: size} from compiler output; key_of_line maps the probe source line to its key.
|
|
201
|
+
|
|
202
|
+
gcc: h2l_probe.c:2:40: warning: initialization of ‘char (*)[8]’ from ‘int’ makes pointer ... [-Wint-conversion]
|
|
203
|
+
gcc14: h2l_probe.c:2:40: error: initialization of 'char (*)[8]' from 'int' makes pointer ... [-Wint-conversion]
|
|
204
|
+
clang: h2l_probe.c:2:8: error: incompatible integer to pointer conversion initializing 'char (*)[8]' with ...
|
|
205
|
+
"""
|
|
206
|
+
found: dict = {}
|
|
207
|
+
for m in PROBE_DIAG.finditer(out):
|
|
208
|
+
k = key_of_line.get(int(m.group(1)))
|
|
209
|
+
if k is not None and k not in found:
|
|
210
|
+
found[k] = int(m.group(2))
|
|
211
|
+
return found
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _kind(cc: list) -> str:
|
|
215
|
+
"""'clang' | 'gcc' | '' (other compiler) from `cc --version`."""
|
|
216
|
+
try:
|
|
217
|
+
v = subprocess.run(cc + ["--version"], capture_output=True, text=True, timeout=10).stdout.lower()
|
|
218
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
219
|
+
return ""
|
|
220
|
+
return "clang" if "clang" in v else "gcc" if ("gcc" in v or "free software foundation" in v) else ""
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def load_cfg(root: str) -> dict:
|
|
224
|
+
try:
|
|
225
|
+
with open(os.path.join(root, ".hydron", "autosar.json")) as fh:
|
|
226
|
+
d = json.load(fh)
|
|
227
|
+
return d if isinstance(d, dict) else {}
|
|
228
|
+
except (OSError, ValueError):
|
|
229
|
+
return {}
|