@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.
Files changed (49) 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 +372 -69
  9. package/python/ecuc_engine/cli.py +119 -10
  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/diff.py +13 -5
  30. package/python/ecuc_engine/find.py +162 -0
  31. package/python/ecuc_engine/gen.py +105 -0
  32. package/python/ecuc_engine/history.py +146 -0
  33. package/python/ecuc_engine/model.py +33 -29
  34. package/python/ecuc_engine/paths.py +221 -0
  35. package/python/ecuc_engine/probe.py +229 -0
  36. package/python/ecuc_engine/rte_api.py +935 -0
  37. package/python/ecuc_engine/rules/__init__.py +5 -3
  38. package/python/ecuc_engine/rules/mem.py +84 -0
  39. package/python/ecuc_engine/rules/nvm.py +131 -0
  40. package/python/ecuc_engine/rules/pdur.py +87 -0
  41. package/python/ecuc_engine/rules/system.py +162 -0
  42. package/python/ecuc_engine/sysmodel.py +152 -0
  43. package/python/ecuc_engine/validate.py +52 -3
  44. package/python/ecuc_engine/verify_rte.py +445 -0
  45. package/python/pyproject.toml +1 -1
  46. package/skills/autosar/SKILL.md +57 -1
  47. package/skills/autosar/references/codegen.md +30 -0
  48. package/skills/autosar/references/project-layout.md +11 -2
  49. 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 {}