@h2loop/autosar 1.0.1 → 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 +328 -48
- package/python/ecuc_engine/cli.py +114 -5
- 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/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 +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/sysmodel.py +152 -0
- package/python/ecuc_engine/validate.py +15 -2
- package/python/ecuc_engine/verify_rte.py +445 -0
- package/python/pyproject.toml +1 -1
- package/skills/autosar/SKILL.md +47 -1
- package/skills/autosar/references/codegen.md +30 -0
- package/skills/autosar/references/project-layout.md +7 -1
- package/skills/autosar/references/runnable-code.md +50 -0
|
@@ -0,0 +1,935 @@
|
|
|
1
|
+
"""Expected RTE API per SW-C and runnable, derived from the SWC description ARXML (pure model layer).
|
|
2
|
+
|
|
3
|
+
Everything is found by following references from the SW-COMPONENT-TYPE: ports -> port interfaces -> data
|
|
4
|
+
elements / operations / mode groups / parameters / triggers, the SWC-INTERNAL-BEHAVIOR's runnables and their
|
|
5
|
+
access points, inter-runnable variables, per-instance memory, internal calibration parameters and exclusive
|
|
6
|
+
areas; data types through IMPLEMENTATION-DATA-TYPEs and the behaviour's DATA-TYPE-MAPPING-SETs. No name or
|
|
7
|
+
layout of any particular project is assumed.
|
|
8
|
+
|
|
9
|
+
Three tiers per API name (AUTOSAR CP R25-11 SWS_RTE, chapter 5.6):
|
|
10
|
+
allowed the runnable has the access point the API's "Existence" requirement names (e.g. a dataSendPoint
|
|
11
|
+
for Rte_Write, SWS_Rte_01280); only these may be called from that runnable
|
|
12
|
+
(SWS_Rte_CONSTR_09017/09083/... "may only be used by the runnable that contains ...")
|
|
13
|
+
generated some runnable of the SW-C is allowed it, so the RTE generator emits it into Rte_<Swc>.h
|
|
14
|
+
derivable the port/element (or IRV, parameter, ...) exists with a compatible direction and semantics, so the
|
|
15
|
+
API could be enabled by adding an access point; calling it now does not compile
|
|
16
|
+
Anything else is not an API of this SW-C.
|
|
17
|
+
|
|
18
|
+
Each entry carries a full C signature by the SWS_RTE parameter-passing rules (IN primitive by value
|
|
19
|
+
SWS_Rte_01017, IN struct/union by const reference SWS_Rte_01018/07086, IN array as pointer to the base type
|
|
20
|
+
SWS_Rte_05107, OUT by reference SWS_Rte_01019/07082/05108, return values SWS_Rte_07069..07073,
|
|
21
|
+
Rte_Instance first for multiple instantiation), so a header generator can emit Rte_<Swc>.h from this model.
|
|
22
|
+
Matching against an existing generated Rte_<Swc>.h is the separate, optional ``match_header`` step.
|
|
23
|
+
"""
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import os
|
|
27
|
+
import re
|
|
28
|
+
from dataclasses import dataclass, field
|
|
29
|
+
from typing import Optional
|
|
30
|
+
|
|
31
|
+
from . import paths
|
|
32
|
+
from .xmltree import Node, parse
|
|
33
|
+
|
|
34
|
+
SWC_TAGS = ("APPLICATION-SW-COMPONENT-TYPE", "SENSOR-ACTUATOR-SW-COMPONENT-TYPE", "SERVICE-SW-COMPONENT-TYPE",
|
|
35
|
+
"COMPLEX-DEVICE-DRIVER-SW-COMPONENT-TYPE", "ECU-ABSTRACTION-SW-COMPONENT-TYPE",
|
|
36
|
+
"NV-BLOCK-SW-COMPONENT-TYPE", "SERVICE-PROXY-SW-COMPONENT-TYPE", "PARAMETER-SW-COMPONENT-TYPE")
|
|
37
|
+
MARKERS = (b"SW-COMPONENT-TYPE", b"INTERFACE>", b"DATA-TYPE", b"MODE-DECLARATION-GROUP", b"SW-BASE-TYPE")
|
|
38
|
+
PRIMITIVE_NAMES = {"boolean", "uint8", "uint16", "uint32", "uint64", "sint8", "sint16", "sint32", "sint64",
|
|
39
|
+
"float32", "float64", "uint8_least", "uint16_least", "uint32_least", "sint8_least",
|
|
40
|
+
"sint16_least", "sint32_least", "Std_ReturnType"}
|
|
41
|
+
|
|
42
|
+
# API kind -> (signature requirement, existence requirement)
|
|
43
|
+
CITE = {
|
|
44
|
+
"Write": ("SWS_Rte_01071", "SWS_Rte_01280"), "Send": ("SWS_Rte_01072", "SWS_Rte_01281"),
|
|
45
|
+
"Switch": ("SWS_Rte_02631", "SWS_Rte_02632"), "Invalidate": ("SWS_Rte_01206", "SWS_Rte_01282"),
|
|
46
|
+
"Feedback": ("SWS_Rte_01083", "SWS_Rte_01285"), "SwitchAck": ("SWS_Rte_02725", "SWS_Rte_02677"),
|
|
47
|
+
"Read": ("SWS_Rte_01091", "SWS_Rte_01289"), "DRead": ("SWS_Rte_07394", "SWS_Rte_07395"),
|
|
48
|
+
"Receive": ("SWS_Rte_01092", "SWS_Rte_01288"), "Call": ("SWS_Rte_01102", "SWS_Rte_01293/01294"),
|
|
49
|
+
"Result": ("SWS_Rte_01111", "SWS_Rte_01296/01297"), "Pim": ("SWS_Rte_01118", "SWS_Rte_01299"),
|
|
50
|
+
"CData": ("SWS_Rte_01252", "SWS_Rte_01300"), "Prm": ("SWS_Rte_03928", "SWS_Rte_03929"),
|
|
51
|
+
"IRead": ("SWS_Rte_03741", "SWS_Rte_01301"), "IReadRef": ("SWS_Rte_04577", "SWS_Rte_04578"),
|
|
52
|
+
"IWrite": ("SWS_Rte_03744", "SWS_Rte_01302"), "IWriteRef": ("SWS_Rte_05509", "SWS_Rte_05510"),
|
|
53
|
+
"IInvalidate": ("SWS_Rte_03800", "SWS_Rte_03801"), "IStatus": ("SWS_Rte_02599", "SWS_Rte_02600"),
|
|
54
|
+
"IrvIRead": ("SWS_Rte_03550", "SWS_Rte_01303"), "IrvIReadRef": ("SWS_Rte_04579", "SWS_Rte_04580"),
|
|
55
|
+
"IrvIWrite": ("SWS_Rte_03553", "SWS_Rte_01304"), "IrvIWriteRef": ("SWS_Rte_06207", "SWS_Rte_06208"),
|
|
56
|
+
"IrvRead": ("SWS_Rte_03560", "SWS_Rte_01305"), "IrvWrite": ("SWS_Rte_03565", "SWS_Rte_01306"),
|
|
57
|
+
"Enter": ("SWS_Rte_01120", "SWS_Rte_01307"), "Exit": ("SWS_Rte_01123", "SWS_Rte_01308"),
|
|
58
|
+
"Mode": ("SWS_Rte_02628", "SWS_Rte_02629"), "Trigger": ("SWS_Rte_07200", "SWS_Rte_07201"),
|
|
59
|
+
"IrTrigger": ("SWS_Rte_07203", "SWS_Rte_07204"), "IsUpdated": ("SWS_Rte_07390", "SWS_Rte_07391"),
|
|
60
|
+
}
|
|
61
|
+
# longest first, so "IrvIReadRef" is tried before "IrvIRead" before "IrvRead"
|
|
62
|
+
KINDS = sorted(CITE, key=len, reverse=True)
|
|
63
|
+
PER_RUNNABLE = {"IRead", "IReadRef", "IWrite", "IWriteRef", "IInvalidate", "IStatus", "IrvIRead", "IrvIReadRef",
|
|
64
|
+
"IrvIWrite", "IrvIWriteRef", "IrvRead", "IrvWrite", "IrTrigger"}
|
|
65
|
+
ROLE = { # access-point collection -> role name used in reports and suggestions
|
|
66
|
+
"DATA-SEND-POINTS": "dataSendPoint", "DATA-RECEIVE-POINT-BY-ARGUMENTS": "dataReceivePointByArgument",
|
|
67
|
+
"DATA-RECEIVE-POINT-BY-VALUES": "dataReceivePointByValue", "DATA-READ-ACCESSS": "dataReadAccess",
|
|
68
|
+
"DATA-WRITE-ACCESSS": "dataWriteAccess", "SERVER-CALL-POINTS": "serverCallPoint",
|
|
69
|
+
"ASYNCHRONOUS-SERVER-CALL-RESULT-POINTS": "asynchronousServerCallResultPoint",
|
|
70
|
+
"MODE-ACCESS-POINTS": "modeAccessPoint", "MODE-SWITCH-POINTS": "modeSwitchPoint",
|
|
71
|
+
"PARAMETER-ACCESSS": "parameterAccess", "READ-LOCAL-VARIABLES": "readLocalVariable",
|
|
72
|
+
"WRITTEN-LOCAL-VARIABLES": "writtenLocalVariable", "EXTERNAL-TRIGGERING-POINTS": "externalTriggeringPoint",
|
|
73
|
+
"INTERNAL-TRIGGERING-POINTS": "internalTriggeringPoint",
|
|
74
|
+
}
|
|
75
|
+
COLLECTION = {v: k for k, v in ROLE.items()}
|
|
76
|
+
INVALIDATING = {"KEEP", "REPLACE", "EXTERNAL-REPLACEMENT"}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# ---------------------------------------------------------------- ARXML index
|
|
80
|
+
class ArIndex:
|
|
81
|
+
"""Identifiables of all non-ECUC ARXML that can hold SWC descriptions, interfaces and data types."""
|
|
82
|
+
|
|
83
|
+
def __init__(self, root: str, files: Optional[list] = None):
|
|
84
|
+
self.root = root
|
|
85
|
+
self.by_path: dict = {}
|
|
86
|
+
self.path_of: dict = {}
|
|
87
|
+
self.file_of: dict = {}
|
|
88
|
+
self.files: list = []
|
|
89
|
+
for f in sorted(files if files is not None else paths.walk(root, paths.is_arxml).files):
|
|
90
|
+
try:
|
|
91
|
+
data = open(f, "rb").read()
|
|
92
|
+
except OSError:
|
|
93
|
+
continue
|
|
94
|
+
if not any(m in data for m in MARKERS):
|
|
95
|
+
continue
|
|
96
|
+
try:
|
|
97
|
+
doc = parse(data)
|
|
98
|
+
except Exception: # noqa: BLE001 (malformed file: skipped, other files still indexed)
|
|
99
|
+
continue
|
|
100
|
+
self.files.append(f)
|
|
101
|
+
stack = [(doc, "")]
|
|
102
|
+
while stack:
|
|
103
|
+
n, prefix = stack.pop()
|
|
104
|
+
sn = n.short_name
|
|
105
|
+
p = f"{prefix}/{sn}" if sn is not None else prefix
|
|
106
|
+
if sn is not None:
|
|
107
|
+
self.by_path.setdefault(p, n)
|
|
108
|
+
self.path_of[id(n)] = p
|
|
109
|
+
self.file_of[id(n)] = f
|
|
110
|
+
stack.extend((c, p) for c in reversed(n.children))
|
|
111
|
+
|
|
112
|
+
def get(self, path: Optional[str]) -> Optional[Node]:
|
|
113
|
+
return self.by_path.get(path.strip()) if path else None
|
|
114
|
+
|
|
115
|
+
def path(self, n: Node) -> str:
|
|
116
|
+
return self.path_of.get(id(n), "")
|
|
117
|
+
|
|
118
|
+
def where(self, n: Node) -> tuple:
|
|
119
|
+
return self.file_of.get(id(n)), n.line
|
|
120
|
+
|
|
121
|
+
def of_tag(self, *tags) -> list:
|
|
122
|
+
return [n for p, n in sorted(self.by_path.items()) if n.tag in tags]
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _desc(n: Node, tag: str) -> Optional[Node]:
|
|
126
|
+
for c in n.iter():
|
|
127
|
+
if c is not n and c.tag == tag:
|
|
128
|
+
return c
|
|
129
|
+
return None
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _desc_text(n: Node, tag: str) -> Optional[str]:
|
|
133
|
+
c = _desc(n, tag)
|
|
134
|
+
return c.text.strip() if c is not None else None
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _own(n: Node, coll: str) -> list:
|
|
138
|
+
"""Children of the collection element ``coll`` directly under n (identifiables in an aggregation)."""
|
|
139
|
+
c = n.child(coll)
|
|
140
|
+
return list(c.children) if c is not None else []
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _ref_ending(n: Node, *suffixes) -> Optional[str]:
|
|
144
|
+
for c in n.iter():
|
|
145
|
+
if c is not n and c.tag.endswith(suffixes):
|
|
146
|
+
t = c.text.strip()
|
|
147
|
+
if t:
|
|
148
|
+
return t
|
|
149
|
+
return None
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
# ---------------------------------------------------------------- types
|
|
153
|
+
@dataclass
|
|
154
|
+
class TypeInfo:
|
|
155
|
+
name: str
|
|
156
|
+
kind: str # primitive | struct | union | array | pointer | unknown
|
|
157
|
+
path: Optional[str] = None
|
|
158
|
+
base: Optional[str] = None # array element C type
|
|
159
|
+
note: Optional[str] = None
|
|
160
|
+
|
|
161
|
+
def to_dict(self):
|
|
162
|
+
return {k: v for k, v in self.__dict__.items() if v is not None}
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class Types:
|
|
166
|
+
def __init__(self, ix: ArIndex):
|
|
167
|
+
self.ix = ix
|
|
168
|
+
self._maps: dict = {}
|
|
169
|
+
|
|
170
|
+
def mapping(self, set_paths: list) -> tuple:
|
|
171
|
+
key = tuple(set_paths)
|
|
172
|
+
if key not in self._maps:
|
|
173
|
+
app, mode = {}, {}
|
|
174
|
+
sets = [self.ix.get(p) for p in set_paths] if set_paths else self.ix.of_tag("DATA-TYPE-MAPPING-SET")
|
|
175
|
+
for s in [x for x in sets if x is not None]:
|
|
176
|
+
for m in _own(s, "DATA-TYPE-MAPS"):
|
|
177
|
+
a, i = m.child_text("APPLICATION-DATA-TYPE-REF"), m.child_text("IMPLEMENTATION-DATA-TYPE-REF")
|
|
178
|
+
if a and i:
|
|
179
|
+
app.setdefault(a, i)
|
|
180
|
+
for m in _own(s, "MODE-REQUEST-TYPE-MAPS"):
|
|
181
|
+
g, i = m.child_text("MODE-GROUP-REF"), m.child_text("IMPLEMENTATION-DATA-TYPE-REF")
|
|
182
|
+
if g and i:
|
|
183
|
+
mode.setdefault(g, i)
|
|
184
|
+
self._maps[key] = (app, mode)
|
|
185
|
+
return self._maps[key]
|
|
186
|
+
|
|
187
|
+
def resolve(self, ref: Optional[str], maps: list, depth: int = 0) -> TypeInfo:
|
|
188
|
+
if not ref:
|
|
189
|
+
return TypeInfo("void", "unknown", note="no type reference")
|
|
190
|
+
n = self.ix.get(ref)
|
|
191
|
+
name = ref.rstrip("/").rsplit("/", 1)[-1]
|
|
192
|
+
if n is None:
|
|
193
|
+
return TypeInfo(name, "primitive" if name in PRIMITIVE_NAMES else "unknown", ref,
|
|
194
|
+
note=f"type {ref} not found in the project ARXML")
|
|
195
|
+
if n.tag.startswith("APPLICATION-") and n.tag.endswith("-DATA-TYPE"):
|
|
196
|
+
impl = self.mapping(maps)[0].get(ref) or (self.mapping([])[0].get(ref) if maps else None)
|
|
197
|
+
if impl is None:
|
|
198
|
+
return TypeInfo(name, "unknown", ref, note=f"application data type {name} has no DataTypeMap to an "
|
|
199
|
+
"ImplementationDataType (SWS_Rte_07069 needs one)")
|
|
200
|
+
return self.resolve(impl, maps, depth + 1)
|
|
201
|
+
cat = (n.child_text("CATEGORY") or "VALUE").upper()
|
|
202
|
+
if cat == "TYPE_REFERENCE" and depth < 8: # redefinition: passed like what it redefines (SWS_Rte_07084)
|
|
203
|
+
inner = self.resolve(_desc_text(n, "IMPLEMENTATION-DATA-TYPE-REF"), maps, depth + 1)
|
|
204
|
+
return TypeInfo(name, inner.kind, ref, inner.base, inner.note)
|
|
205
|
+
if cat in ("STRUCTURE", "UNION"):
|
|
206
|
+
return TypeInfo(name, cat.lower() if cat == "UNION" else "struct", ref)
|
|
207
|
+
if cat == "ARRAY":
|
|
208
|
+
el = _desc(n, "IMPLEMENTATION-DATA-TYPE-ELEMENT")
|
|
209
|
+
base = None
|
|
210
|
+
if el is not None:
|
|
211
|
+
r = _desc_text(el, "IMPLEMENTATION-DATA-TYPE-REF")
|
|
212
|
+
if r:
|
|
213
|
+
base = r.rsplit("/", 1)[-1]
|
|
214
|
+
else:
|
|
215
|
+
b = self.ix.get(_desc_text(el, "BASE-TYPE-REF"))
|
|
216
|
+
base = (_desc_text(b, "NATIVE-DECLARATION") or b.short_name) if b is not None else None
|
|
217
|
+
return TypeInfo(name, "array", ref, base)
|
|
218
|
+
if cat == "DATA_REFERENCE":
|
|
219
|
+
return TypeInfo(name, "pointer", ref)
|
|
220
|
+
return TypeInfo(name, "primitive", ref)
|
|
221
|
+
|
|
222
|
+
def mode_type(self, group_proto: Optional[Node], maps: list) -> TypeInfo:
|
|
223
|
+
if group_proto is None:
|
|
224
|
+
return TypeInfo("uint8", "primitive", note="mode group not found; uint8 assumed")
|
|
225
|
+
mdg = group_proto.child_text("TYPE-TREF")
|
|
226
|
+
impl = self.mapping(maps)[1].get(mdg or "") or self.mapping([])[1].get(mdg or "")
|
|
227
|
+
if impl:
|
|
228
|
+
t = self.resolve(impl, maps)
|
|
229
|
+
t.note = None
|
|
230
|
+
return t
|
|
231
|
+
return TypeInfo("uint8", "primitive", note=f"no ModeRequestTypeMap for {mdg}; the <mode> type is the "
|
|
232
|
+
"ImplementationDataType mapped to the ModeDeclarationGroup (SWS_Rte_02632 description); uint8 assumed")
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def in_param(t: TypeInfo, name: str = "data") -> str:
|
|
236
|
+
if t.kind in ("struct", "union"):
|
|
237
|
+
return f"const {t.name}* {name}" # SWS_Rte_01018, SWS_Rte_07086
|
|
238
|
+
if t.kind == "array":
|
|
239
|
+
return f"const {t.base or t.name}* {name}" # SWS_Rte_05107, SWS_Rte_07086
|
|
240
|
+
return f"{t.name} {name}" # SWS_Rte_01017 (and DATA_REFERENCE: the pointer value itself)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def out_param(t: TypeInfo, name: str = "data") -> str:
|
|
244
|
+
if t.kind == "array":
|
|
245
|
+
return f"{t.base or t.name}* {name}" # SWS_Rte_05108
|
|
246
|
+
return f"{t.name}* {name}" # SWS_Rte_01019, SWS_Rte_07082, SWS_Rte_07083
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def ret_of(t: TypeInfo) -> str:
|
|
250
|
+
if t.kind in ("struct", "union"):
|
|
251
|
+
return f"const {t.name}*" # SWS_Rte_07071, SWS_Rte_08300
|
|
252
|
+
if t.kind == "array":
|
|
253
|
+
return f"const {t.base or t.name}*" # SWS_Rte_07072
|
|
254
|
+
return t.name # SWS_Rte_07070, SWS_Rte_07073
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
# ---------------------------------------------------------------- model
|
|
258
|
+
@dataclass
|
|
259
|
+
class ApiEntry:
|
|
260
|
+
name: str
|
|
261
|
+
api: str
|
|
262
|
+
ret: str
|
|
263
|
+
params: list # [{"name", "type", "dir"}]
|
|
264
|
+
target: str # what the API accesses: AUTOSAR path of the data element / operation / IRV / ...
|
|
265
|
+
port: Optional[str] = None
|
|
266
|
+
port_path: Optional[str] = None
|
|
267
|
+
element: Optional[str] = None
|
|
268
|
+
runnable: Optional[str] = None # the <re> of per-runnable APIs (IRead, IrvWrite, ...)
|
|
269
|
+
type: Optional[TypeInfo] = None
|
|
270
|
+
semantics: Optional[str] = None # data | event (S/R)
|
|
271
|
+
access: list = field(default_factory=list) # [{"runnable", "role", "point"}]
|
|
272
|
+
any_runnable: bool = False # Rte_Pim: every runnable of the SW-C
|
|
273
|
+
condition: Optional[str] = None # extra configuration the API depends on
|
|
274
|
+
|
|
275
|
+
@property
|
|
276
|
+
def cite(self) -> str:
|
|
277
|
+
s, e = CITE[self.api]
|
|
278
|
+
return f"{s} (signature), {e} (existence)"
|
|
279
|
+
|
|
280
|
+
@property
|
|
281
|
+
def signature(self) -> str:
|
|
282
|
+
ps = ", ".join(p["type"] for p in self.params) or "void"
|
|
283
|
+
return f"{self.ret} {self.name}({ps})"
|
|
284
|
+
|
|
285
|
+
def allowed_for(self, runnable: str) -> bool:
|
|
286
|
+
return self.any_runnable or any(a["runnable"] == runnable for a in self.access)
|
|
287
|
+
|
|
288
|
+
@property
|
|
289
|
+
def generated(self) -> bool:
|
|
290
|
+
return self.any_runnable or bool(self.access)
|
|
291
|
+
|
|
292
|
+
def to_dict(self, brief: bool = False) -> dict:
|
|
293
|
+
d = {"name": self.name, "api": self.api, "signature": self.signature, "cite": self.cite, "target": self.target}
|
|
294
|
+
if not brief:
|
|
295
|
+
d.update({"ret": self.ret, "params": self.params})
|
|
296
|
+
for k in ("port", "element", "runnable", "semantics", "condition"):
|
|
297
|
+
if getattr(self, k) is not None:
|
|
298
|
+
d[k] = getattr(self, k)
|
|
299
|
+
if self.type is not None:
|
|
300
|
+
d["type"] = self.type.to_dict()
|
|
301
|
+
d["access"] = self.access
|
|
302
|
+
d["generated"] = self.generated
|
|
303
|
+
return d
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
@dataclass
|
|
307
|
+
class Runnable:
|
|
308
|
+
name: str
|
|
309
|
+
path: str
|
|
310
|
+
symbol: str
|
|
311
|
+
file: str
|
|
312
|
+
line: int
|
|
313
|
+
events: list = field(default_factory=list)
|
|
314
|
+
points: list = field(default_factory=list) # [{"role", "point", "port", "target"}]
|
|
315
|
+
concurrent: bool = False
|
|
316
|
+
signature: str = "" # the runnable entry point (SWS_Rte_01126 / SWS_Rte_01166 for servers)
|
|
317
|
+
exclusive_areas: list = field(default_factory=list)
|
|
318
|
+
|
|
319
|
+
def to_dict(self) -> dict:
|
|
320
|
+
return {"name": self.name, "path": self.path, "symbol": self.symbol, "at": f"{self.file}:{self.line}",
|
|
321
|
+
"signature": self.signature, "events": self.events, "accessPoints": self.points,
|
|
322
|
+
"canBeInvokedConcurrently": self.concurrent}
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
@dataclass
|
|
326
|
+
class SwcModel:
|
|
327
|
+
name: str
|
|
328
|
+
path: str
|
|
329
|
+
tag: str
|
|
330
|
+
file: str
|
|
331
|
+
line: int
|
|
332
|
+
behavior: Optional[str]
|
|
333
|
+
multiple_instantiation: bool
|
|
334
|
+
ports: list
|
|
335
|
+
runnables: list
|
|
336
|
+
apis: dict # name -> ApiEntry (every derivable API)
|
|
337
|
+
types: dict # type name -> TypeInfo dict (data types used by the SW-C)
|
|
338
|
+
notes: list = field(default_factory=list)
|
|
339
|
+
unresolved: list = field(default_factory=list) # access points whose reference does not resolve
|
|
340
|
+
|
|
341
|
+
def runnable(self, name_or_symbol: str) -> Optional[Runnable]:
|
|
342
|
+
for r in self.runnables:
|
|
343
|
+
if name_or_symbol in (r.name, r.symbol):
|
|
344
|
+
return r
|
|
345
|
+
return None
|
|
346
|
+
|
|
347
|
+
def allowed(self, runnable: str) -> list:
|
|
348
|
+
return [a for a in self.apis.values() if a.allowed_for(runnable)]
|
|
349
|
+
|
|
350
|
+
def generated(self) -> list:
|
|
351
|
+
return [a for a in self.apis.values() if a.generated]
|
|
352
|
+
|
|
353
|
+
def classify(self, name: str, runnable: Optional[str]) -> tuple:
|
|
354
|
+
"""-> (status, entry|None, reason). status: OK | NOT-ALLOWED | INVENTED."""
|
|
355
|
+
e = self.apis.get(name)
|
|
356
|
+
if e is None:
|
|
357
|
+
return "INVENTED", None, explain_unknown(self, name)
|
|
358
|
+
if runnable is None:
|
|
359
|
+
return ("OK" if e.generated else "NOT-ALLOWED"), e, (
|
|
360
|
+
"generated for this SW-C" if e.generated else "derivable, but no runnable has the access point")
|
|
361
|
+
if e.allowed_for(runnable):
|
|
362
|
+
return "OK", e, f"{runnable} has the access point"
|
|
363
|
+
others = sorted({a["runnable"] for a in e.access})
|
|
364
|
+
return "NOT-ALLOWED", e, (f"{name} exists on {self.name} but runnable {runnable} has no "
|
|
365
|
+
f"{needed_role(e)} for it" + (f" (only {', '.join(others)} do)" if others else
|
|
366
|
+
" (no runnable has one, so it is not generated)"))
|
|
367
|
+
|
|
368
|
+
def to_dict(self, runnable: Optional[str] = None) -> dict:
|
|
369
|
+
d = {"swc": self.name, "path": self.path, "tag": self.tag, "at": f"{self.file}:{self.line}",
|
|
370
|
+
"behavior": self.behavior, "multipleInstantiation": self.multiple_instantiation, "ports": self.ports,
|
|
371
|
+
"runnables": [r.to_dict() for r in self.runnables if runnable in (None, r.name, r.symbol)],
|
|
372
|
+
"counts": {"derivable": len(self.apis), "generated": len(self.generated())}}
|
|
373
|
+
if runnable:
|
|
374
|
+
r = self.runnable(runnable)
|
|
375
|
+
d["allowed"] = [a.to_dict() for a in self.allowed(r.name)] if r else []
|
|
376
|
+
else:
|
|
377
|
+
d["allowedByRunnable"] = {r.name: sorted(a.name for a in self.allowed(r.name)) for r in self.runnables}
|
|
378
|
+
d["apis"] = [a.to_dict() for a in sorted(self.apis.values(), key=lambda a: a.name)]
|
|
379
|
+
if self.notes:
|
|
380
|
+
d["notes"] = self.notes
|
|
381
|
+
if self.unresolved:
|
|
382
|
+
d["unresolved"] = self.unresolved
|
|
383
|
+
return d
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def needed_role(e: ApiEntry) -> str:
|
|
387
|
+
return {"Write": "dataSendPoint", "Send": "dataSendPoint", "Invalidate": "dataSendPoint",
|
|
388
|
+
"Feedback": "dataSendPoint", "Read": "dataReceivePointByArgument",
|
|
389
|
+
"Receive": "dataReceivePointByArgument", "IsUpdated": "dataReceivePointByArgument",
|
|
390
|
+
"DRead": "dataReceivePointByValue", "IRead": "dataReadAccess", "IReadRef": "dataReadAccess",
|
|
391
|
+
"IStatus": "dataReadAccess", "IWrite": "dataWriteAccess", "IWriteRef": "dataWriteAccess",
|
|
392
|
+
"IInvalidate": "dataWriteAccess", "Call": "serverCallPoint",
|
|
393
|
+
"Result": "asynchronousServerCallResultPoint", "Mode": "modeAccessPoint", "Switch": "modeSwitchPoint",
|
|
394
|
+
"SwitchAck": "modeSwitchPoint", "Prm": "parameterAccess", "CData": "parameterAccess",
|
|
395
|
+
"IrvRead": "readLocalVariable", "IrvIRead": "readLocalVariable", "IrvIReadRef": "readLocalVariable",
|
|
396
|
+
"IrvWrite": "writtenLocalVariable", "IrvIWrite": "writtenLocalVariable",
|
|
397
|
+
"IrvIWriteRef": "writtenLocalVariable", "Enter": "canEnterExclusiveArea",
|
|
398
|
+
"Exit": "canEnterExclusiveArea", "Trigger": "externalTriggeringPoint",
|
|
399
|
+
"IrTrigger": "internalTriggeringPoint"}.get(e.api, "access point")
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def split_name(name: str) -> tuple:
|
|
403
|
+
"""'Rte_IWrite_Re_Port_El' -> ('IWrite', 'Re_Port_El'); unknown kinds -> (None, rest)."""
|
|
404
|
+
if not name.startswith("Rte_"):
|
|
405
|
+
return None, name
|
|
406
|
+
rest = name[4:]
|
|
407
|
+
if rest.startswith("Byps_"):
|
|
408
|
+
rest = rest[5:]
|
|
409
|
+
for k in KINDS:
|
|
410
|
+
if rest.startswith(k + "_"):
|
|
411
|
+
return k, rest[len(k) + 1:]
|
|
412
|
+
return None, rest
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def explain_unknown(m: SwcModel, name: str) -> str:
|
|
416
|
+
kind, rest = split_name(name)
|
|
417
|
+
if kind is None:
|
|
418
|
+
return f"{name} is not an RTE API name pattern of SWS_RTE 5.6 (Rte_<Write|Read|Call|...>_...)"
|
|
419
|
+
if kind in PER_RUNNABLE:
|
|
420
|
+
re_names = [r.name for r in m.runnables if rest.startswith(r.name + "_") or rest == r.name]
|
|
421
|
+
if not re_names:
|
|
422
|
+
return (f"{name}: Rte_{kind} names the runnable first (Rte_{kind}_<re>_...); no runnable of {m.name} "
|
|
423
|
+
f"prefixes '{rest}' (runnables: {', '.join(r.name for r in m.runnables)})")
|
|
424
|
+
rest = rest[len(max(re_names, key=len)) + 1:]
|
|
425
|
+
if kind in ("Pim", "CData", "Enter", "Exit") or kind.startswith("Irv") or kind == "IrTrigger":
|
|
426
|
+
return f"{name}: {m.name} declares no {kind} target named '{rest}'"
|
|
427
|
+
ports = [p for p in m.ports if rest.startswith(p["name"] + "_")]
|
|
428
|
+
if not ports:
|
|
429
|
+
return f"{name}: {m.name} has no port that prefixes '{rest}' (ports: {', '.join(p['name'] for p in m.ports)})"
|
|
430
|
+
p = max(ports, key=lambda x: len(x["name"]))
|
|
431
|
+
el = rest[len(p["name"]) + 1:]
|
|
432
|
+
els = [e["name"] for e in p["elements"]]
|
|
433
|
+
if el not in els:
|
|
434
|
+
return f"{name}: interface {p['interface']} of port {p['name']} has no element '{el}' (has: {', '.join(els)})"
|
|
435
|
+
e = next(e for e in p["elements"] if e["name"] == el)
|
|
436
|
+
return (f"{name}: Rte_{kind} does not apply to {p['direction']}-port {p['name']} element {el} "
|
|
437
|
+
f"({p['interfaceKind']}{', ' + e['semantics'] + ' semantics' if e.get('semantics') else ''}"
|
|
438
|
+
f"{'; ' + e['needs'] if e.get('needs') and kind in ('Invalidate', 'IInvalidate', 'IsUpdated') else ''})")
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
class Builder:
|
|
442
|
+
def __init__(self, ix: ArIndex, types: Types):
|
|
443
|
+
self.ix, self.t = ix, types
|
|
444
|
+
|
|
445
|
+
def rel(self, f: Optional[str]) -> str:
|
|
446
|
+
if not f:
|
|
447
|
+
return "?"
|
|
448
|
+
r = os.path.relpath(f, self.ix.root)
|
|
449
|
+
return f if r.startswith("..") else r
|
|
450
|
+
|
|
451
|
+
def build(self, swc: Node) -> SwcModel:
|
|
452
|
+
ix = self.ix
|
|
453
|
+
spath = ix.path(swc)
|
|
454
|
+
f, line = ix.where(swc)
|
|
455
|
+
ib = next(iter(_own(swc, "INTERNAL-BEHAVIORS")), None)
|
|
456
|
+
maps = [r.text.strip() for r in (ib.child("DATA-TYPE-MAPPING-REFS").children if ib is not None and
|
|
457
|
+
ib.child("DATA-TYPE-MAPPING-REFS") is not None else [])]
|
|
458
|
+
multi = (ib.child_text("SUPPORTS-MULTIPLE-INSTANTIATION") or "").lower() == "true" if ib is not None else False
|
|
459
|
+
m = SwcModel(swc.short_name, spath, swc.tag, self.rel(f), line, ix.path(ib) if ib is not None else None,
|
|
460
|
+
multi, [], [], {}, {})
|
|
461
|
+
self.m, self.maps = m, maps
|
|
462
|
+
inst = [{"name": "self", "type": "Rte_Instance self", "dir": "IN"}] if multi else []
|
|
463
|
+
self.inst = inst
|
|
464
|
+
|
|
465
|
+
# ---- ports and everything derivable from them
|
|
466
|
+
for p in _own(swc, "PORTS"):
|
|
467
|
+
direction = {"P-PORT-PROTOTYPE": "P", "R-PORT-PROTOTYPE": "R", "PR-PORT-PROTOTYPE": "PR"}.get(p.tag)
|
|
468
|
+
if direction is None:
|
|
469
|
+
continue
|
|
470
|
+
iref = p.child_text("PROVIDED-INTERFACE-TREF") or p.child_text("REQUIRED-INTERFACE-TREF") or \
|
|
471
|
+
p.child_text("PROVIDED-REQUIRED-INTERFACE-TREF")
|
|
472
|
+
itf = ix.get(iref)
|
|
473
|
+
port = {"name": p.short_name, "path": ix.path(p), "direction": direction, "interface": iref,
|
|
474
|
+
"interfaceKind": itf.tag if itf is not None else "?", "elements": [],
|
|
475
|
+
"at": f"{self.rel(ix.where(p)[0])}:{p.line}"}
|
|
476
|
+
m.ports.append(port)
|
|
477
|
+
if itf is None:
|
|
478
|
+
m.notes.append(f"port {p.short_name}: interface {iref} not found in the project ARXML")
|
|
479
|
+
continue
|
|
480
|
+
self.port_apis(p, port, itf, direction)
|
|
481
|
+
|
|
482
|
+
# ---- internal behaviour: IRVs, PIM, internal parameters, exclusive areas
|
|
483
|
+
irvs = {}
|
|
484
|
+
if ib is not None:
|
|
485
|
+
for coll, kind in (("EXPLICIT-INTER-RUNNABLE-VARIABLES", "explicit"),
|
|
486
|
+
("IMPLICIT-INTER-RUNNABLE-VARIABLES", "implicit")):
|
|
487
|
+
for v in _own(ib, coll):
|
|
488
|
+
irvs[ix.path(v)] = (v, kind)
|
|
489
|
+
for pim in _own(ib, "PER-INSTANCE-MEMORYS"):
|
|
490
|
+
tname = pim.child_text("TYPE") or "void"
|
|
491
|
+
self.add(ApiEntry(f"Rte_Pim_{pim.short_name}", "Pim", f"{tname}*", list(inst), ix.path(pim),
|
|
492
|
+
type=TypeInfo(tname, "unknown"), any_runnable=True))
|
|
493
|
+
for pim in _own(ib, "AR-TYPED-PER-INSTANCE-MEMORYS"):
|
|
494
|
+
t = self.t.resolve(pim.child_text("TYPE-TREF"), maps)
|
|
495
|
+
self.add(ApiEntry(f"Rte_Pim_{pim.short_name}", "Pim", f"{t.name}*", list(inst), ix.path(pim),
|
|
496
|
+
type=t, any_runnable=True))
|
|
497
|
+
for coll in ("PER-INSTANCE-PARAMETERS", "SHARED-PARAMETERS"):
|
|
498
|
+
for prm in _own(ib, coll):
|
|
499
|
+
t = self.t.resolve(prm.child_text("TYPE-TREF"), maps)
|
|
500
|
+
self.add(ApiEntry(f"Rte_CData_{prm.short_name}", "CData", ret_of(t), list(inst), ix.path(prm),
|
|
501
|
+
element=prm.short_name, type=t))
|
|
502
|
+
for ea in _own(ib, "EXCLUSIVE-AREAS"):
|
|
503
|
+
for k in ("Enter", "Exit"):
|
|
504
|
+
self.add(ApiEntry(f"Rte_{k}_{ea.short_name}", k, "void", list(inst), ix.path(ea),
|
|
505
|
+
element=ea.short_name))
|
|
506
|
+
|
|
507
|
+
# ---- runnables, events and their access points
|
|
508
|
+
events: dict = {}
|
|
509
|
+
if ib is not None:
|
|
510
|
+
for ev in _own(ib, "EVENTS"):
|
|
511
|
+
tgt = ev.child_text("START-ON-EVENT-REF")
|
|
512
|
+
if tgt:
|
|
513
|
+
d = {"event": ev.short_name, "kind": ev.tag}
|
|
514
|
+
if ev.child_text("PERIOD"):
|
|
515
|
+
d["period"] = ev.child_text("PERIOD")
|
|
516
|
+
trig = _ref_ending(ev, "TARGET-DATA-ELEMENT-REF", "TARGET-PROVIDED-OPERATION-REF",
|
|
517
|
+
"TARGET-MODE-DECLARATION-REF", "TARGET-REQUIRED-OPERATION-REF", "TARGET-TRIGGER-REF")
|
|
518
|
+
port_ref = _ref_ending(ev, "PORT-REF")
|
|
519
|
+
if trig:
|
|
520
|
+
d["target"] = trig
|
|
521
|
+
if port_ref:
|
|
522
|
+
d["port"] = port_ref.rsplit("/", 1)[-1]
|
|
523
|
+
events.setdefault(tgt, []).append(d)
|
|
524
|
+
for r in (_own(ib, "RUNNABLES") if ib is not None else []):
|
|
525
|
+
rp = ix.path(r)
|
|
526
|
+
run = Runnable(r.short_name, rp, r.child_text("SYMBOL") or r.short_name, self.rel(ix.where(r)[0]), r.line,
|
|
527
|
+
events.get(rp, []),
|
|
528
|
+
concurrent=(r.child_text("CAN-BE-INVOKED-CONCURRENTLY") or "").lower() == "true")
|
|
529
|
+
run.signature = self.entry_signature(run, inst)
|
|
530
|
+
m.runnables.append(run)
|
|
531
|
+
for coll in ROLE:
|
|
532
|
+
for ap in _own(r, coll):
|
|
533
|
+
self.access_point(run, coll, ap, irvs)
|
|
534
|
+
eas = r.child("CAN-ENTER-EXCLUSIVE-AREA-REFS")
|
|
535
|
+
for ref in (eas.children if eas is not None else []):
|
|
536
|
+
ea = ref.text.strip()
|
|
537
|
+
run.exclusive_areas.append(ea)
|
|
538
|
+
for k in ("Enter", "Exit"):
|
|
539
|
+
e = self.m.apis.get(f"Rte_{k}_{ea.rsplit('/', 1)[-1]}")
|
|
540
|
+
if e is not None:
|
|
541
|
+
e.access.append({"runnable": run.name, "role": "canEnterExclusiveArea", "point": ea})
|
|
542
|
+
# per-runnable IRV APIs that exist for every runnable but are allowed only with an access point
|
|
543
|
+
for run in m.runnables:
|
|
544
|
+
for vp, (v, kind) in irvs.items():
|
|
545
|
+
t = self.t.resolve(v.child_text("TYPE-TREF"), maps)
|
|
546
|
+
self.irv_apis(run.name, v.short_name, vp, t, kind)
|
|
547
|
+
for r in m.runnables:
|
|
548
|
+
for pt in r.points:
|
|
549
|
+
if pt.get("unresolved"):
|
|
550
|
+
m.unresolved.append({"runnable": r.name, **pt})
|
|
551
|
+
for e in m.apis.values():
|
|
552
|
+
if e.type is not None and e.type.name not in m.types:
|
|
553
|
+
m.types[e.type.name] = e.type.to_dict()
|
|
554
|
+
return m
|
|
555
|
+
|
|
556
|
+
# -- helpers
|
|
557
|
+
def add(self, e: ApiEntry) -> ApiEntry:
|
|
558
|
+
if e.name in self.m.apis:
|
|
559
|
+
return self.m.apis[e.name]
|
|
560
|
+
self.m.apis[e.name] = e
|
|
561
|
+
return e
|
|
562
|
+
|
|
563
|
+
def entry_signature(self, run: Runnable, inst: list) -> str:
|
|
564
|
+
"""Runnable entry point: void <symbol>([Rte_Instance]) (SWS_Rte_01126); a server runnable returns
|
|
565
|
+
Std_ReturnType and takes the operation's arguments (SWS_Rte_01166)."""
|
|
566
|
+
for ev in run.events:
|
|
567
|
+
if ev["kind"] == "OPERATION-INVOKED-EVENT":
|
|
568
|
+
op = self.ix.get(ev.get("target"))
|
|
569
|
+
if op is not None:
|
|
570
|
+
args = [p["type"] for p in self.op_args(op, ("IN", "INOUT", "OUT"))]
|
|
571
|
+
ps = ", ".join([x["type"] for x in inst] + args) or "void"
|
|
572
|
+
return f"Std_ReturnType {run.symbol}({ps})"
|
|
573
|
+
return f"void {run.symbol}({'Rte_Instance self' if inst else 'void'})"
|
|
574
|
+
|
|
575
|
+
def op_args(self, op: Node, dirs: tuple) -> list:
|
|
576
|
+
out = []
|
|
577
|
+
for a in _own(op, "ARGUMENTS"):
|
|
578
|
+
d = (a.child_text("DIRECTION") or "IN").upper()
|
|
579
|
+
if d not in dirs:
|
|
580
|
+
continue
|
|
581
|
+
t = self.t.resolve(a.child_text("TYPE-TREF"), self.maps)
|
|
582
|
+
typ = in_param(t, a.short_name) if d == "IN" else out_param(t, a.short_name)
|
|
583
|
+
out.append({"name": a.short_name, "type": typ, "dir": d})
|
|
584
|
+
return out
|
|
585
|
+
|
|
586
|
+
def port_apis(self, p: Node, port: dict, itf: Node, direction: str):
|
|
587
|
+
ix, inst, maps = self.ix, self.inst, self.maps
|
|
588
|
+
pname, ppath = p.short_name, ix.path(p)
|
|
589
|
+
prov, req = direction in ("P", "PR"), direction in ("R", "PR")
|
|
590
|
+
kind = itf.tag
|
|
591
|
+
if kind in ("SENDER-RECEIVER-INTERFACE", "NV-DATA-INTERFACE"):
|
|
592
|
+
inv = {}
|
|
593
|
+
for ip in _own(itf, "INVALIDATION-POLICYS"):
|
|
594
|
+
inv[ip.child_text("DATA-ELEMENT-REF") or ""] = (ip.child_text("HANDLE-INVALID") or "").upper()
|
|
595
|
+
ack = any(_desc(cs, "TRANSMISSION-ACKNOWLEDGE") is not None for cs in _own(p, "PROVIDED-COM-SPECS"))
|
|
596
|
+
for el in _own(itf, "DATA-ELEMENTS") + _own(itf, "NV-DATAS"):
|
|
597
|
+
ep = ix.path(el)
|
|
598
|
+
t = self.t.resolve(el.child_text("TYPE-TREF"), maps)
|
|
599
|
+
queued = (_desc_text(el, "SW-IMPL-POLICY") or "").upper() == "QUEUED"
|
|
600
|
+
sem = "event" if queued else "data"
|
|
601
|
+
invalid = inv.get(ep, "") in INVALIDATING
|
|
602
|
+
upd = self.rcv_comspec(p, ep, "ENABLE-UPDATE") == "true"
|
|
603
|
+
status = any(self.rcv_comspec(p, ep, k) not in (None, "", "0", "false", "none", "NONE")
|
|
604
|
+
for k in ("ALIVE-TIMEOUT", "HANDLE-NEVER-RECEIVED", "HANDLE-OUT-OF-RANGE",
|
|
605
|
+
"HANDLE-DATA-STATUS")) or invalid
|
|
606
|
+
needs = []
|
|
607
|
+
port["elements"].append({"name": el.short_name, "path": ep, "type": t.name, "semantics": sem})
|
|
608
|
+
common = dict(port=pname, port_path=ppath, element=el.short_name, type=t, semantics=sem, target=ep)
|
|
609
|
+
if prov:
|
|
610
|
+
if queued:
|
|
611
|
+
self.add(ApiEntry(f"Rte_Send_{pname}_{el.short_name}", "Send", "Std_ReturnType",
|
|
612
|
+
inst + [{"name": "data", "type": in_param(t), "dir": "IN"}], **common))
|
|
613
|
+
else:
|
|
614
|
+
self.add(ApiEntry(f"Rte_Write_{pname}_{el.short_name}", "Write", "Std_ReturnType",
|
|
615
|
+
inst + [{"name": "data", "type": in_param(t), "dir": "IN"}], **common))
|
|
616
|
+
if invalid:
|
|
617
|
+
self.add(ApiEntry(f"Rte_Invalidate_{pname}_{el.short_name}", "Invalidate",
|
|
618
|
+
"Std_ReturnType", list(inst), condition="InvalidationPolicy", **common))
|
|
619
|
+
else:
|
|
620
|
+
needs.append("Rte_Invalidate/IInvalidate need an InvalidationPolicy keep/replace")
|
|
621
|
+
if ack:
|
|
622
|
+
self.add(ApiEntry(f"Rte_Feedback_{pname}_{el.short_name}", "Feedback", "Std_ReturnType",
|
|
623
|
+
list(inst), condition="TransmissionAcknowledgementRequest", **common))
|
|
624
|
+
if req:
|
|
625
|
+
if queued:
|
|
626
|
+
self.add(ApiEntry(f"Rte_Receive_{pname}_{el.short_name}", "Receive", "Std_ReturnType",
|
|
627
|
+
inst + [{"name": "data", "type": out_param(t), "dir": "OUT"}], **common))
|
|
628
|
+
else:
|
|
629
|
+
self.add(ApiEntry(f"Rte_Read_{pname}_{el.short_name}", "Read", "Std_ReturnType",
|
|
630
|
+
inst + [{"name": "data", "type": out_param(t), "dir": "OUT"}], **common))
|
|
631
|
+
if t.kind in ("primitive", "unknown", "pointer"): # SWS_Rte_07395: primitive types only
|
|
632
|
+
self.add(ApiEntry(f"Rte_DRead_{pname}_{el.short_name}", "DRead", t.name, list(inst),
|
|
633
|
+
**common))
|
|
634
|
+
if upd:
|
|
635
|
+
self.add(ApiEntry(f"Rte_IsUpdated_{pname}_{el.short_name}", "IsUpdated", "boolean",
|
|
636
|
+
list(inst), condition="enableUpdate", **common))
|
|
637
|
+
else:
|
|
638
|
+
needs.append("Rte_IsUpdated needs enableUpdate in the NonqueuedReceiverComSpec")
|
|
639
|
+
port["elements"][-1]["_flags"] = {"invalid": invalid, "status": status, "queued": queued, "t": t}
|
|
640
|
+
if needs:
|
|
641
|
+
port["elements"][-1]["needs"] = "; ".join(needs)
|
|
642
|
+
elif kind == "CLIENT-SERVER-INTERFACE":
|
|
643
|
+
for op in _own(itf, "OPERATIONS"):
|
|
644
|
+
port["elements"].append({"name": op.short_name, "path": ix.path(op), "kind": "operation"})
|
|
645
|
+
if req: # sync signature; the async variant replaces it when an async call point is found
|
|
646
|
+
self.add(ApiEntry(f"Rte_Call_{pname}_{op.short_name}", "Call", "Std_ReturnType",
|
|
647
|
+
inst + self.op_args(op, ("IN", "INOUT", "OUT")), ix.path(op), port=pname,
|
|
648
|
+
port_path=ppath, element=op.short_name))
|
|
649
|
+
elif kind == "MODE-SWITCH-INTERFACE":
|
|
650
|
+
# the ModeDeclarationGroupPrototype is serialised under its role name <MODE-GROUP>
|
|
651
|
+
for mg in [c for c in itf.children if c.tag in ("MODE-GROUP", "MODE-DECLARATION-GROUP-PROTOTYPE")]:
|
|
652
|
+
t = self.t.mode_type(mg, maps)
|
|
653
|
+
port["elements"].append({"name": mg.short_name, "path": ix.path(mg), "kind": "modeGroup", "type": t.name})
|
|
654
|
+
common = dict(port=pname, port_path=ppath, element=mg.short_name, type=t, target=ix.path(mg))
|
|
655
|
+
self.add(ApiEntry(f"Rte_Mode_{pname}_{mg.short_name}", "Mode", t.name, list(inst), **common))
|
|
656
|
+
if prov:
|
|
657
|
+
self.add(ApiEntry(f"Rte_Switch_{pname}_{mg.short_name}", "Switch", "Std_ReturnType",
|
|
658
|
+
inst + [{"name": "mode", "type": f"{t.name} mode", "dir": "IN"}], **common))
|
|
659
|
+
if any(_desc(cs, "MODE-SWITCHED-ACK") is not None for cs in _own(p, "PROVIDED-COM-SPECS")):
|
|
660
|
+
self.add(ApiEntry(f"Rte_SwitchAck_{pname}_{mg.short_name}", "SwitchAck", "Std_ReturnType",
|
|
661
|
+
list(inst), condition="ModeSwitchedAckRequest", **common))
|
|
662
|
+
elif kind == "PARAMETER-INTERFACE":
|
|
663
|
+
for prm in _own(itf, "PARAMETERS"):
|
|
664
|
+
t = self.t.resolve(prm.child_text("TYPE-TREF"), maps)
|
|
665
|
+
port["elements"].append({"name": prm.short_name, "path": ix.path(prm), "kind": "parameter", "type": t.name})
|
|
666
|
+
if req:
|
|
667
|
+
self.add(ApiEntry(f"Rte_Prm_{pname}_{prm.short_name}", "Prm", ret_of(t), list(inst), ix.path(prm),
|
|
668
|
+
port=pname, port_path=ppath, element=prm.short_name, type=t))
|
|
669
|
+
elif kind == "TRIGGER-INTERFACE":
|
|
670
|
+
for tr in _own(itf, "TRIGGERS"):
|
|
671
|
+
port["elements"].append({"name": tr.short_name, "path": ix.path(tr), "kind": "trigger"})
|
|
672
|
+
if prov:
|
|
673
|
+
self.add(ApiEntry(f"Rte_Trigger_{pname}_{tr.short_name}", "Trigger", "Std_ReturnType", list(inst),
|
|
674
|
+
ix.path(tr), port=pname, port_path=ppath, element=tr.short_name))
|
|
675
|
+
|
|
676
|
+
def rcv_comspec(self, p: Node, elem_path: str, tag: str) -> Optional[str]:
|
|
677
|
+
for cs in _own(p, "REQUIRED-COM-SPECS"):
|
|
678
|
+
if (cs.child_text("DATA-ELEMENT-REF") or "") == elem_path:
|
|
679
|
+
v = cs.child_text(tag)
|
|
680
|
+
if v is not None:
|
|
681
|
+
return v.lower()
|
|
682
|
+
return None
|
|
683
|
+
|
|
684
|
+
def per_runnable_sr(self, run: str, port: dict, el: dict):
|
|
685
|
+
"""Implicit APIs for one runnable / port / element (names carry the runnable)."""
|
|
686
|
+
f = el.get("_flags")
|
|
687
|
+
if not f or f["queued"]:
|
|
688
|
+
return
|
|
689
|
+
t, inst = f["t"], self.inst
|
|
690
|
+
pn, en = port["name"], el["name"]
|
|
691
|
+
common = dict(port=pn, port_path=port["path"], element=en, type=t, semantics="data", target=el["path"],
|
|
692
|
+
runnable=run)
|
|
693
|
+
if port["direction"] in ("R", "PR"):
|
|
694
|
+
self.add(ApiEntry(f"Rte_IRead_{run}_{pn}_{en}", "IRead", ret_of(t), list(inst), **common))
|
|
695
|
+
self.add(ApiEntry(f"Rte_IReadRef_{run}_{pn}_{en}", "IReadRef", f"const {t.base or t.name}*"
|
|
696
|
+
if t.kind == "array" else f"const {t.name}*", list(inst), **common))
|
|
697
|
+
if f["status"]:
|
|
698
|
+
self.add(ApiEntry(f"Rte_IStatus_{run}_{pn}_{en}", "IStatus", "Std_ReturnType", list(inst),
|
|
699
|
+
condition="data element status (aliveTimeout/handleNeverReceived/...)", **common))
|
|
700
|
+
if port["direction"] in ("P", "PR"):
|
|
701
|
+
self.add(ApiEntry(f"Rte_IWrite_{run}_{pn}_{en}", "IWrite", "void",
|
|
702
|
+
inst + [{"name": "data", "type": in_param(t), "dir": "IN"}], **common))
|
|
703
|
+
self.add(ApiEntry(f"Rte_IWriteRef_{run}_{pn}_{en}", "IWriteRef", f"{t.base or t.name}*"
|
|
704
|
+
if t.kind == "array" else f"{t.name}*", list(inst), **common))
|
|
705
|
+
if f["invalid"]:
|
|
706
|
+
self.add(ApiEntry(f"Rte_IInvalidate_{run}_{pn}_{en}", "IInvalidate", "void", list(inst),
|
|
707
|
+
condition="InvalidationPolicy", **common))
|
|
708
|
+
|
|
709
|
+
def irv_apis(self, run: str, name: str, vpath: str, t: TypeInfo, kind: str):
|
|
710
|
+
inst = self.inst
|
|
711
|
+
common = dict(element=name, type=t, target=vpath, runnable=run)
|
|
712
|
+
if kind == "explicit":
|
|
713
|
+
if t.kind in ("struct", "union", "array"): # complex type signature (SWS_Rte_03560)
|
|
714
|
+
self.add(ApiEntry(f"Rte_IrvRead_{run}_{name}", "IrvRead", "void",
|
|
715
|
+
inst + [{"name": "data", "type": out_param(t), "dir": "OUT"}], **common))
|
|
716
|
+
else:
|
|
717
|
+
self.add(ApiEntry(f"Rte_IrvRead_{run}_{name}", "IrvRead", t.name, list(inst), **common))
|
|
718
|
+
self.add(ApiEntry(f"Rte_IrvWrite_{run}_{name}", "IrvWrite", "void",
|
|
719
|
+
inst + [{"name": "data", "type": in_param(t), "dir": "IN"}], **common))
|
|
720
|
+
else:
|
|
721
|
+
self.add(ApiEntry(f"Rte_IrvIRead_{run}_{name}", "IrvIRead", ret_of(t), list(inst), **common))
|
|
722
|
+
self.add(ApiEntry(f"Rte_IrvIReadRef_{run}_{name}", "IrvIReadRef", f"const {t.base or t.name}*"
|
|
723
|
+
if t.kind == "array" else f"const {t.name}*", list(inst), **common))
|
|
724
|
+
self.add(ApiEntry(f"Rte_IrvIWrite_{run}_{name}", "IrvIWrite", "void",
|
|
725
|
+
inst + [{"name": "data", "type": in_param(t), "dir": "IN"}], **common))
|
|
726
|
+
self.add(ApiEntry(f"Rte_IrvIWriteRef_{run}_{name}", "IrvIWriteRef", f"{t.base or t.name}*"
|
|
727
|
+
if t.kind == "array" else f"{t.name}*", list(inst), **common))
|
|
728
|
+
|
|
729
|
+
def access_point(self, run: Runnable, coll: str, ap: Node, irvs: dict):
|
|
730
|
+
ix, m = self.ix, self.m
|
|
731
|
+
role = ROLE[coll]
|
|
732
|
+
point = ix.path(ap)
|
|
733
|
+
rec = {"role": role, "point": point}
|
|
734
|
+
grant = lambda name: (m.apis[name].access.append({"runnable": run.name, "role": role, "point": point}) # noqa: E731
|
|
735
|
+
if name in m.apis else None)
|
|
736
|
+
if coll in ("READ-LOCAL-VARIABLES", "WRITTEN-LOCAL-VARIABLES"):
|
|
737
|
+
vref = _ref_ending(ap, "LOCAL-VARIABLE-REF")
|
|
738
|
+
rec["target"] = vref
|
|
739
|
+
run.points.append(rec)
|
|
740
|
+
hit = irvs.get(vref or "")
|
|
741
|
+
if hit is None:
|
|
742
|
+
rec["unresolved"] = f"inter-runnable variable {vref} not declared in the behaviour"
|
|
743
|
+
return
|
|
744
|
+
v, kind = hit
|
|
745
|
+
t = self.t.resolve(v.child_text("TYPE-TREF"), self.maps)
|
|
746
|
+
self.irv_apis(run.name, v.short_name, vref, t, kind)
|
|
747
|
+
names = (["IrvRead"] if kind == "explicit" else ["IrvIRead", "IrvIReadRef"]) if coll.startswith("READ") else \
|
|
748
|
+
(["IrvWrite"] if kind == "explicit" else ["IrvIWrite", "IrvIWriteRef"])
|
|
749
|
+
for k in names:
|
|
750
|
+
grant(f"Rte_{k}_{run.name}_{v.short_name}")
|
|
751
|
+
return
|
|
752
|
+
if coll == "PARAMETER-ACCESSS":
|
|
753
|
+
local = _ref_ending(ap, "LOCAL-PARAMETER-REF")
|
|
754
|
+
if local:
|
|
755
|
+
rec["target"] = local
|
|
756
|
+
run.points.append(rec)
|
|
757
|
+
name = f"Rte_CData_{local.rsplit('/', 1)[-1]}"
|
|
758
|
+
if name not in m.apis:
|
|
759
|
+
rec["unresolved"] = f"internal parameter {local} not declared"
|
|
760
|
+
grant(name)
|
|
761
|
+
return
|
|
762
|
+
if coll == "INTERNAL-TRIGGERING-POINTS":
|
|
763
|
+
rec["target"] = point
|
|
764
|
+
run.points.append(rec)
|
|
765
|
+
self.add(ApiEntry(f"Rte_IrTrigger_{run.name}_{ap.short_name}", "IrTrigger", "void", list(self.inst), point,
|
|
766
|
+
element=ap.short_name, runnable=run.name))
|
|
767
|
+
grant(f"Rte_IrTrigger_{run.name}_{ap.short_name}")
|
|
768
|
+
return
|
|
769
|
+
if coll == "ASYNCHRONOUS-SERVER-CALL-RESULT-POINTS":
|
|
770
|
+
call = ix.get(ap.child_text("ASYNCHRONOUS-SERVER-CALL-POINT-REF"))
|
|
771
|
+
if call is None:
|
|
772
|
+
rec["unresolved"] = "ASYNCHRONOUS-SERVER-CALL-POINT-REF does not resolve"
|
|
773
|
+
run.points.append(rec)
|
|
774
|
+
return
|
|
775
|
+
ap = call # the result point inherits port/operation from its call point
|
|
776
|
+
port_ref = _ref_ending(ap, "PORT-REF", "PORT-PROTOTYPE-REF")
|
|
777
|
+
tgt = _ref_ending(ap, "TARGET-DATA-PROTOTYPE-REF", "TARGET-REQUIRED-OPERATION-REF", "TARGET-MODE-GROUP-REF",
|
|
778
|
+
"TARGET-TRIGGER-REF", "TARGET-DATA-ELEMENT-REF", "TARGET-PROVIDED-OPERATION-REF")
|
|
779
|
+
rec.update({"port": (port_ref or "").rsplit("/", 1)[-1] or None, "target": tgt})
|
|
780
|
+
run.points.append(rec)
|
|
781
|
+
port = next((p for p in m.ports if p["path"] == port_ref), None)
|
|
782
|
+
if port is None:
|
|
783
|
+
rec["unresolved"] = f"port {port_ref} is not a port of {m.name}"
|
|
784
|
+
return
|
|
785
|
+
el = next((e for e in port["elements"] if e["path"] == tgt), None)
|
|
786
|
+
if el is None:
|
|
787
|
+
rec["unresolved"] = f"{tgt} is not an element of {port['interface']}"
|
|
788
|
+
return
|
|
789
|
+
pn, en = port["name"], el["name"]
|
|
790
|
+
if coll in ("DATA-READ-ACCESSS", "DATA-WRITE-ACCESSS"):
|
|
791
|
+
self.per_runnable_sr(run.name, port, el)
|
|
792
|
+
kinds = ["IRead", "IReadRef", "IStatus"] if coll == "DATA-READ-ACCESSS" else ["IWrite", "IWriteRef", "IInvalidate"]
|
|
793
|
+
for k in kinds:
|
|
794
|
+
grant(f"Rte_{k}_{run.name}_{pn}_{en}")
|
|
795
|
+
return
|
|
796
|
+
kinds = {"DATA-SEND-POINTS": ["Write", "Send", "Invalidate", "Feedback"],
|
|
797
|
+
"DATA-RECEIVE-POINT-BY-ARGUMENTS": ["Read", "Receive", "IsUpdated"],
|
|
798
|
+
"DATA-RECEIVE-POINT-BY-VALUES": ["DRead", "IsUpdated"],
|
|
799
|
+
"SERVER-CALL-POINTS": ["Call"], "ASYNCHRONOUS-SERVER-CALL-RESULT-POINTS": ["Result"],
|
|
800
|
+
"MODE-ACCESS-POINTS": ["Mode"], "MODE-SWITCH-POINTS": ["Switch", "SwitchAck"],
|
|
801
|
+
"PARAMETER-ACCESSS": ["Prm"], "EXTERNAL-TRIGGERING-POINTS": ["Trigger"]}[coll]
|
|
802
|
+
if coll == "SERVER-CALL-POINTS" and ap.tag == "ASYNCHRONOUS-SERVER-CALL-POINT":
|
|
803
|
+
op = ix.get(tgt)
|
|
804
|
+
call = m.apis.get(f"Rte_Call_{pn}_{en}")
|
|
805
|
+
if call is not None and op is not None: # async: Call carries IN/INOUT, Result the OUT/INOUT
|
|
806
|
+
call.params = self.inst + self.op_args(op, ("IN", "INOUT"))
|
|
807
|
+
call.condition = "asynchronous"
|
|
808
|
+
self.add(ApiEntry(f"Rte_Result_{pn}_{en}", "Result", "Std_ReturnType",
|
|
809
|
+
self.inst + self.op_args(op, ("INOUT", "OUT")), tgt, port=pn, port_path=port["path"],
|
|
810
|
+
element=en, condition="asynchronous"))
|
|
811
|
+
for k in kinds:
|
|
812
|
+
grant(f"Rte_{k}_{pn}_{en}")
|
|
813
|
+
|
|
814
|
+
|
|
815
|
+
# ---------------------------------------------------------------- entry points
|
|
816
|
+
def build_all(root: str, ix: Optional[ArIndex] = None) -> list:
|
|
817
|
+
ix = ix or ArIndex(root)
|
|
818
|
+
b = Builder(ix, Types(ix))
|
|
819
|
+
out = []
|
|
820
|
+
for n in ix.of_tag(*SWC_TAGS):
|
|
821
|
+
out.append(b.build(n))
|
|
822
|
+
# derivable per-runnable implicit S/R APIs (names include the runnable): add after runnables are known
|
|
823
|
+
for m in out:
|
|
824
|
+
b.m, b.inst = m, ([{"name": "self", "type": "Rte_Instance self", "dir": "IN"}] if m.multiple_instantiation else [])
|
|
825
|
+
for r in m.runnables:
|
|
826
|
+
for p in m.ports:
|
|
827
|
+
for el in p["elements"]:
|
|
828
|
+
b.per_runnable_sr(r.name, p, el)
|
|
829
|
+
for p in m.ports:
|
|
830
|
+
for el in p["elements"]:
|
|
831
|
+
el.pop("_flags", None)
|
|
832
|
+
return out
|
|
833
|
+
|
|
834
|
+
|
|
835
|
+
def find_swc(models: list, name: Optional[str]) -> Optional[SwcModel]:
|
|
836
|
+
if not name:
|
|
837
|
+
return None
|
|
838
|
+
for m in models:
|
|
839
|
+
if name in (m.name, m.path):
|
|
840
|
+
return m
|
|
841
|
+
low = name.lower()
|
|
842
|
+
hits = [m for m in models if m.name.lower() == low]
|
|
843
|
+
return hits[0] if len(hits) == 1 else None
|
|
844
|
+
|
|
845
|
+
|
|
846
|
+
RTE_DECL = re.compile(r"\b(Rte_\w+)\s*\(")
|
|
847
|
+
|
|
848
|
+
|
|
849
|
+
def header_entries(files: list) -> dict:
|
|
850
|
+
"""Rte_* macros and function prototypes declared in the given headers -> {name: {form, text, file, line}}."""
|
|
851
|
+
from .audit import parse_defines, strip_comments
|
|
852
|
+
out: dict = {}
|
|
853
|
+
for name, (params, body, f, line) in parse_defines(files).items():
|
|
854
|
+
if name.startswith("Rte_") and params is not None:
|
|
855
|
+
out.setdefault(name, {"form": "macro", "text": f"#define {name}({', '.join(params)}) {body}".strip(),
|
|
856
|
+
"file": f, "line": line})
|
|
857
|
+
for f in files:
|
|
858
|
+
try:
|
|
859
|
+
raw = open(f, encoding="utf-8", errors="replace").read()
|
|
860
|
+
except OSError:
|
|
861
|
+
continue
|
|
862
|
+
from .csrc import _blank_preproc
|
|
863
|
+
text = _blank_preproc(strip_comments(raw))
|
|
864
|
+
for st in re.finditer(r"[^;{}#]*?\b(Rte_\w+)\s*\([^;{}]*\)\s*;", text):
|
|
865
|
+
name = st.group(1)
|
|
866
|
+
ls = text.rfind("\n", 0, st.start(1)) + 1
|
|
867
|
+
if text[ls:st.start(1)].lstrip().startswith("#") or name in out:
|
|
868
|
+
continue
|
|
869
|
+
decl = " ".join(text[st.start():st.end()].split())
|
|
870
|
+
if re.match(r"^(?:return\b|\(void\))", decl) or "=" in decl.split(name)[0]:
|
|
871
|
+
continue
|
|
872
|
+
out[name] = {"form": "function", "text": decl, "file": f, "line": text.count("\n", 0, st.start(1)) + 1}
|
|
873
|
+
return out
|
|
874
|
+
|
|
875
|
+
|
|
876
|
+
def find_header(root: str, swc: str, c_files: Optional[list] = None) -> Optional[str]:
|
|
877
|
+
want = f"rte_{swc}.h".lower()
|
|
878
|
+
files = c_files if c_files is not None else paths.walk(root, paths.is_c).files
|
|
879
|
+
hits = sorted(f for f in files if os.path.basename(f).lower() == want)
|
|
880
|
+
return hits[0] if hits else None
|
|
881
|
+
|
|
882
|
+
|
|
883
|
+
def match_header(m: SwcModel, header: Optional[str], rel=lambda f: f) -> dict:
|
|
884
|
+
"""Optional second step: match each generated API to its declaration/macro in Rte_<Swc>.h."""
|
|
885
|
+
if not header:
|
|
886
|
+
return {"header": None, "note": f"no Rte_{m.name}.h in the project; nothing to match"}
|
|
887
|
+
decl = header_entries([header])
|
|
888
|
+
gen = {a.name for a in m.generated()}
|
|
889
|
+
matched = {}
|
|
890
|
+
for a in sorted(m.apis.values(), key=lambda x: x.name):
|
|
891
|
+
d = decl.get(a.name)
|
|
892
|
+
if d:
|
|
893
|
+
matched[a.name] = {"form": d["form"], "text": d["text"], "at": f"{rel(d['file'])}:{d['line']}"}
|
|
894
|
+
missing = sorted(gen - set(decl))
|
|
895
|
+
extra = []
|
|
896
|
+
for name in sorted(set(decl) - gen):
|
|
897
|
+
e = m.apis.get(name)
|
|
898
|
+
extra.append({"name": name, "at": f"{rel(decl[name]['file'])}:{decl[name]['line']}",
|
|
899
|
+
"why": "derivable but no runnable has the access point" if e is not None
|
|
900
|
+
else explain_unknown(m, name)})
|
|
901
|
+
symbols = {}
|
|
902
|
+
try:
|
|
903
|
+
text = open(header, encoding="utf-8", errors="replace").read()
|
|
904
|
+
except OSError:
|
|
905
|
+
text = ""
|
|
906
|
+
for r in m.runnables:
|
|
907
|
+
mm = re.search(rf"^[^\n#]*\b{re.escape(r.symbol)}\s*\([^)]*\)\s*;", text, re.M)
|
|
908
|
+
symbols[r.name] = mm.group(0).strip() if mm else None
|
|
909
|
+
return {"header": rel(header), "matched": matched, "missing": missing, "extra": extra,
|
|
910
|
+
"runnablePrototypes": symbols,
|
|
911
|
+
"counts": {"generatedExpected": len(gen), "declared": len([n for n in decl if n in gen]),
|
|
912
|
+
"missing": len(missing), "extra": len(extra)}}
|
|
913
|
+
|
|
914
|
+
|
|
915
|
+
def rte_api(root: str, swc: Optional[str] = None, runnable: Optional[str] = None, header: bool = True) -> dict:
|
|
916
|
+
ix = ArIndex(root)
|
|
917
|
+
models = build_all(root, ix)
|
|
918
|
+
rel = lambda f: os.path.relpath(f, root) if not os.path.relpath(f, root).startswith("..") else f # noqa: E731
|
|
919
|
+
if swc:
|
|
920
|
+
m = find_swc(models, swc)
|
|
921
|
+
if m is None:
|
|
922
|
+
return {"error": f"no SW-COMPONENT-TYPE {swc!r}; known: {', '.join(x.name for x in models)}"}
|
|
923
|
+
models = [m]
|
|
924
|
+
if runnable and m.runnable(runnable) is None:
|
|
925
|
+
return {"error": f"{m.name} has no runnable {runnable!r}; runnables: {', '.join(r.name for r in m.runnables)}"}
|
|
926
|
+
cfiles = paths.walk(root, paths.is_c).files if header else []
|
|
927
|
+
out = []
|
|
928
|
+
for m in models:
|
|
929
|
+
d = m.to_dict(runnable)
|
|
930
|
+
if header:
|
|
931
|
+
d["header"] = match_header(m, find_header(root, m.name, cfiles), rel)
|
|
932
|
+
out.append(d)
|
|
933
|
+
return {"swcs": out, "summary": {"swcs": len(out), "runnables": sum(len(x["runnables"]) for x in out),
|
|
934
|
+
"derivable": sum(x["counts"]["derivable"] for x in out),
|
|
935
|
+
"generated": sum(x["counts"]["generated"] for x in out)}}
|