@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,159 @@
|
|
|
1
|
+
"""SchM_<Mip>.h and <Mip>_MemMap.h (+ an aggregating MemMap.h when the project has none).
|
|
2
|
+
|
|
3
|
+
SchM_<Mip>.h (SWS_Rte BSW scheduler part): exclusive-area enter/exit macros and the module's main
|
|
4
|
+
function prototypes.
|
|
5
|
+
* Exclusive areas: the ones the module's static code actually uses (`SchM_Enter_<Mip>_<EA>` found
|
|
6
|
+
in the project's non-generated C), plus any configured for the module in the Rte ECUC.
|
|
7
|
+
Mechanism per RteExclusiveAreaImplMechanism when configured (RteBswExclusiveAreaImpl), else
|
|
8
|
+
ALL_INTERRUPT_BLOCKING (OS SuspendAllInterrupts/ResumeAllInterrupts).
|
|
9
|
+
* Main functions: <Mip>_MainFunction for a `...MainFunctionPeriod` parameter, and one per instance
|
|
10
|
+
of a `<Mip>MainFunction<X>` container (Com-style named main functions).
|
|
11
|
+
<Mip>_MemMap.h: SWS_MemMap_00032 file name, keyword evaluation and START/STOP sequence checks in the
|
|
12
|
+
shape of the SWS_MemMap example (chapter A.3); pragmas are left to the project's MemMapAllocation
|
|
13
|
+
(`.hydron/autosar.json` codegen.memmap.pragmas), so the default build is compiler-neutral.
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import re
|
|
18
|
+
|
|
19
|
+
from . import cwriter as W
|
|
20
|
+
|
|
21
|
+
DEFAULT_SECTIONS = ["CODE", "CONST_UNSPECIFIED", "CONFIG_DATA_UNSPECIFIED", "VAR_INIT_UNSPECIFIED",
|
|
22
|
+
"VAR_NOINIT_UNSPECIFIED", "VAR_CLEARED_UNSPECIFIED"]
|
|
23
|
+
|
|
24
|
+
MECHANISMS = {
|
|
25
|
+
"ALL_INTERRUPT_BLOCKING": ("SuspendAllInterrupts()", "ResumeAllInterrupts()"),
|
|
26
|
+
"OS_INTERRUPT_BLOCKING": ("SuspendOSInterrupts()", "ResumeOSInterrupts()"),
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _scan(ctx, rx) -> dict:
|
|
31
|
+
hits: dict = {}
|
|
32
|
+
for f in ctx.sources:
|
|
33
|
+
try:
|
|
34
|
+
with open(f, errors="replace") as fh:
|
|
35
|
+
text = fh.read()
|
|
36
|
+
except OSError:
|
|
37
|
+
continue
|
|
38
|
+
for m in rx.finditer(text):
|
|
39
|
+
hits.setdefault(m.group(1), set()).add(ctx.cg.p.rel(f))
|
|
40
|
+
return hits
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _ea_mechanisms(cg) -> dict:
|
|
44
|
+
"""EA short name -> (mechanism, os resource) from the Rte ECUC, when configured."""
|
|
45
|
+
out = {}
|
|
46
|
+
rte = cg.module("Rte")
|
|
47
|
+
if rte is None:
|
|
48
|
+
return out
|
|
49
|
+
for c in rte.containers:
|
|
50
|
+
if c.def_name != "RteBswExclusiveAreaImpl":
|
|
51
|
+
continue
|
|
52
|
+
ref = c.text("RteBswExclusiveAreaRef") or ""
|
|
53
|
+
mech = c.text("RteExclusiveAreaImplMechanism") or "ALL_INTERRUPT_BLOCKING"
|
|
54
|
+
res = (c.text("RteBswExclusiveAreaOsResourceRef") or "").rsplit("/", 1)[-1]
|
|
55
|
+
if ref:
|
|
56
|
+
out[ref.rsplit("/", 1)[-1]] = (mech, res)
|
|
57
|
+
return out
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def main_functions(cg, mo) -> list:
|
|
61
|
+
mc = mo.mc
|
|
62
|
+
out = list(getattr(mo, "main_functions", []))
|
|
63
|
+
for c in mc.containers:
|
|
64
|
+
if c.defn is None:
|
|
65
|
+
continue
|
|
66
|
+
m = re.match(rf"^{re.escape(mc.name)}MainFunction(\w*)$", c.def_name)
|
|
67
|
+
if m:
|
|
68
|
+
suffix = m.group(1)
|
|
69
|
+
fn = f"{mo.mip}_MainFunction{suffix}" + ("" if c.name == c.def_name else f"_{c.name}")
|
|
70
|
+
if fn not in out:
|
|
71
|
+
out.append(fn)
|
|
72
|
+
continue
|
|
73
|
+
if cg.is_multi(c):
|
|
74
|
+
continue
|
|
75
|
+
for p in c.defn.params:
|
|
76
|
+
if p.endswith("MainFunctionPeriod") and c.text(p.rsplit("/", 1)[-1]) is not None:
|
|
77
|
+
fn = f"{mo.mip}_MainFunction"
|
|
78
|
+
if fn not in out:
|
|
79
|
+
out.append(fn)
|
|
80
|
+
return out
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def schm_header(ctx, mo) -> tuple:
|
|
84
|
+
mip, MIP = mo.mip, mo.MIP
|
|
85
|
+
eas = _scan(ctx, re.compile(rf"\bSchM_(?:Enter|Exit)_{re.escape(mip)}_(\w+)\s*\("))
|
|
86
|
+
mech = _ea_mechanisms(ctx.cg)
|
|
87
|
+
for ea in mech:
|
|
88
|
+
if ea.upper().startswith(MIP + "_") and ea not in eas:
|
|
89
|
+
eas[ea] = {"Rte ECUC"}
|
|
90
|
+
mfs = main_functions(ctx.cg, mo)
|
|
91
|
+
name = f"SchM_{mip}.h"
|
|
92
|
+
g = W.guard(name)
|
|
93
|
+
lines = [mo._banner(name), "", f"#ifndef {g}", f"#define {g}", "", '#include "Std_Types.h"']
|
|
94
|
+
if eas:
|
|
95
|
+
lines.append('#include "Os.h"')
|
|
96
|
+
if eas:
|
|
97
|
+
lines += ["", "/* Exclusive areas (SWS_Rte SchM_Enter/SchM_Exit); used in: "
|
|
98
|
+
+ ", ".join(sorted({f for s in eas.values() for f in s})) + " */"]
|
|
99
|
+
for ea in sorted(eas):
|
|
100
|
+
m, res = mech.get(ea, ("ALL_INTERRUPT_BLOCKING", ""))
|
|
101
|
+
if m == "OS_RESOURCE" and res:
|
|
102
|
+
enter, exit_ = f"(void)GetResource({res})", f"(void)ReleaseResource({res})"
|
|
103
|
+
else:
|
|
104
|
+
enter, exit_ = MECHANISMS.get(m, ("do { } while (0)", "do { } while (0)"))
|
|
105
|
+
lines += [W.define(f"SchM_Enter_{mip}_{ea}()", enter, m), W.define(f"SchM_Exit_{mip}_{ea}()", exit_)]
|
|
106
|
+
if mfs:
|
|
107
|
+
lines += ["", "/* Main functions called by the BSW scheduler */"]
|
|
108
|
+
lines += [f"extern FUNC(void, {MIP}_CODE) {fn}(void);" for fn in mfs]
|
|
109
|
+
lines += ["", f"#endif /* {g} */", ""]
|
|
110
|
+
return name, "\n".join(lines)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def module_memmap(ctx, mo, pragmas: dict) -> tuple:
|
|
114
|
+
mip, MIP = mo.mip, mo.MIP
|
|
115
|
+
used = _scan(ctx, re.compile(rf"\b{re.escape(MIP)}_START_SEC_(\w+)"))
|
|
116
|
+
sections = list(DEFAULT_SECTIONS) + sorted(s for s in used if s not in DEFAULT_SECTIONS)
|
|
117
|
+
name = f"{mip}_MemMap.h"
|
|
118
|
+
lines = [mo._banner(name),
|
|
119
|
+
"/* Memory mapping of the module's sections (SWS_MemMap_00032). No include guard: it is included",
|
|
120
|
+
" * once per section switch. Pragmas come from codegen.memmap.pragmas (MemMapAllocation); none by default. */",
|
|
121
|
+
"", "#define MEMMAP_ERROR", ""]
|
|
122
|
+
for i, sec in enumerate(sections):
|
|
123
|
+
kw = f"{MIP}_SEC_{sec}"
|
|
124
|
+
start = pragmas.get(sec, {}).get("start", "") if isinstance(pragmas.get(sec), dict) else ""
|
|
125
|
+
stop = pragmas.get(sec, {}).get("stop", "") if isinstance(pragmas.get(sec), dict) else ""
|
|
126
|
+
lines += [
|
|
127
|
+
f"{'#if' if i == 0 else '#elif'} defined({MIP}_START_SEC_{sec})",
|
|
128
|
+
f" #undef {MIP}_START_SEC_{sec}",
|
|
129
|
+
" #undef MEMMAP_ERROR",
|
|
130
|
+
f" #ifdef {MIP}_MEMMAP_SEQUENCE_OPEN",
|
|
131
|
+
f' #error "{name}: {kw}: STOP the open section before starting a new one"',
|
|
132
|
+
" #endif",
|
|
133
|
+
f" #define {MIP}_MEMMAP_SEQUENCE_OPEN",
|
|
134
|
+
*( [f" {start}"] if start else []),
|
|
135
|
+
f"#elif defined({MIP}_STOP_SEC_{sec})",
|
|
136
|
+
f" #undef {MIP}_STOP_SEC_{sec}",
|
|
137
|
+
" #undef MEMMAP_ERROR",
|
|
138
|
+
f" #ifndef {MIP}_MEMMAP_SEQUENCE_OPEN",
|
|
139
|
+
f' #error "{name}: {kw}: STOP without START"',
|
|
140
|
+
" #endif",
|
|
141
|
+
f" #undef {MIP}_MEMMAP_SEQUENCE_OPEN",
|
|
142
|
+
*( [f" {stop}"] if stop else []),
|
|
143
|
+
]
|
|
144
|
+
lines += ["#endif", "", "#if defined(MEMMAP_ERROR)", f' #error "{name}: unknown memory section"', "#endif", ""]
|
|
145
|
+
return name, "\n".join(lines), sections
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def aggregate_memmap(ctx, entries: list) -> str:
|
|
149
|
+
"""Legacy MemMap.h dispatching to the per-module files (only emitted when the project has none)."""
|
|
150
|
+
lines = [W.banner("MemMap.h", "MemMap", "-", ctx.release, [], "-"),
|
|
151
|
+
"/* Dispatches each <MIP>_START_SEC_/<MIP>_STOP_SEC_ keyword to <Mip>_MemMap.h. No include guard. */", ""]
|
|
152
|
+
first = True
|
|
153
|
+
for mip, sections in entries:
|
|
154
|
+
MIP = mip.upper()
|
|
155
|
+
cond = " || \\\n ".join(f"defined({MIP}_{k}_SEC_{s})" for s in sections for k in ("START", "STOP"))
|
|
156
|
+
lines += [f"{'#if' if first else '#elif'} {cond}", f' #include "{mip}_MemMap.h"']
|
|
157
|
+
first = False
|
|
158
|
+
lines += ["#else", ' #error "MemMap.h: unknown memory section"', "#endif", ""]
|
|
159
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
"""Deterministic context assembly for "implement / change a runnable" tasks (autosar_build_context).
|
|
2
|
+
|
|
3
|
+
Fixed section order:
|
|
4
|
+
1 TASK the task text, verbatim
|
|
5
|
+
2 SWC DEFINITION ports, interfaces, data types, runnables, events (from ARXML, by reference)
|
|
6
|
+
3 ALLOWED RTE API for the runnable(s): exact declaration in Rte_<Swc>.h when present, else the
|
|
7
|
+
SWS_RTE signature from the model; never dropped or truncated
|
|
8
|
+
4 RUNNABLE CODE the entry function body from the C sources (local parse)
|
|
9
|
+
5 CALLEES functions it reaches: bodies of in-house ones, prototypes of the rest
|
|
10
|
+
6 RELATED CONFIG Com signals / NvM blocks behind its ports and calls (ECUC values + file:line)
|
|
11
|
+
7 FUZZY: SIMILAR FUNCTIONS in other SW-Cs, by lexical/structural similarity of RTE API + call names
|
|
12
|
+
8 FUZZY: SIMILAR PAST CHANGES git-history-lite (history.similar_changes)
|
|
13
|
+
|
|
14
|
+
Budget (about 4 characters per token): drop 8, then 7, then reduce 5 to prototypes, truncate 4, truncate 6,
|
|
15
|
+
reduce 5 to names, cut 4 to its head, compact 2. Sections 1 and 3 are never reduced. Same inputs -> byte-identical
|
|
16
|
+
output: everything is sorted, paths are project-relative, nothing time- or environment-dependent is emitted.
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import hashlib
|
|
21
|
+
import json
|
|
22
|
+
import os
|
|
23
|
+
import re
|
|
24
|
+
from typing import Optional
|
|
25
|
+
|
|
26
|
+
from . import paths
|
|
27
|
+
from .csrc import CIndex, Func
|
|
28
|
+
from .history import similar_changes, terms_of
|
|
29
|
+
from .rte_api import ArIndex, SwcModel, build_all, find_header, find_swc, header_entries
|
|
30
|
+
|
|
31
|
+
CHARS_PER_TOKEN = 4
|
|
32
|
+
SIM_TOP = 3
|
|
33
|
+
CODE_TRUNC_LINES = 40
|
|
34
|
+
CODE_HEAD_LINES = 12
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _h(text: str) -> str:
|
|
38
|
+
return hashlib.sha1(text.encode("utf-8")).hexdigest()[:12]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _cut(text: str, n: int) -> str:
|
|
42
|
+
lines = text.split("\n")
|
|
43
|
+
if len(lines) <= n:
|
|
44
|
+
return text
|
|
45
|
+
return "\n".join(lines[:n] + [f" /* … {len(lines) - n} more line(s) truncated by the context budget */", "}"])
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class Ctx:
|
|
49
|
+
def __init__(self, root: str):
|
|
50
|
+
self.root = root
|
|
51
|
+
self.ix = ArIndex(root)
|
|
52
|
+
self.models = build_all(root, self.ix)
|
|
53
|
+
self.cx = CIndex(root)
|
|
54
|
+
self._project = None
|
|
55
|
+
self._hdr_text: Optional[dict] = None
|
|
56
|
+
|
|
57
|
+
def rel(self, f: Optional[str]) -> str:
|
|
58
|
+
if not f:
|
|
59
|
+
return "?"
|
|
60
|
+
r = os.path.relpath(f, self.root)
|
|
61
|
+
return f if r.startswith("..") else r
|
|
62
|
+
|
|
63
|
+
def project(self):
|
|
64
|
+
if self._project is None:
|
|
65
|
+
try:
|
|
66
|
+
from .model import Project
|
|
67
|
+
self._project = Project(self.root)
|
|
68
|
+
except Exception: # noqa: BLE001
|
|
69
|
+
self._project = False
|
|
70
|
+
return self._project or None
|
|
71
|
+
|
|
72
|
+
# ---- C helpers
|
|
73
|
+
def headers(self) -> dict:
|
|
74
|
+
if self._hdr_text is None:
|
|
75
|
+
self._hdr_text = {}
|
|
76
|
+
for f in paths.walk(self.root, paths.is_c).files:
|
|
77
|
+
if f.lower().endswith(".h"):
|
|
78
|
+
try:
|
|
79
|
+
self._hdr_text[f] = open(f, encoding="utf-8", errors="replace").read()
|
|
80
|
+
except OSError:
|
|
81
|
+
pass
|
|
82
|
+
return self._hdr_text
|
|
83
|
+
|
|
84
|
+
def prototype(self, name: str) -> Optional[tuple]:
|
|
85
|
+
rx = re.compile(rf"^[^\n#;{{}}]*\b{re.escape(name)}\s*\([^;{{}}]*\)\s*;", re.M)
|
|
86
|
+
for f in sorted(self.headers()):
|
|
87
|
+
m = rx.search(self.headers()[f])
|
|
88
|
+
if m:
|
|
89
|
+
return " ".join(m.group(0).split()), f"{self.rel(f)}:{self.headers()[f].count(chr(10), 0, m.start()) + 1}"
|
|
90
|
+
for name2, lst in self.cx.funcs.items():
|
|
91
|
+
if name2 == name:
|
|
92
|
+
return lst[0].signature + ";", f"{self.rel(lst[0].file)}:{lst[0].line}"
|
|
93
|
+
return None
|
|
94
|
+
|
|
95
|
+
def body(self, fn: Func) -> str:
|
|
96
|
+
return fn.text(self.cx.src[fn.file])
|
|
97
|
+
|
|
98
|
+
def swc_files(self, m: SwcModel) -> tuple:
|
|
99
|
+
"""(ARXML files, C files) of a SW-C: its description + its interfaces; C reached from its runnables or
|
|
100
|
+
including Rte_<Swc>.h."""
|
|
101
|
+
ax = {os.path.join(self.root, m.file)}
|
|
102
|
+
for p in m.ports:
|
|
103
|
+
n = self.ix.get(p.get("interface"))
|
|
104
|
+
if n is not None:
|
|
105
|
+
ax.add(self.ix.where(n)[0])
|
|
106
|
+
cf = set()
|
|
107
|
+
for r in m.runnables:
|
|
108
|
+
fn = self.cx.get(r.symbol)
|
|
109
|
+
if fn is not None:
|
|
110
|
+
for g in self.cx.reachable(fn):
|
|
111
|
+
cf.add(g.file)
|
|
112
|
+
want = f"rte_{m.name}.h".lower()
|
|
113
|
+
for f, incs in self.cx.includes.items():
|
|
114
|
+
if any(os.path.basename(i).lower() == want for i in incs):
|
|
115
|
+
cf.add(f)
|
|
116
|
+
return sorted(x for x in ax if x), sorted(cf)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# ---------------------------------------------------------------- sections
|
|
120
|
+
def sec_swc(c: Ctx, m: SwcModel, runs: list) -> list:
|
|
121
|
+
"""-> [full, compact] renderings + sources."""
|
|
122
|
+
lines = [f"SW-C {m.name} ({m.tag}) {m.path} @ {m.file}:{m.line}"
|
|
123
|
+
+ ("; supports multiple instantiation (Rte_Instance self first)" if m.multiple_instantiation else "")]
|
|
124
|
+
lines.append("Ports:")
|
|
125
|
+
for p in m.ports:
|
|
126
|
+
lines.append(f" {p['direction']}-port {p['name']} : {p['interfaceKind']} {p['interface']} @ {p['at']}")
|
|
127
|
+
for e in p["elements"]:
|
|
128
|
+
extra = ", ".join(x for x in (e.get("type"), e.get("semantics") and f"{e['semantics']} semantics",
|
|
129
|
+
e.get("kind")) if x)
|
|
130
|
+
lines.append(f" - {e['name']}" + (f" ({extra})" if extra else ""))
|
|
131
|
+
if m.types:
|
|
132
|
+
lines.append("Data types: " + "; ".join(f"{k}={v['kind']}" + (f"[{v['base']}]" if v.get("base") else "")
|
|
133
|
+
for k, v in sorted(m.types.items())))
|
|
134
|
+
lines.append("Runnables:")
|
|
135
|
+
for r in m.runnables:
|
|
136
|
+
ev = "; ".join(f"{e['kind']} {e['event']}" + (f" period {e['period']}s" if e.get("period") else "")
|
|
137
|
+
+ (f" on {e.get('port')}.{(e.get('target') or '').rsplit('/', 1)[-1]}" if e.get("port") else "")
|
|
138
|
+
for e in r.events) or "no event (not scheduled)"
|
|
139
|
+
mark = " <== target" if r in runs and len(runs) < len(m.runnables) else ""
|
|
140
|
+
lines.append(f" {r.name} symbol {r.symbol} [{ev}] @ {r.file}:{r.line}{mark}")
|
|
141
|
+
lines.append(f" entry: {r.signature}")
|
|
142
|
+
for pt in r.points:
|
|
143
|
+
lines.append(f" {pt['role']}: {pt.get('port') or ''}{'.' if pt.get('port') else ''}"
|
|
144
|
+
f"{(pt.get('target') or '').rsplit('/', 1)[-1]}" + (f" !! {pt['unresolved']}" if pt.get("unresolved") else ""))
|
|
145
|
+
full = "\n".join(lines)
|
|
146
|
+
compact = "\n".join(l for l in lines if not l.startswith(" ") or "<== target" in l or l.startswith(" -"))
|
|
147
|
+
src = [{"kind": "arxml", "ref": m.path, "at": f"{m.file}:{m.line}"}]
|
|
148
|
+
return [full, compact], src
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def sec_api(c: Ctx, m: SwcModel, runs: list, header: Optional[str]) -> tuple:
|
|
152
|
+
decl = header_entries([header]) if header else {}
|
|
153
|
+
lines, src = [], []
|
|
154
|
+
if header:
|
|
155
|
+
lines.append(f"Generated contract header: {c.rel(header)}")
|
|
156
|
+
else:
|
|
157
|
+
lines.append(f"No Rte_{m.name}.h in the project: signatures below are derived from the ARXML (SWS_RTE 5.6).")
|
|
158
|
+
for r in runs:
|
|
159
|
+
allowed = sorted(m.allowed(r.name), key=lambda a: a.name)
|
|
160
|
+
lines.append(f"Runnable {r.name}: {len(allowed)} allowed API(s) — call ONLY these from {r.symbol} and the "
|
|
161
|
+
"functions it calls:")
|
|
162
|
+
for a in allowed:
|
|
163
|
+
d = decl.get(a.name)
|
|
164
|
+
lines.append(f" {a.signature}; [{a.cite}]")
|
|
165
|
+
if d:
|
|
166
|
+
lines.append(f" declared {c.rel(d['file'])}:{d['line']}: {d['text']}")
|
|
167
|
+
src.append({"kind": "rte-decl", "ref": a.name, "at": f"{c.rel(d['file'])}:{d['line']}"})
|
|
168
|
+
else:
|
|
169
|
+
src.append({"kind": "rte-model", "ref": a.name, "at": a.target})
|
|
170
|
+
others = sorted(a.name for a in m.generated() if not a.allowed_for(r.name))
|
|
171
|
+
if others:
|
|
172
|
+
lines.append(f" NOT allowed in {r.name} (other runnables' access points; adding a call is NOT-ALLOWED "
|
|
173
|
+
f"until an access point is added): {', '.join(others)}")
|
|
174
|
+
return "\n".join(lines), src
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def sec_code(c: Ctx, runs: list) -> tuple:
|
|
178
|
+
full, trunc, head, src, missing = [], [], [], [], []
|
|
179
|
+
for r in runs:
|
|
180
|
+
fn = c.cx.get(r.symbol)
|
|
181
|
+
if fn is None:
|
|
182
|
+
missing.append(r.symbol)
|
|
183
|
+
msg = f"/* {r.symbol}: no definition found in the project C sources (new runnable?) */"
|
|
184
|
+
full.append(msg)
|
|
185
|
+
trunc.append(msg)
|
|
186
|
+
head.append(msg)
|
|
187
|
+
continue
|
|
188
|
+
b = c.body(fn)
|
|
189
|
+
hdr = f"/* {c.rel(fn.file)}:{fn.line}-{fn.end_line} */"
|
|
190
|
+
full.append(hdr + "\n" + b)
|
|
191
|
+
trunc.append(hdr + "\n" + _cut(b, CODE_TRUNC_LINES))
|
|
192
|
+
head.append(hdr + "\n" + _cut(b, CODE_HEAD_LINES))
|
|
193
|
+
src.append({"kind": "c-function", "ref": fn.name, "at": f"{c.rel(fn.file)}:{fn.line}-{fn.end_line}", "sha1": _h(b)})
|
|
194
|
+
return ["\n\n".join(full), "\n\n".join(trunc), "\n\n".join(head)], src, missing
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def sec_callees(c: Ctx, runs: list) -> tuple:
|
|
198
|
+
entries = [c.cx.get(r.symbol) for r in runs]
|
|
199
|
+
entries = [e for e in entries if e is not None]
|
|
200
|
+
entry_ids = {id(e) for e in entries}
|
|
201
|
+
own, ext = [], set()
|
|
202
|
+
seen = set()
|
|
203
|
+
for e in entries:
|
|
204
|
+
for g in c.cx.reachable(e):
|
|
205
|
+
if id(g) in entry_ids or id(g) in seen:
|
|
206
|
+
continue
|
|
207
|
+
seen.add(id(g))
|
|
208
|
+
own.append(g)
|
|
209
|
+
for g in [e] + own:
|
|
210
|
+
for name in g.calls:
|
|
211
|
+
if name.startswith("Rte_") or c.cx.callee(g, name) is not None:
|
|
212
|
+
continue
|
|
213
|
+
ext.add(name)
|
|
214
|
+
own.sort(key=lambda g: (c.rel(g.file), g.line))
|
|
215
|
+
ext_l = []
|
|
216
|
+
for name in sorted(ext):
|
|
217
|
+
p = c.prototype(name)
|
|
218
|
+
if p:
|
|
219
|
+
ext_l.append((name, p))
|
|
220
|
+
bodies = [f"/* {c.rel(g.file)}:{g.line}-{g.end_line} */\n{c.body(g)}" for g in own]
|
|
221
|
+
sigs = [f"{g.signature}; /* {c.rel(g.file)}:{g.line} */" for g in own]
|
|
222
|
+
protos = [f"{p[0]} /* {p[1]} */" for _n, p in ext_l]
|
|
223
|
+
names = ", ".join([g.name for g in own] + [n for n, _p in ext_l])
|
|
224
|
+
full = "\n\n".join(bodies + (["/* called, defined elsewhere: */\n" + "\n".join(protos)] if protos else []))
|
|
225
|
+
sig = "\n".join(sigs + protos)
|
|
226
|
+
src = [{"kind": "c-function", "ref": g.name, "at": f"{c.rel(g.file)}:{g.line}-{g.end_line}", "sha1": _h(c.body(g))}
|
|
227
|
+
for g in own] + [{"kind": "c-prototype", "ref": n, "at": p[1]} for n, p in ext_l]
|
|
228
|
+
return [full or "(no callees)", sig or "(no callees)", ("calls: " + names) if names else "(no callees)"], src
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _sysmap(c: Ctx) -> dict:
|
|
232
|
+
"""(port path, data element path) -> system signal path, from SENDER-RECEIVER-TO-SIGNAL-MAPPINGs."""
|
|
233
|
+
out = {}
|
|
234
|
+
for sysn in c.ix.of_tag("SYSTEM"):
|
|
235
|
+
for n in sysn.iter():
|
|
236
|
+
if n.tag == "SENDER-RECEIVER-TO-SIGNAL-MAPPING":
|
|
237
|
+
port = None
|
|
238
|
+
el = None
|
|
239
|
+
for x in n.iter():
|
|
240
|
+
if x.tag == "CONTEXT-PORT-REF":
|
|
241
|
+
port = x.text.strip()
|
|
242
|
+
elif x.tag == "TARGET-DATA-PROTOTYPE-REF":
|
|
243
|
+
el = x.text.strip()
|
|
244
|
+
sig = n.child_text("SYSTEM-SIGNAL-REF")
|
|
245
|
+
if port and el and sig:
|
|
246
|
+
out.setdefault((port, el), sig)
|
|
247
|
+
return out
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def sec_config(c: Ctx, m: SwcModel, runs: list, header: Optional[str]) -> tuple:
|
|
251
|
+
p = c.project()
|
|
252
|
+
if p is None:
|
|
253
|
+
return ["(ECUC configuration not loadable)", "(ECUC configuration not loadable)"], [], []
|
|
254
|
+
from .audit import signal_owners
|
|
255
|
+
from .find import _own
|
|
256
|
+
decl = header_entries([header]) if header else {}
|
|
257
|
+
by_name: dict = {}
|
|
258
|
+
for path, group in p.by_path.items():
|
|
259
|
+
e = group[0]
|
|
260
|
+
if e.kind == "container":
|
|
261
|
+
by_name.setdefault(((e.def_ref or "").rsplit("/", 1)[-1], e.name), e)
|
|
262
|
+
sysmap = _sysmap(c)
|
|
263
|
+
sig_to_com: dict = {}
|
|
264
|
+
for (dname, _n), e in sorted(by_name.items()):
|
|
265
|
+
if dname in ("ComSignal", "ComGroupSignal"):
|
|
266
|
+
ref = (_own(p, e).get("ComSystemTemplateSystemSignalRef") or [None])[0]
|
|
267
|
+
mp = c.ix.get(ref)
|
|
268
|
+
if mp is not None:
|
|
269
|
+
isig = c.ix.get(mp.child_text("I-SIGNAL-REF"))
|
|
270
|
+
ssig = isig.child_text("SYSTEM-SIGNAL-REF") if isig is not None else None
|
|
271
|
+
if ssig:
|
|
272
|
+
sig_to_com.setdefault(ssig, []).append(e)
|
|
273
|
+
lines, src, terms = [], [], []
|
|
274
|
+
want = []
|
|
275
|
+
for r in runs:
|
|
276
|
+
for a in sorted(m.allowed(r.name), key=lambda a: a.name):
|
|
277
|
+
if a.port and a not in want:
|
|
278
|
+
want.append(a)
|
|
279
|
+
for a in want:
|
|
280
|
+
hits = []
|
|
281
|
+
d = decl.get(a.name)
|
|
282
|
+
if d:
|
|
283
|
+
for mod, dname, short in re.findall(r"\b([A-Z][A-Za-z]+)Conf_([A-Za-z]+)_(\w+)", d["text"]):
|
|
284
|
+
e = by_name.get((dname, short))
|
|
285
|
+
if e is not None:
|
|
286
|
+
hits.append((e, f"via {c.rel(d['file'])}:{d['line']}"))
|
|
287
|
+
if not hits:
|
|
288
|
+
ssig = sysmap.get((a.port_path, a.target))
|
|
289
|
+
for e in sig_to_com.get(ssig or "", []):
|
|
290
|
+
hits.append((e, f"via SenderReceiverToSignalMapping -> {ssig}"))
|
|
291
|
+
for e, how in hits:
|
|
292
|
+
own = _own(p, e)
|
|
293
|
+
vals = ", ".join(f"{k}={v[0] if len(v) == 1 else v}" for k, v in sorted(own.items())
|
|
294
|
+
if k in ("ComBitPosition", "ComBitSize", "ComSignalType", "ComSignalEndianness",
|
|
295
|
+
"ComHandleId", "ComTransferProperty", "ComSignalInitValue"))
|
|
296
|
+
owners = signal_owners(p, [e.path]).get(e.path) or []
|
|
297
|
+
ip = "; ".join(f"I-PDU {o['ipdu']} {o.get('direction') or '?'}" for o in owners)
|
|
298
|
+
lines.append(f"{a.port}.{a.element} -> {e.path} [{vals}] {ip} @ {p.rel(e.file)}:{e.line} ({how})")
|
|
299
|
+
src.append({"kind": "ecuc", "ref": e.path, "at": f"{p.rel(e.file)}:{e.line}"})
|
|
300
|
+
terms.append(e.name)
|
|
301
|
+
# NvM blocks used by the runnable's code
|
|
302
|
+
blocks = set()
|
|
303
|
+
for r in runs:
|
|
304
|
+
fn = c.cx.get(r.symbol)
|
|
305
|
+
if fn is None:
|
|
306
|
+
continue
|
|
307
|
+
for g in c.cx.reachable(fn):
|
|
308
|
+
blocks.update(re.findall(r"\bNvMConf_NvMBlockDescriptor_(\w+)", c.body(g)))
|
|
309
|
+
for b in sorted(blocks):
|
|
310
|
+
e = by_name.get(("NvMBlockDescriptor", b))
|
|
311
|
+
if e is None:
|
|
312
|
+
lines.append(f"NvM block {b}: not configured (NvMConf_NvMBlockDescriptor_{b} has no NvMBlockDescriptor)")
|
|
313
|
+
continue
|
|
314
|
+
own = _own(p, e)
|
|
315
|
+
vals = ", ".join(f"{k}={v[0] if len(v) == 1 else v}" for k, v in sorted(own.items())
|
|
316
|
+
if k in ("NvMNvBlockLength", "NvMRamBlockDataAddress", "NvMBlockUseCrc", "NvMBlockCrcType",
|
|
317
|
+
"NvMBlockManagementType", "NvMNvramBlockIdentifier", "NvMSelectBlockForReadAll"))
|
|
318
|
+
lines.append(f"NvM {e.path} [{vals}] @ {p.rel(e.file)}:{e.line}")
|
|
319
|
+
src.append({"kind": "ecuc", "ref": e.path, "at": f"{p.rel(e.file)}:{e.line}"})
|
|
320
|
+
if not lines:
|
|
321
|
+
lines.append("(no Com signal or NvM block found behind this runnable's ports and calls)")
|
|
322
|
+
full = "\n".join(lines)
|
|
323
|
+
return [full, _cut(full, 15)], src, terms
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def _features(c: Ctx, fn: Optional[Func], extra: set) -> set:
|
|
327
|
+
feats = set(extra)
|
|
328
|
+
if fn is None:
|
|
329
|
+
return feats
|
|
330
|
+
for g in c.cx.reachable(fn)[:20]:
|
|
331
|
+
for name, _l, _a in g.rte:
|
|
332
|
+
feats.add("api:" + name.split("_")[1] if "_" in name else name)
|
|
333
|
+
feats |= {t for t in terms_of(name)}
|
|
334
|
+
for n in g.calls:
|
|
335
|
+
if not n.startswith("Rte_"):
|
|
336
|
+
feats.add("call:" + n)
|
|
337
|
+
feats |= terms_of(c.body(g))
|
|
338
|
+
return feats
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def sec_similar(c: Ctx, m: SwcModel, runs: list, task: str) -> tuple:
|
|
342
|
+
target = set()
|
|
343
|
+
task_t = terms_of(task)
|
|
344
|
+
for r in runs:
|
|
345
|
+
target |= _features(c, c.cx.get(r.symbol), set())
|
|
346
|
+
for a in m.allowed(r.name):
|
|
347
|
+
target.add("api:" + a.api)
|
|
348
|
+
target |= terms_of(a.element or "")
|
|
349
|
+
target |= task_t
|
|
350
|
+
cands = []
|
|
351
|
+
for o in c.models:
|
|
352
|
+
if o is m:
|
|
353
|
+
continue
|
|
354
|
+
for r in o.runnables:
|
|
355
|
+
fn = c.cx.get(r.symbol)
|
|
356
|
+
if fn is None:
|
|
357
|
+
continue
|
|
358
|
+
f = _features(c, fn, {"api:" + a.api for a in o.allowed(r.name)})
|
|
359
|
+
inter, union = len(target & f), len(target | f) or 1
|
|
360
|
+
score = round(inter / union, 4)
|
|
361
|
+
if inter:
|
|
362
|
+
cands.append((-score, o.name, r.name, fn, sorted(target & f)))
|
|
363
|
+
cands.sort(key=lambda x: (x[0], x[1], x[2]))
|
|
364
|
+
top = cands[:SIM_TOP]
|
|
365
|
+
lines, src = [], []
|
|
366
|
+
for s, swc, rn, fn, common in top:
|
|
367
|
+
lines.append(f"[{swc}.{rn} similarity {-s:.3f}; shared: {', '.join(common[:12])}]\n"
|
|
368
|
+
f"/* {c.rel(fn.file)}:{fn.line}-{fn.end_line} */\n" + _cut(c.body(fn), 30))
|
|
369
|
+
src.append({"kind": "c-function", "ref": f"{swc}.{rn}", "at": f"{c.rel(fn.file)}:{fn.line}", "score": -s,
|
|
370
|
+
"sha1": _h(c.body(fn))})
|
|
371
|
+
return ("\n\n".join(lines) or "(no similar function in other SW-Cs)"), src, [x[1] for x in top]
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def sec_history(c: Ctx, m: SwcModel, runs: list, task: str, similar_swcs: list) -> tuple:
|
|
375
|
+
ax, cf = c.swc_files(m)
|
|
376
|
+
rel_files = []
|
|
377
|
+
for name in sorted(set(similar_swcs)):
|
|
378
|
+
o = find_swc(c.models, name)
|
|
379
|
+
if o is not None:
|
|
380
|
+
a2, c2 = c.swc_files(o)
|
|
381
|
+
rel_files += a2 + c2
|
|
382
|
+
terms = terms_of(task)
|
|
383
|
+
for r in runs:
|
|
384
|
+
for a in m.allowed(r.name):
|
|
385
|
+
terms |= terms_of(f"{a.port or ''} {a.element or ''}")
|
|
386
|
+
terms.add(r.name.lower())
|
|
387
|
+
res = similar_changes(c.root, ax + cf, sorted(set(rel_files) - set(ax + cf)), terms)
|
|
388
|
+
lines, src = [], []
|
|
389
|
+
for ch in res["changes"]:
|
|
390
|
+
lines.append(f"[commit {ch['commit']} score {ch['score']}: {ch['subject']}; overlap: {', '.join(ch['overlap'][:10])}]")
|
|
391
|
+
for d in ch["arxml"]:
|
|
392
|
+
lines.append(f" ARXML {d['file']}: " + (d.get("status") or d.get("error") or json.dumps(d.get("summary"))))
|
|
393
|
+
lines.extend(f" {x}" for x in d.get("items", []))
|
|
394
|
+
for h in ch["c"]:
|
|
395
|
+
lines.append(f" C {h['file']}:\n" + "\n".join(" " + x for x in h["diff"].splitlines()))
|
|
396
|
+
src.append({"kind": "git-commit", "ref": ch["commit"], "score": ch["score"]})
|
|
397
|
+
if not lines:
|
|
398
|
+
lines = [f"(no similar past change: {'; '.join(res['notes']) or 'nothing matched'})"]
|
|
399
|
+
return "\n".join(lines), src
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
# ---------------------------------------------------------------- assembly
|
|
403
|
+
def build_context(root: str, swc: str, task: str, runnable: Optional[str] = None, budget_tokens: int = 24000,
|
|
404
|
+
history: bool = True) -> dict:
|
|
405
|
+
c = Ctx(root)
|
|
406
|
+
m = find_swc(c.models, swc)
|
|
407
|
+
if m is None:
|
|
408
|
+
return {"error": f"no SW-COMPONENT-TYPE {swc!r}; known: {', '.join(x.name for x in c.models) or 'none'}"}
|
|
409
|
+
if runnable:
|
|
410
|
+
r = m.runnable(runnable)
|
|
411
|
+
if r is None:
|
|
412
|
+
return {"error": f"{m.name} has no runnable {runnable!r}; runnables: {', '.join(x.name for x in m.runnables)} "
|
|
413
|
+
"(to add a runnable, add the RUNNABLE-ENTITY + event with arxml_edit first)"}
|
|
414
|
+
runs = [r]
|
|
415
|
+
else:
|
|
416
|
+
runs = list(m.runnables)
|
|
417
|
+
header = find_header(root, m.name, sorted(c.headers()))
|
|
418
|
+
s_swc, src_swc = sec_swc(c, m, runs)
|
|
419
|
+
s_api, src_api = sec_api(c, m, runs, header)
|
|
420
|
+
s_code, src_code, missing = sec_code(c, runs)
|
|
421
|
+
s_cal, src_cal = sec_callees(c, runs)
|
|
422
|
+
s_cfg, src_cfg, _t = sec_config(c, m, runs, header)
|
|
423
|
+
s_sim, src_sim, sim_swcs = sec_similar(c, m, runs, task)
|
|
424
|
+
if history:
|
|
425
|
+
s_hist, src_hist = sec_history(c, m, runs, task, sim_swcs)
|
|
426
|
+
else:
|
|
427
|
+
s_hist, src_hist = "(history disabled)", []
|
|
428
|
+
sections = [
|
|
429
|
+
{"id": "task", "title": "1. TASK", "variants": [task.strip()], "sources": []},
|
|
430
|
+
{"id": "swc", "title": f"2. SW-C DEFINITION ({m.name})", "variants": s_swc, "sources": src_swc},
|
|
431
|
+
{"id": "api", "title": "3. ALLOWED RTE API", "variants": [s_api], "sources": src_api},
|
|
432
|
+
{"id": "code", "title": "4. CURRENT RUNNABLE CODE", "variants": s_code, "sources": src_code},
|
|
433
|
+
{"id": "callees", "title": "5. FUNCTIONS IT CALLS", "variants": s_cal, "sources": src_cal},
|
|
434
|
+
{"id": "config", "title": "6. RELATED CONFIGURATION (Com signals / NvM blocks)", "variants": s_cfg, "sources": src_cfg},
|
|
435
|
+
{"id": "similar", "title": "7. FUZZY: SIMILAR FUNCTIONS IN OTHER SW-Cs", "variants": [s_sim, ""], "sources": src_sim},
|
|
436
|
+
{"id": "history", "title": "8. FUZZY: SIMILAR PAST CHANGES", "variants": [s_hist, ""], "sources": src_hist},
|
|
437
|
+
]
|
|
438
|
+
mode_names = {"similar": ["full", "dropped"], "history": ["full", "dropped"],
|
|
439
|
+
"callees": ["bodies", "prototypes", "names"], "code": ["full", "truncated", "head"],
|
|
440
|
+
"config": ["full", "truncated"], "swc": ["full", "compact"]}
|
|
441
|
+
level = {s["id"]: 0 for s in sections}
|
|
442
|
+
steps = [("history", 1), ("similar", 1), ("callees", 1), ("code", 1), ("config", 1), ("callees", 2),
|
|
443
|
+
("code", 2), ("swc", 1)]
|
|
444
|
+
budget = max(1, budget_tokens) * CHARS_PER_TOKEN
|
|
445
|
+
applied = []
|
|
446
|
+
|
|
447
|
+
def render() -> str:
|
|
448
|
+
parts = [f"# AUTOSAR context: {m.name}" + (f" / {runs[0].name}" if len(runs) == 1 else " (all runnables)")]
|
|
449
|
+
for s in sections:
|
|
450
|
+
v = s["variants"][min(level[s["id"]], len(s["variants"]) - 1)]
|
|
451
|
+
if v == "":
|
|
452
|
+
parts.append(f"## {s['title']}\n(dropped: context budget)")
|
|
453
|
+
else:
|
|
454
|
+
parts.append(f"## {s['title']}\n{v}")
|
|
455
|
+
return "\n\n".join(parts) + "\n"
|
|
456
|
+
|
|
457
|
+
text = render()
|
|
458
|
+
for sid, lv in steps:
|
|
459
|
+
if len(text) <= budget:
|
|
460
|
+
break
|
|
461
|
+
sec = next(s for s in sections if s["id"] == sid)
|
|
462
|
+
if lv < len(sec["variants"]) and level[sid] < lv:
|
|
463
|
+
level[sid] = lv
|
|
464
|
+
applied.append(f"{sid}->{mode_names[sid][lv]}")
|
|
465
|
+
text = render()
|
|
466
|
+
manifest = []
|
|
467
|
+
for s in sections:
|
|
468
|
+
mode = mode_names.get(s["id"], ["full"])[level[s["id"]]]
|
|
469
|
+
for x in s["sources"]:
|
|
470
|
+
manifest.append({"section": s["id"], **x, "included": mode})
|
|
471
|
+
out = {"text": text, "manifest": manifest,
|
|
472
|
+
"sections": [{"id": s["id"], "title": s["title"], "mode": mode_names.get(s["id"], ["full"])[level[s["id"]]],
|
|
473
|
+
"chars": len(s["variants"][min(level[s["id"]], len(s["variants"]) - 1)])} for s in sections],
|
|
474
|
+
"swc": m.name, "runnables": [r.name for r in runs], "budgetTokens": budget_tokens,
|
|
475
|
+
"estimatedTokens": (len(text) + CHARS_PER_TOKEN - 1) // CHARS_PER_TOKEN, "reductions": applied,
|
|
476
|
+
"sha1": _h(text)}
|
|
477
|
+
if len(text) > budget:
|
|
478
|
+
out["overBudget"] = True
|
|
479
|
+
if missing:
|
|
480
|
+
out["codeMissing"] = missing
|
|
481
|
+
return out
|