@h2loop/autosar 1.1.0 → 1.2.1

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.
@@ -0,0 +1,163 @@
1
+ """Requirement-ID check (autosar_spec_id_check) against the user's own AUTOSAR spec texts.
2
+
3
+ No requirement IDs or spec text are bundled with the pack: AUTOSAR documents are licensed to the user, so the
4
+ index is built at run time from text exports the project points at (`.hydron/autosar.json` "specDocs" globs, or
5
+ $AUTOSAR_SPEC_DOCS, an os.pathsep-separated list of files/dirs/globs; .txt and .md files). The index is cached
6
+ per file set (size + mtime) under $XDG_CACHE_HOME/h2loop-autosar.
7
+
8
+ An ID is "defined" where a line starts with "[ID]" alone (optionally after a label such as "Signature:", as the
9
+ RTE spec writes API requirements), followed within a few lines by the requirement
10
+ bracket "⌈" or "Upstream requirements" (how SWS/TPS/SRS/ECUC requirements are introduced), and "mentioned" when
11
+ it only occurs in running text or trace tables.
12
+ """
13
+ from __future__ import annotations
14
+
15
+ import hashlib
16
+ import json
17
+ import os
18
+ import re
19
+ from typing import Optional
20
+
21
+ from . import paths
22
+
23
+ ID = re.compile(r"\b([A-Z][A-Za-z]{1,7}_[A-Za-z0-9]+_\d{3,6})\b")
24
+ # "SWS_NvM_00898/00899" -> both IDs
25
+ SHORTHAND = re.compile(r"\b([A-Z][A-Za-z]{1,7}_[A-Za-z0-9]+_)(\d{3,6})((?:/\d{3,6})+)")
26
+ DEF_LINE = re.compile(r"^\s*(?:[A-Z][A-Za-z ]{0,30}:\s*)?\[([A-Z][A-Za-z]{1,7}_[A-Za-z0-9]+_\d{3,6})\]")
27
+ INDEX_VERSION = 3
28
+
29
+
30
+ def expand_ids(text: str) -> list:
31
+ """Every requirement ID written in text, with "X_Mod_00001/00002" shorthand expanded, in order, unique."""
32
+ out: list = []
33
+ for m in SHORTHAND.finditer(text):
34
+ out.append(m.group(1) + m.group(2))
35
+ out.extend(m.group(1) + n for n in m.group(3).strip("/").split("/"))
36
+ out.extend(m.group(1) for m in ID.finditer(text))
37
+ seen: set = set()
38
+ return [x for x in out if not (x in seen or seen.add(x))]
39
+
40
+
41
+ def _is_doc(f: str) -> bool:
42
+ return f.lower().endswith((".txt", ".md"))
43
+
44
+
45
+ def doc_files(root: str, docs: Optional[list] = None) -> tuple:
46
+ """(files, origin) of the spec text docs to index."""
47
+ pats, origin = list(docs or []), "argument"
48
+ if not pats:
49
+ cfg = os.path.join(root, ".hydron", "autosar.json")
50
+ try:
51
+ with open(cfg) as fh:
52
+ spec = json.load(fh).get("specDocs")
53
+ if spec:
54
+ pats, origin = ([spec] if isinstance(spec, str) else list(spec)), ".hydron/autosar.json specDocs"
55
+ except (OSError, ValueError, AttributeError):
56
+ pass
57
+ if not pats and os.environ.get("AUTOSAR_SPEC_DOCS"):
58
+ pats, origin = os.environ["AUTOSAR_SPEC_DOCS"].split(os.pathsep), "AUTOSAR_SPEC_DOCS"
59
+ files: list = []
60
+ for p in paths.patterns(root, [x for x in pats if x]):
61
+ files.extend(f for f in p.expand(_is_doc) if _is_doc(f) and os.path.isfile(f))
62
+ return sorted({os.path.abspath(f) for f in files}), origin if pats else None
63
+
64
+
65
+ def _cache_path(files: list) -> str:
66
+ h = hashlib.sha1(str(INDEX_VERSION).encode())
67
+ for f in files:
68
+ st = os.stat(f)
69
+ h.update(f"{f}\0{st.st_size}\0{int(st.st_mtime)}\n".encode())
70
+ base = os.environ.get("XDG_CACHE_HOME") or os.path.join(os.path.expanduser("~"), ".cache")
71
+ return os.path.join(base, "h2loop-autosar", f"specids-{h.hexdigest()[:16]}.json")
72
+
73
+
74
+ def build_index(files: list) -> dict:
75
+ """ID -> [file index, line, defined] (the defining line when there is one, else the first mention)."""
76
+ idx: dict = {}
77
+ for fi, f in enumerate(files):
78
+ with open(f, encoding="utf-8", errors="replace") as fh:
79
+ lines = fh.readlines()
80
+ for i, line in enumerate(lines):
81
+ d = DEF_LINE.match(line)
82
+ if d and len(ID.findall(line)) == 1 and any(
83
+ "⌈" in x or "Upstream requirements" in x for x in lines[i: i + 5]):
84
+ cur = idx.get(d.group(1))
85
+ if cur is None or not cur[2]:
86
+ idx[d.group(1)] = [fi, i + 1, True]
87
+ for m in ID.finditer(line):
88
+ idx.setdefault(m.group(1), [fi, i + 1, False])
89
+ return idx
90
+
91
+
92
+ def load_index(files: list) -> dict:
93
+ cache = _cache_path(files)
94
+ try:
95
+ with open(cache) as fh:
96
+ data = json.load(fh)
97
+ if data.get("files") == files:
98
+ return data["ids"]
99
+ except (OSError, ValueError):
100
+ pass
101
+ ids = build_index(files)
102
+ try:
103
+ os.makedirs(os.path.dirname(cache), exist_ok=True)
104
+ tmp = cache + ".tmp"
105
+ with open(tmp, "w") as fh:
106
+ json.dump({"files": files, "ids": ids}, fh)
107
+ os.replace(tmp, cache)
108
+ except OSError:
109
+ pass
110
+ return ids
111
+
112
+
113
+ def _snippet(f: str, line: int, max_lines: int = 14, max_chars: int = 700) -> str:
114
+ out: list = []
115
+ with open(f, encoding="utf-8", errors="replace") as fh:
116
+ for ln, text in enumerate(fh, 1):
117
+ if ln < line:
118
+ continue
119
+ out.append(text.strip())
120
+ if "⌋" in text or len(out) >= max_lines:
121
+ break
122
+ s = re.sub(r"\s+", " ", " ".join(out)).strip()
123
+ return s if len(s) <= max_chars else s[: max_chars - 1] + "…"
124
+
125
+
126
+ def check_ids(root: str, ids: list, docs: Optional[list] = None) -> dict:
127
+ files, origin = doc_files(root, docs)
128
+ wanted: list = []
129
+ for raw in ids:
130
+ got = expand_ids(str(raw))
131
+ wanted.extend(got or [str(raw).strip().strip("[]")])
132
+ if not files:
133
+ return {"error": "no AUTOSAR spec text docs configured, so no requirement ID can be confirmed. Set "
134
+ "`specDocs` (globs of .txt/.md spec exports) in .hydron/autosar.json or $AUTOSAR_SPEC_DOCS. "
135
+ "Until then cite only IDs that appear verbatim in a tool result or a spec text you read; "
136
+ "otherwise describe the rule without an ID.",
137
+ "ids": wanted}
138
+ idx = load_index(files)
139
+ lower: dict = {}
140
+ for k in idx:
141
+ lower.setdefault(k.lower(), k)
142
+ results = []
143
+ for i in wanted:
144
+ hit = idx.get(i)
145
+ if hit is None:
146
+ alt = lower.get(i.lower())
147
+ r = {"id": i, "status": "NOT-FOUND"}
148
+ if not ID.fullmatch(i):
149
+ r["status"] = "MALFORMED"
150
+ if alt:
151
+ r["didYouMean"] = alt
152
+ results.append(r)
153
+ continue
154
+ f = files[hit[0]]
155
+ r = {"id": i, "status": "DEFINED" if hit[2] else "MENTIONED-ONLY",
156
+ "doc": os.path.basename(f), "line": hit[1]}
157
+ if hit[2]:
158
+ r["text"] = _snippet(f, hit[1])
159
+ results.append(r)
160
+ counts: dict = {}
161
+ for r in results:
162
+ counts[r["status"]] = counts.get(r["status"], 0) + 1
163
+ return {"docs": len(files), "docsOrigin": origin, "indexedIds": len(idx), "counts": counts, "results": results}
@@ -144,3 +144,29 @@ def from_json(d: Optional[DefNode], v) -> str:
144
144
  if d is not None and d.kind == "BOOLEAN" and s.lower() in ("true", "false"):
145
145
  s = s.lower()
146
146
  return s
147
+
148
+
149
+ def hex_str(v: int) -> str:
150
+ return f"-0x{-v:X}" if v < 0 else f"0x{v:X}"
151
+
152
+
153
+ DEST_KIND = {"ECUC-INTEGER-PARAM-DEF": "INTEGER", "ECUC-FLOAT-PARAM-DEF": "FLOAT", "ECUC-BOOLEAN-PARAM-DEF": "BOOLEAN"}
154
+
155
+
156
+ def int_forms(d: Optional[DefNode], raw: Optional[str], is_ref: bool, numerical: bool,
157
+ dest: Optional[str] = None) -> Optional[dict]:
158
+ """{"dec": int, "hex": "0x..."} for an integer-typed value (def kind INTEGER; without loaded defs the
159
+ DEFINITION-REF DEST, else a numerical value whose text is an integer), else None. Computed here so answers
160
+ never convert bases by hand."""
161
+ if raw is None or is_ref:
162
+ return None
163
+ k = d.kind if d is not None else DEST_KIND.get(dest or "") or ("NUMBER" if numerical else None)
164
+ if k not in ("INTEGER", "NUMBER"):
165
+ return None
166
+ t = raw.strip()
167
+ if t.lower() in ("true", "false"):
168
+ return None
169
+ v = parse_int(t)
170
+ if v is None:
171
+ return None
172
+ return {"dec": v, "hex": hex_str(v)}
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ecuc-engine"
3
- version = "1.2.0-pre"
3
+ version = "1.4.0-pre"
4
4
  description = "Load, read, edit in place, validate and diff AUTOSAR ECUC ARXML (Hydron AUTOSAR pack)"
5
5
  requires-python = ">=3.10"
6
6
  # The core is stdlib-only; autosar-data adds a strict schema check of every edited file.
@@ -23,12 +23,35 @@ in `Output/` is derived from it; in-house code in `Source/` calls the generated
23
23
  the param defs and patches only the changed bytes. Reading raw ARXML for context is fine; prefer `ecuc_read`.
24
24
  2. **Validate after every edit**: `ecuc_validate` (scoped with `files`/`module` if large). Fix
25
25
  every error you introduced before moving on; report pre-existing ones separately.
26
- 3. **Cite** the AUTOSAR path, the DEFINITION-REF and file:line for every element you discuss or change.
26
+ 3. **Cite** the AUTOSAR path, the DEFINITION-REF and file:line for every element you discuss or change —
27
+ copied from tool output (see *Citations and numbers*), never estimated.
27
28
  4. **Generated code is an output.** Never edit `Output/` (or other generated files) to fix a
28
29
  problem — change the configuration and regenerate.
29
30
  5. Values come from the param defs (ranges, literals, multiplicity) and the SWS; when the defs do
30
- not settle a question (cross-module consistency, handle IDs, timing), say so and cite the SWS
31
- requirement rather than guessing.
31
+ not settle a question (cross-module consistency, handle IDs, timing), say so plainly. Quote the SWS
32
+ requirement only if you have its text from a tool result this session; otherwise describe the rule
33
+ without an ID — never fill the gap from memory.
34
+
35
+ ## Citations and numbers (always)
36
+ - **Requirement IDs** (SWS_/SRS_/TPS_/ECUC_/RS_…): cite one only when it appears verbatim in a tool result
37
+ (e.g. the `spec:` line of `autosar_audit`, an H2L rule message, `autosar_spec_id_check`) or in spec text you
38
+ actually read this session. Before citing any other ID, run `autosar_spec_id_check ids=[…]`; cite it only if it
39
+ is DEFINED, and describe it with the returned text. If the check is unavailable or says NOT-FOUND /
40
+ MENTIONED-ONLY, describe the rule without an ID. An invented or misremembered ID is worse than none.
41
+ - **No requirement IDs for plain value questions** ("what is X", "which file sets Y", "trace Z"): answer with the
42
+ value, its AUTOSAR path and file:line. Add an ID only when the user asks why/what the standard requires.
43
+ - **file:line** must come from a tool result or a file you read: for a value use `"@at:<Param>"` (the VALUE
44
+ element) from `ecuc_read`/`ecuc_find`, or the `@ file:line` of a `VALUES` line from `autosar_trace` /
45
+ `autosar_config_lookup`; `@at` / `at` is the container. A graph `file:line` of an element not in the local
46
+ checkout is where the element starts: read the file to find the value line. Never estimate or offset a line.
47
+ - **Numbers and units**: never convert bases or units in your head. Copy `"@hex:<Param>"` / `"@dec:<Param>"`
48
+ (and the `dec …, hex …` of VALUES lines, CAN IDs, audit `…Hex` evidence). For any other conversion or
49
+ arithmetic (bytes ↔ bits, ms ↔ s, address + offset), compute it with a tool or say it is not computed.
50
+ - **Parameter semantics**: explain what a parameter means only from its param def (`ecuc_read schema=true`,
51
+ the def `DESC`) or spec text you read; if neither covers it, say the meaning is not in the loaded defs/specs
52
+ rather than inventing one.
53
+ - **Simple look-ups get short, exact answers**: the value (with hex if integer), the AUTOSAR path, the
54
+ DEFINITION-REF and file:line — no spec background, no padding.
32
55
 
33
56
  ## Tool map
34
57
  | Need | Tool |
@@ -51,6 +74,7 @@ in `Output/` is derived from it; in-house code in `Source/` calls the generated
51
74
  | Generate BSW config code ourselves (NvM/Fee/MemIf/Dem/PduR/CanIf/Port/Dio) | `autosar_codegen`, then `autosar_codegen_diff reference_dir=Output` (see `references/codegen.md`) |
52
75
  | Regenerate code with the vendor tool | `autosar_generate` (configured command, asks permission) |
53
76
  | Compile/link | `autosar_build` |
77
+ | Is this requirement ID real, and what does it say? | `autosar_spec_id_check ids=[…]` (needs `specDocs` / `$AUTOSAR_SPEC_DOCS`) |
54
78
  | Spec questions (SWS/TPS requirement text) | the project's ingested docs search tools |
55
79
 
56
80
  ## Workflow (validation and fix tasks)
@@ -86,9 +110,11 @@ Details and failure handling: `references/workflow.md`.
86
110
  `autosar_trace <symbol>` for the owning I-PDU, generated symbol and other callers.
87
111
  3. Review what the audit lists as *unchecked* by hand (pointer arguments, expressions as handles), and
88
112
  look for logic issues it cannot see (wrong signal chosen although types match, missing error handling).
89
- 4. Report: file:line, the call, the configured facts (AUTOSAR paths + values), the spec ID, the fix.
113
+ 4. Report: file:line, the call, the configured facts (AUTOSAR paths + values with their `…At` lines), the spec
114
+ ID exactly as the audit's `spec:` line gives it (never add others), the fix.
90
115
 
91
- Facts that decide common review calls:
116
+ Facts that decide common review calls (the IDs below are checked against R25-11; still cite one only when a
117
+ tool result this session shows it, per *Citations and numbers*):
92
118
  - A Com signal's layout must match the System extract it came from (ISignalToIPduMapping startPosition /
93
119
  packingByteOrder, ISignal length) and an EcuC Pdu's PduLength the system I-PDU length (H2L-SYS-001/002).
94
120
  - A PduR routing path forwards one I-PDU: source and destination must stand for the same system I-PDU (or the same
@@ -12,11 +12,39 @@ Generated C is never written by hand or by the model: run `autosar_codegen`, rev
12
12
  | `SchM_<Mip>.h` | exclusive areas the static code uses (`SchM_Enter_<Mip>_<EA>` found in non-generated C) + main functions |
13
13
  | `<Mip>_MemMap.h` | SWS_MemMap_00032 keyword file; pragmas from `codegen.memmap.pragmas` |
14
14
 
15
+ Release macros: `<MIP>_CFG_AR_RELEASE_*` come from the static code's published information (`<MIP>_AR_RELEASE_*` in
16
+ `<Mip>.h`, SWS_BSW_00059), so the generated release check matches the SIP; the ECUC schema release is the fallback.
17
+
15
18
  Handle IDs: missing symbolic values are assigned (lowest free >= def MIN, existing values kept, duplicates
16
19
  are errors) and written back into the ARXML in place (TPS_ECUC_02107); `write_back=false` keeps them in memory.
17
20
 
21
+ A binding may declare `handleLayout` (e.g. Com: Tx signals/I-PDUs before Rx, because the static code indexes its
22
+ tables by handle). Values that break it are renumbered and written back, only for withAuto params (ComHandleId,
23
+ ComIPduHandleId); otherwise it is an error. Configuration the static code cannot represent (binding `require` /
24
+ `unsupported`, e.g. BIG_ENDIAN signals or signal groups for the stand-in Com) fails generation; nothing is written.
25
+ Notes list C code that uses a `<Mip>Conf_*` symbol the configuration no longer has (the build would fail on it).
26
+
18
27
  Output goes to `.hydron/codegen/out` unless `out_dir` is given (`project` = `codegen.outDir`, default `Output`).
19
28
 
29
+ ## Com
30
+ `Com_Cfg.h`: ComGeneral switches, `COM_DEV_ERROR_DETECT` (ECUC_Com_00141), `ComConf_ComSignal_*`, `ComConf_ComIPdu_*`,
31
+ `ComConf_ComIPduGroup_*` (and signal-group / group-signal) symbols. `Com_PBcfg.c` (binding): I-PDU buffers sized from the
32
+ EcuC Pdu, signal tables (bit position, size, type, init value, transfer property, notification), Tx I-PDU table (PduR
33
+ source handle via the shared EcuC Pdu, Tx mode, cycle = ComTxModeTimePeriod / ComMainTxTimeBase), Rx I-PDU table (deadline
34
+ = smallest non-zero ComFirstTimeout/ComTimeout of its signals without update bit, SWS_Com_00291).
35
+ Notification callouts: R25-11 Com users (ComUserModule -> ComUserModuleCnf, ComUserSignal with the same system signal,
36
+ ComUserCallback of type COM_RX_ACK / COM_TX_ACK; `call:comUserCallback`), or, for a static Com with `void(void)`
37
+ callbacks, the RTE's `Rte_COMCbk_<sn>()` / `Rte_COMCbkTAck_<sn>()` when the RTE's Rte_Cbk.h declares it
38
+ (`call:rteSignalCallback`). Nothing is bound from guesses: no declaration, no callback.
39
+
40
+ ## Known differences from the sample/large fixture oracle (all explained)
41
+ | Item | Oracle | Ours | Why |
42
+ |---|---|---|---|
43
+ | `NvM_BlockDescriptorTable[*].RamBlockDataAddress` | `X` | `&X` (as configured) | ECUC_NvM_00482 value is the address expression; kept verbatim, same address for arrays |
44
+ | Rx I-PDU `TimeoutTicks` with mixed signal timeouts | largest ComTimeout | smallest non-zero | SWS_Com_00291 |
45
+ | `<MIP>_CFG_*` version macros, generic ComGeneral/NvMCommon/... switches, Dem/Fee tables | absent | extra | def-driven output the oracle does not emit |
46
+ | SchM main function names | `Com_MainFunctionTx` | same | SWS_Com says `Com_MainFunctionTx_<shortName>`; the instance named like its def keeps the SIP's legacy name |
47
+
20
48
  ## Reading a diff
21
49
  - *missing*: in the reference only (e.g. a symbol whose ECUC container was deleted: stale Output, not a generator bug).
22
50
  - *extra*: generated only (generic def-driven macros/tables the reference generator does not emit).
@@ -15,7 +15,10 @@ its def through `DEFINITION-REF` (an AUTOSAR path); the `DEST` attribute names t
15
15
 
16
16
  **ECUC JSON** (what `ecuc_read` returns and `ecuc_edit add` accepts): containers keyed
17
17
  `"<DefName>:<ShortName>"` (just `"<Name>"` when equal), params by def short-name, multi-valued
18
- params as arrays, refs as `"->/path"`, `@path`/`@def`/`@at` metadata.
18
+ params as arrays, refs as `"->/path"`, `@path`/`@def`/`@at` metadata (`@at` = the container's file:line).
19
+ Each value is followed by `"@at:<Param>"` (file:line of its `VALUE`/`VALUE-REF` element — cite this one for a
20
+ value) and integers by `"@hex:<Param>"` (plus `"@dec:<Param>"` when the ARXML text is hex). Keys starting with
21
+ `@` are metadata; `ecuc_edit` ignores them.
19
22
 
20
23
  **Multiplicity.** `LOWER/UPPER-MULTIPLICITY` (or `UPPER-MULTIPLICITY-INFINITE`) on every container,
21
24
  param and ref def. Lower ≥ 1 = mandatory. A **choice container** (`ECUC-CHOICE-CONTAINER-DEF`)
@@ -25,7 +25,8 @@ Optional; pins what the tools load and how to generate/build:
25
25
  "generate": {"command": "<vendor-cli> -p Config/Ecu.dpa --generate {module}", "cwd": ".", "timeoutSec": 3600},
26
26
  "build": {"command": "make -C Build -j8 all", "timeoutSec": 3600},
27
27
  "probe": {"cc": "tricore-elf-gcc", "flags": ["-I", "SIP/include", "-DCPU=TC4D"]},
28
- "checkGenerated": false
28
+ "checkGenerated": false,
29
+ "specDocs": ["~/autosar/R25-11/txt/*.txt"]
29
30
  }
30
31
  ```
31
32
  Without it: param defs = every `.arxml` holding `ECUC-MODULE-DEF` (else `$AUTOSAR_PARAMDEFS`, a
@@ -36,3 +37,5 @@ BSWMD defs take precedence for vendor-specific parameters.
36
37
  `probe` sets the compiler/flags for the compile-only sizeof probe (H2L-NVM-001; default: the Makefile's CC/CFLAGS,
37
38
  else host `cc` with the discovered header dirs). `checkGenerated` turns the stale-Output check (H2L-GEN-001, run in a
38
39
  scratch copy) on/off; by default it is on when a `generate` command exists and the configuration is under 5 MB.
40
+ `specDocs` (else `$AUTOSAR_SPEC_DOCS`, a path list) points `autosar_spec_id_check` at your own text exports
41
+ (.txt/.md) of the AUTOSAR specifications; the pack bundles no spec text and no requirement-ID index.