From 6b4d74336a4165a477133704ef7dbafb7fa030e4 Mon Sep 17 00:00:00 2001 From: longvo920 Date: Sun, 23 Aug 2026 11:21:02 +0700 Subject: [PATCH 1/2] docs: rewrite the README and usage guide in plain language The docs leaned on metaphor where a plain description was available -- an ARXML called an "interface contract", an A2L a "calibration surface", a single-model regen called a "fingerprint". A reader who knows AUTOSAR but not this codebase gets nothing from those, and the Vietnamese translation turned them into literal nonsense ("hop dong interface"). Every concept is now defined before a rule is built on it, the rename and reorder conditions became lists instead of chained prose, and the reasons given are the ones a user cares about rather than the internal design rule. The README also gained the consistency check, which shipped in 1.10/1.11 and was never documented there, plus the logo header. Same content throughout: the flag table, the noise rules, the exit codes and every anchor are unchanged. --- README.md | 113 +++++++------ docs/usage.md | 298 +++++++++----------------------- docs/vi/README.md | 194 ++++++++++++--------- docs/vi/usage.md | 421 ++++++++++++++++++++++++++-------------------- 4 files changed, 495 insertions(+), 531 deletions(-) diff --git a/README.md b/README.md index dc07551..7e1f2ef 100644 --- a/README.md +++ b/README.md @@ -1,142 +1,151 @@ -# CodeGen Compare Tool +

+ CodeGen Compare Tool +

-[![Test](https://github.com/longvo92/codegen-compare-tool/actions/workflows/test.yml/badge.svg)](https://github.com/longvo92/codegen-compare-tool/actions/workflows/test.yml) -[![Python 3.8+](https://img.shields.io/badge/python-3.8%2B-blue.svg)](https://www.python.org/downloads/) -[![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE) -[![Release](https://img.shields.io/github/v/release/longvo92/codegen-compare-tool?label=release&color=blue)](https://github.com/longvo92/codegen-compare-tool/releases/latest) +

+ Test + Python 3.8+ + License: MIT + Release +

-🇻🇳 **Tiếng Việt:** [README](docs/vi/README.md) · [Hướng dẫn](docs/vi/usage.md) · [Kiến trúc](docs/vi/architecture.md) +

🇻🇳 Tiếng Việt: README · Hướng dẫn · Kiến trúc

-Diff two AUTOSAR code-generation output folders (MATLAB/Simulink Embedded Coder) and show **only the changes that matter**. +Regenerate a Simulink model and the diff against yesterday's output can run into thousands of lines — a new timestamp banner, a fresh UUID on every ARXML element, variable names the codegen renumbered from scratch. Somewhere in that pile there might be an actual behaviour change, or there might not be, and finding out by scrolling is how a five-minute code review turns into an afternoon. -Regenerating a Simulink model rewrites timestamps, UUIDs, comment banners and auto-generated variable names even when the behaviour is identical. This tool classifies every hunk as *real* or *ignorable*, then gives you two ways to review the result — a self-contained **HTML report** and a **side-by-side desktop viewer**, both over one compare core, so a verdict never depends on how you look at it. +This tool reads both folders, works out which of those thousands of lines are just the generator's fingerprints and which ones are real, and shows you only the second kind. Point it at an old codegen output and a new one, and it tells you — in plain terms, and in AUTOSAR terms — what actually changed. -| | For | Runs when | +It gives you two ways to look at that answer: a **desktop viewer** for reviewing interactively, and a **CLI** that writes a self-contained **HTML report** and sets an exit code your pipeline can gate on. Both run on the exact same compare engine, so you never get two different answers depending on which one you opened. + +| | What it's for | When it runs | |---|---|---| -| **Viewer** | reviewing interactively: folder tree, two-pane diff, minimap, review notes | no folders on the command line (or a double-clicked `.exe`) | -| **CLI** | pipelines and scripts — writes the report, exit code gates the build | both folders named on the command line | +| **Viewer** | Reviewing by hand — folder tree, two-pane diff, minimap, review notes | No folders given on the command line (or you double-click the `.exe`) | +| **CLI** | Pipelines and scripts — writes the report, exit code gates the build | Both folders named on the command line | -**Zero dependencies for the compare itself** — Python 3.8+ standard library only for the CLI and the HTML report: no pip install, no server, no internet access. The viewer adds PySide6, imported only when it opens. +The compare itself — scanning, the noise rules, the diff, the HTML report — is **pure Python standard library**. Nothing to `pip install`, no server, no network call, ever. The viewer is the one piece that needs PySide6, and even that is only imported the moment it actually opens. -📖 **[Usage guide](docs/usage.md)** — every flag, the viewer's keys, the exact noise rules, the report's layout, CI and packaging. -🏗 **[Architecture](docs/architecture.md)** — how the pieces fit and why. +📖 **[Usage guide](docs/usage.md)** — every flag, the viewer's shortcuts, the exact noise rules, how the report is laid out, CI and packaging. -## Install +🏗 **[Architecture](docs/architecture.md)** — how the pieces fit together and why. -Run straight from a clone — nothing to install: +## Getting it -```bash -git clone https://github.com/longvo92/codegen-compare-tool.git -``` +You can run it straight out of a clone, nothing to install: ```bash +git clone https://github.com/longvo92/codegen-compare-tool.git python -m compare_tool --help ``` -Or install it as a command (`compare-tool`): +Or install it as a proper command, `compare-tool`: ```bash pip install git+https://github.com/longvo92/codegen-compare-tool.git ``` -For machines where you cannot install anything, build a [single file](docs/usage.md#single-file-build). +Stuck on a machine that won't let you install anything at all? There's a [single-file build](docs/usage.md#single-file-build) for that. -## Quick start +## A first compare ```bash python -m compare_tool --report out.html ``` -Writes a self-contained HTML report that opens in any browser and can be mailed as one file. - -Either folder can be a `.zip` — a build artifact downloaded from Azure DevOps, say. It is unpacked read-only to a temp directory, compared as if it were a folder, and cleaned up afterwards; the report header names the zip rather than the temp path: +That writes one self-contained HTML file — open it in any browser, email it, nothing else needed. Either side can also be a `.zip` (a build artifact pulled straight from Azure DevOps, say); it's unpacked read-only into a temp folder, compared as if it were a normal directory, and cleaned up afterwards. The report still shows the zip's name, not the temp path: ```bash python -m compare_tool baseline.zip current.zip --report out.html ``` -Leave the folders out and the viewer opens instead — drop the two folders (or two `.zip` artifacts) onto it: +Leave the folders off entirely and the viewer opens instead — just drag the two folders (or two `.zip`s) onto it: ```bash python -m compare_tool ``` -Exit codes — the contract with your pipeline: +If you're wiring this into a build, the exit code is the contract: | Code | Meaning | |---|---| | `0` | No real changes | -| `1` | Real changes found (the CI gate) | -| `2` | **Compare INCOMPLETE** — a path could not be listed, read or compared, or the report could not be written | +| `1` | Real changes found — the usual CI gate | +| `2` | **Compare INCOMPLETE** — some path couldn't be listed, read or compared, or the report couldn't be written | -Exit `2` always shows: `!!` in the terminal, a red banner in the report. `--exit-zero` does not suppress it. A run with no record must never look like a clean one. +Exit `2` is loud on purpose: `!!` in the terminal, a red banner in the report, and `--exit-zero` does not silence it. A run that couldn't produce a real answer should never look like a clean one. -## What it filters +## What actually gets filtered out -| Kind | Rule | Files | +| Kind | What it catches | Files | |---|---|---| | `comment` | C/C++/A2L comments (`//`, `/* */`), XML comments (``), `#` line comments (Python, YAML) | .c .h .cpp .hpp .arxml .a2l .py .yaml .yml | -| `rename` | Consistent 1-to-1 renaming of generator-owned names. Anything the mapping cannot fully explain stays a real change | .c .h | +| `rename` | A consistent 1-to-1 rename of generator-owned names — anything the mapping can't fully explain is still a real change | .c .h | | `uuid` | `UUID="..."` attributes | .arxml .xml | | `timestamp` | `` blocks, `` | .arxml .xml | -| `sw-version` | `` stamps, bumped on every regenerate | .arxml .xml | +| `sw-version` | `` stamps, which bump on every regenerate | .arxml .xml | | `description` | ``, ``, `` | .arxml .xml | | `whitespace` | Indentation, trailing spaces, blank lines | all | | `line-endings` | CRLF vs LF, BOM | all | -**If it cannot be proven to be noise, it is a real change.** `SIG_TORQUE_MIN` → `SIG_TORQUE_MAX` is a real change; `rtb_AND_c4nxjoom3d` → `rtb_AND_j2kqp1wxab` is a rename. A block that moved intact is labelled `moved`, coloured blue, and still counts as Modified. Comment-only files are their own category, separate from Unimportant. +The rule the tool never bends: **if it can't be proven to be noise, it's a real change.** `SIG_TORQUE_MIN` becoming `SIG_TORQUE_MAX` is a real change; `rtb_AND_c4nxjoom3d` becoming `rtb_AND_j2kqp1wxab` is a rename the generator made up. A block that moved intact gets its own `moved` label, coloured blue, and still counts toward Modified — it's not hidden, just explained. A file that's only had its comments touched gets its own category too, separate from the merely-unimportant, because "the comment banner moved" and "an identifier got renamed" are not the same kind of nothing. -→ [the exact rules](docs/usage.md#what-counts-as-noise) +→ [the exact rules, one by one](docs/usage.md#what-counts-as-noise) -## AUTOSAR semantic summary +## An AUTOSAR-level summary, not just a text diff -Both surfaces open with **what changed in AUTOSAR terms**, not just in text: port interfaces, SWCs, ports, runnables, events (including a TIMING-EVENT period going `0.01s → 0.02s`), `Rte_*` access points and A2L `CHARACTERISTIC`/`MEASUREMENT` objects. Files are grouped by Simulink model. +Both the viewer and the report open with what changed **in AUTOSAR terms** before you ever look at a line of C or XML: port interfaces, SWCs, ports, runnables, events (a `TIMING-EVENT` period going from `0.01s` to `0.02s` shows up as exactly that), `Rte_*` access points, and A2L `CHARACTERISTIC` / `MEASUREMENT` objects — grouped by the Simulink model they belong to. -→ [what is extracted, and how it is shown](docs/usage.md#autosar-semantic-summary) +→ [what gets extracted, and how it's shown](docs/usage.md#autosar-semantic-summary) -## Side-by-side viewer +## Catching a stale or partial regenerate -```bash -pip install "codegen-compare-tool[viewer]" -``` +A model's ARXML declares its interface — which ports, runnables and events it has. Its A2L declares the calibration and measurement variables. The generated C has to match both: add a port in the ARXML and the code needs a matching `Rte_*` call, add a characteristic in the A2L and the code needs a matching variable. + +So when a port, runnable or calibration variable is added or removed in the ARXML/A2L but that model's C file didn't change by a single byte, the tool flags it — usually the sign of a regenerate that didn't finish. A file-by-file diff can't catch this, because each file is fine on its own; what's wrong is that the two no longer agree. + +It also checks across models: if model A's code gains a new `Rte_*` call while model B's code is untouched, you probably regenerated only model A. That new `Rte_*` call needs the RTE layer regenerated before the code will build and integrate. Both are heads-up flags shown next to the AUTOSAR summary — neither changes a file's verdict or the exit code. + +→ [how the consistency check works](docs/usage.md#consistency-check) + +## The desktop viewer ```bash +pip install "codegen-compare-tool[viewer]" python -m compare_tool ``` ![Side-by-side viewer](resources/pic/main_page.png) -Folder tree, two-pane diff with minimap and syntax colouring, `F7`/`F8` through every change in the whole compare, `Ctrl+F` across files, per-change review notes, a **current-function caption** that names the enclosing C/C++ function, Python class/method, AUTOSAR SHORT-NAME or A2L block as you scroll, and a commit picker so **one** folder in a git checkout can be compared against its own history. The built-in `Help` → `User guide` (`F1`) works offline. +A folder tree on the left, a two-pane diff with a minimap and syntax colouring on the right. `F7`/`F8` step through every change in the whole compare, `Ctrl+F` searches across every file, and you can leave a review note on any individual change. A caption above the diff tracks whatever you're scrolled into — the enclosing C/C++ function, the Python class or method, the AUTOSAR SHORT-NAME, the A2L block — so you're never lost about *where* you are. There's also a commit picker, so you can compare one folder in a git checkout against its own history instead of against a second folder. Press `F1` for the built-in user guide; it works offline like everything else here. → [reading a scan, review mode, every shortcut](docs/usage.md#side-by-side-viewer) -## HTML report +## The HTML report ![Report viewer](resources/pic/report_page.png) -One self-contained file per compare: badge toggles, folder tree, filter box, collapsible diffs. It shows **three lines either side of each real change**, not the whole file — the noise elsewhere takes up no space until you ask for it. Each change is captioned with the function it lives in, and a modified file lists the functions its changes touch. Dark and light are both embedded, so the switch fetches nothing on a machine with no internet. +One file per compare, and it's genuinely self-contained — badge toggles, folder tree, a filter box, diffs you can collapse, all in a single `.html` you can attach to an email. It shows three lines of context on either side of each real change rather than the whole file, so the noise sitting around it takes up no screen space until you specifically ask to see it. Every change is captioned with the function it's inside, and a modified file lists every function its changes touch. Both dark and light themes are baked in, so switching doesn't fetch anything — it'll render exactly the same on a machine with no internet as on yours. → [the layout, the badges, what collapses and why](docs/usage.md#html-report) -## CI integration +## Wiring it into CI ```bash python -m compare_tool old_dir new_dir --exit-zero --exclude compare_report.html ``` -`--exit-zero` keeps the build green on regenerated code; `--exclude` keeps the previous run's report from counting as a diff. Publish `compare_report.html` as a build artifact. See [azure-pipelines.yml](azure-pipelines.yml) for a working example. +`--exit-zero` keeps the build green even when the only thing that happened was a regenerate; `--exclude` stops the previous run's own report from being counted as part of the diff. Publish `compare_report.html` as a build artifact and you've got a clickable record of every run. [azure-pipelines.yml](azure-pipelines.yml) has a working example if you want to see it end to end. -→ [flags and packaging for locked-down machines](docs/usage.md#ci-integration) +→ [flags, exit codes, and packaging for locked-down build machines](docs/usage.md#ci-integration) -## Development +## Contributing ```bash python -m unittest discover -s tests ``` -CI runs the suite on Linux and Windows against Python 3.8 and 3.11, plus a headless scan of the fixture tree checking both the report and the exit code. +CI runs that suite on Linux and Windows against Python 3.8 and 3.11, plus a headless scan over the fixture tree that checks both the report and the exit code. -Issues and pull requests are welcome. Please keep the **compare core stdlib-only** — it has to run on locked-down build servers, so PySide6 stays confined to `compare_tool/qtviewer/` and is imported only when the viewer opens — and add a test under `tests/` for any new rule. [docs/architecture.md](docs/architecture.md) has the module map and a *where to change what* table. +Issues and pull requests are welcome. The one rule that matters: the **compare core stays stdlib-only** — it has to run on build servers where nothing gets installed, so PySide6 lives entirely inside `compare_tool/qtviewer/` and is only imported once the viewer actually opens. If you're adding a noise rule, add a test for it under `tests/` too. [docs/architecture.md](docs/architecture.md) has the module map and a table of what to touch for what kind of change. ## Author diff --git a/docs/usage.md b/docs/usage.md index fbbeb55..a0413b4 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -2,9 +2,7 @@ 🇻🇳 Bản tiếng Việt: [vi/usage.md](vi/usage.md) -Everything the [README](../README.md) links out to: every flag, the viewer's -keys, the exact noise rules, what the report shows and why, CI and packaging. -For *how the code is put together*, see [architecture.md](architecture.md). +This is the long version of everything the [README](../README.md) points to: every flag, every viewer shortcut, the exact noise rules, why the report shows what it shows, and how to wire the whole thing into CI. If you're after *how the code itself is organized* rather than how to run it, that's [architecture.md](architecture.md) instead. - [Command line](#command-line) - [Side-by-side viewer](#side-by-side-viewer) @@ -12,6 +10,7 @@ For *how the code is put together*, see [architecture.md](architecture.md). - [Moved block detection](#moved-block-detection) - [AUTOSAR semantic summary](#autosar-semantic-summary) - [Grouping by model / SWC](#grouping-by-model--swc) +- [Consistency check](#consistency-check) - [HTML report](#html-report) - [CI integration](#ci-integration) - [Single-file build](#single-file-build) @@ -22,12 +21,7 @@ For *how the code is put together*, see [architecture.md](architecture.md). python -m compare_tool [--report out.html] ``` -Either positional may be a `.zip` (an Azure DevOps build artifact, for -instance). It is unpacked read-only into a temp directory, compared as a -folder, and removed on exit; a lone wrapper directory inside the archive is -descended into, and the report header names the zip rather than the temp path -(`--baseline-name` / `--current-name` still override). A zip that cannot be -read stops the run loudly — it never falls through to an empty folder. +Either side can be a `.zip` instead of a folder — an Azure DevOps build artifact, say. The tool unpacks it read-only into a temp directory, compares it like an ordinary folder, and deletes the temp copy on exit. If the archive has a single wrapper directory inside it, the tool descends into that automatically. The report header shows the zip's name rather than the temp path it was unpacked to (`--baseline-name` / `--current-name` still override that if you want something else). A zip that can't be read stops the run with a loud error — it never quietly falls through to comparing an empty folder. | Flag | Meaning | |---|---| @@ -41,19 +35,13 @@ read stops the run loudly — it never falls through to an empty folder. | `--theme dark\|light` | Colour scheme the report and the viewer open with (default `dark`). The report carries **both** and has its own switch, so this only sets what the reader sees first | | `--qt`, `--viewer` | Open the side-by-side viewer on folders named on the command line, instead of comparing them in the terminal. Needs the `viewer` extra | -Omitting `old_dir`/`new_dir` opens the viewer. `--gui` (the tkinter panel) was -removed in 1.1.0. +Leave `old_dir`/`new_dir` off entirely and the viewer opens instead. (The old tkinter panel, `--gui`, is gone as of 1.1.0, if you're looking at an older doc that still mentions it.) -A report path that cannot be written (missing folder, file open in a browser, -read-only) is exit `2` with a one-line reason — never a traceback, and never -exit `1`, which a pipeline reads as the ordinary "real changes found". What the -scan did find is still printed. +If the report path can't be written — the folder's missing, the file is open in a browser, the disk is read-only — that's an exit `2` with a one-line reason, never a traceback and never exit `1` (which a pipeline would read as "ordinary real changes found", the wrong signal entirely). Whatever the scan *did* manage to find still gets printed before the process exits. ## Side-by-side viewer -Desktop app (PySide6): folder tree, two-pane diff with a minimap and syntax -colouring, per-change review notes, and a commit picker when the folder is in a -git checkout. +The viewer is a PySide6 desktop app: a folder tree, a two-pane diff with a minimap and syntax colouring, review notes you can leave on individual changes, and a commit picker for when the folder you're looking at happens to be a git checkout. ```bash pip install "codegen-compare-tool[viewer]" # or: pip install PySide6 @@ -67,32 +55,21 @@ python -m compare_tool # then drop the fo python -m compare_tool --qt # or start loaded ``` -Two ways in: `Open folders…` for two folders you name yourself, and -`Git compare…` for **one** folder in a git checkout — it lists the commits that -touched that folder, checks the one you pick out to a temp folder (read-only — -your working copy is never touched), and compares as usual. +There are two ways to load a compare. `Open folders…` is for two folders you name yourself. `Git compare…` is for the case where you only have **one** folder and it's a git checkout — it lists the commits that touched it, checks out whichever one you pick into a temp folder (read-only, so your working copy is never touched), and compares against that. -Either side can be a `.zip` instead of a folder: drop it onto the window, or use -the `Zip…` button in `Open folders…`. It is unpacked to a temp folder and the -pane is labelled by the zip name, not the temp path. +Either side can be a `.zip` too — drop it straight onto the window, or use the `Zip…` button inside `Open folders…`. It's unpacked to a temp folder and the pane is labelled by the zip's own name, not the temp path. -Once a file is open, a **caption beside its name** shows the function you are -looking at — the enclosing C/C++ function, Python class/method, AUTOSAR -SHORT-NAME or A2L block — and follows the scroll, so you always know where in a -long generated file you are. -For **C files**, when the function's signature scrolls off the top it is also -**pinned to the top of each pane** (like VS Code's sticky scroll) until you -leave the function. +Once a file is open, a small caption next to its name tracks whatever you're looking at — the enclosing C/C++ function, a Python class or method, an AUTOSAR SHORT-NAME, an A2L block — and updates as you scroll, so you're never lost inside a long generated file. For C files specifically, if the function's signature scrolls off the top of the pane, it stays pinned there (much like VS Code's sticky scroll) until you actually leave the function. ### Reading a scan -- The scan **opens on the first change** — the pane is never empty next to a tree full of results. -- `F8` / `F7` step through changes in the open file, then **carry on into the next (previous) changed file**, wrapping at the end. `Ctrl+Home` / `Ctrl+End` stay inside the file. Comment / noise files join the walk while their category is ticked, but stopping on one signs off nothing — only real and moved changes enter the review record. -- `Ctrl+F` **finds text in the open file** (either side, `F3` / `Shift+F3` to step, `Esc` to close). The query survives moving to another file, so an identifier can be chased across the compare. -- `Hide identical` leaves only the files with a difference in the tree. It is a view: verdicts, counts and the exported report are untouched. -- Unticking `Comment` / `Unimportant` **greys those lines out** rather than removing them: they keep their place and line numbers, lose their red/green, and drop off the minimap and `F7`/`F8`. Left ticked (the default) they keep their colour and are `F7`/`F8` stops like any other change. -- The change you are on is marked by a **small arrow in the line-number gutter**, on both panes — so `F7`/`F8` visibly move even in a file short enough that there is nothing to scroll. -- `☀ Light` / `☾ Dark` in the toolbar switches the colour scheme; `--theme` picks the one it starts in. C, C++, ARXML/XML, A2L, Python, JSON and YAML are syntax-coloured in both. +- The scan **opens on the first change** — you never land on an empty pane next to a tree full of results. +- `F8` / `F7` step through the changes in the open file, then carry on into the next (or previous) changed file once you run out, wrapping around at the end. `Ctrl+Home` / `Ctrl+End` stay inside the current file. Comment and noise files join that walk while their category is ticked on, but stopping on one signs off nothing — only real and moved changes ever enter the review record. +- `Ctrl+F` finds text in the open file, either side, with `F3` / `Shift+F3` to step through matches and `Esc` to close it. The query survives moving to another file, so you can chase one identifier across the whole compare. +- `Hide identical` narrows the tree down to files that actually differ. It's purely a view — verdicts, counts and the exported report are untouched by it. +- Unticking `Comment` / `Unimportant` greys those lines out rather than deleting them: they keep their place and their line numbers, just lose their red/green colouring and drop off the minimap and the `F7`/`F8` walk. Left ticked, which is the default, they keep their colour and behave like any other stop. +- Wherever you currently are is marked with a small arrow in the line-number gutter, on both panes, so `F7`/`F8` visibly move you even in a file too short to scroll. +- `☀ Light` / `☾ Dark` in the toolbar swaps the colour scheme on the fly; `--theme` just picks which one it opens in. C, C++, ARXML/XML, A2L, Python, JSON and YAML are all syntax-coloured in either theme. | Mark | Verdict | Meaning | |---|---|---| @@ -106,41 +83,23 @@ leave the function. ### Renamed and moved files -Rename a model, move `Foo.c` from `swc_a/` to `swc_b/`, or restructure the -output folders, and the file comes back as one Added plus one Deleted. The tool -matches those two back up and reports them as one move: +Rename a model, move `Foo.c` from `swc_a/` to `swc_b/`, or restructure the output folders some other way, and on its own that would just look like one file Added and a different one Deleted. The tool matches those two back up and reports them as a single move instead: > `swc_b/Sub.c` **Added** *(moved from swc_a/Sub.c — and changed, 89% alike)* -The Added entry then shows a **diff against the file it came from** instead of -its whole contents, and the Deleted entry links to it rather than printing the -same lines a second time. +The Added entry then shows a diff against the file it came from, instead of dumping its whole contents, and the Deleted entry just links to it rather than printing the same lines a second time. -In the viewer both rows read `Added (moved)` / `Deleted (moved)` in the Status -column, with the path and the similarity on hover. Rows that did not move are -labelled exactly as before. +In the viewer both rows read `Added (moved)` / `Deleted (moved)` in the Status column, with the origin path and the similarity percentage available on hover. Files that genuinely didn't move keep the labels they always had. -The pairing needs the two files to share an extension, to pick each other as -the best match, and to be clearly better than the runner-up — generated files -resemble each other enough that a near-tie is not an answer. Files it cannot -match are reported as plain Added / Deleted, exactly as before. +The pairing logic needs the two files to share an extension, to pick each other as the best available match, and to be clearly better than whatever the runner-up match was — generated files tend to resemble each other closely enough that a near-tie isn't a real answer. Anything it can't confidently match falls back to plain Added / Deleted, exactly as it would without this feature at all. -Both files keep their own verdict and their place in the counts, and **the exit -code does not change**: a file that moved is still a change to the tree, so a -pipeline gating on Added/Deleted keeps working. +Both files still keep their own verdict and their own place in the counts, and the exit code doesn't change because of a move — a moved file is still a change to the tree, so a pipeline gating on Added/Deleted keeps working exactly as before. ### Review mode -`Review mode` adds the note box and a `Review` column in the tree — green when -every change in a row is signed off, amber part way, grey when none is. Sign off -one change (`Ctrl+R`) or a whole file (`Ctrl+Shift+R`); a note follows the -change's *content*, not its line number, so it survives a rescan. Saves to -`codegen-review.json` next to the CURRENT folder. +Turning on `Review mode` adds a note box and a `Review` column to the tree — green when every change in a row is signed off, amber when it's part way there, grey when none of it is. You sign off one change with `Ctrl+R`, or a whole file at once with `Ctrl+Shift+R`. A note is attached to the change's *content*, not its line number, so it survives a later rescan instead of drifting onto the wrong line. Everything saves to `codegen-review.json` next to the CURRENT folder. -`Export report…` (`Ctrl+E`) writes the same self-contained HTML report the CLI -writes, with the review notes folded in. It is always built from the **complete -scan**, never from what is on screen — a category you collapsed in the tree still -appears in the file with its real verdict. +`Export report…` (`Ctrl+E`) writes the same self-contained HTML report the CLI writes, with your review notes folded in. It's always built from the complete scan, never from whatever happens to be on screen at the time — so a category you'd collapsed in the tree still shows up in the exported file with its real verdict. | Shortcut | Action | |---|---| @@ -170,73 +129,35 @@ appears in the file with its real verdict. ### Renames -Auto-generated name churn is recognised as a `rename`. Two identifiers count as -the same name only when the code generator owns both — a generated prefix -(`rtb_`, `rtu_`, `rty_`, `rtDW`, `rtP`, `rtC`, `rtZC`, `localB`, `localDW`, …), -a DWork field (`_DSTATE`, `_PreviousInput`, `_MODE`, `_SubsysRanBC`, …), or an -embedded block-path checksum (`Sub_c4nxjoom3d_step` → `Sub_j2kqp1wxab_step`) — -**and** they share a root once the generated part is removed. The generated part -is a mangling suffix (`_c`, `_o4`) or a checksum (`rtb_AND_c4nxjoom3d` → -`rtb_AND_j2kqp1wxab`); renumbered MATLAB Coder temporaries (`tmp`, `idx`, -`loop_ub`, `i`) are covered too. - -A shorter name can stop an argument wrapping at 80 columns, so the two sides -hold the same statements over a different number of lines. Such a hunk is -compared as one token stream — where the newlines fell stops mattering, while -token order still has to match exactly. - -Everything else keeps its suffix as meaning. `SIG_TORQUE_MIN` → -`SIG_TORQUE_MAX` and `CFG_TIMEOUT_MS` → `CFG_TIMEOUT_US` are real changes, and -so are `rtb_AND_…` → `rtb_OR_…` (a different block drives that buffer) and -`Sub_…_step` → `Sub_…_Init` (a different entry point). Digits glued to a block -name (`rtb_Switch1` vs `rtb_Switch2`) are part of the name, not a mangle tail. +Auto-generated name churn gets recognised as a `rename`, but only under a fairly strict test. Two identifiers count as the same name only when the code generator plausibly owns both of them — a generated prefix (`rtb_`, `rtu_`, `rty_`, `rtDW`, `rtP`, `rtC`, `rtZC`, `localB`, `localDW`, and so on), a DWork field (`_DSTATE`, `_PreviousInput`, `_MODE`, `_SubsysRanBC`, …), or an embedded block-path checksum (`Sub_c4nxjoom3d_step` → `Sub_j2kqp1wxab_step`) — **and** they still share a root once the generated part is stripped away. That generated part is either a mangling suffix (`_c`, `_o4`) or a checksum (`rtb_AND_c4nxjoom3d` → `rtb_AND_j2kqp1wxab`); renumbered MATLAB Coder temporaries (`tmp`, `idx`, `loop_ub`, `i`) fall under the same rule. + +Sometimes a shorter name is enough to stop an argument list wrapping at 80 columns, which leaves the two sides holding the same statements over a different number of lines. That kind of hunk gets compared as one token stream instead, so where the newlines happen to fall stops mattering — token order still has to match exactly, though. + +Everything else keeps its suffix as meaning, which is the whole point of being this strict. `SIG_TORQUE_MIN` becoming `SIG_TORQUE_MAX` is a real change, and so is `CFG_TIMEOUT_MS` becoming `CFG_TIMEOUT_US`, `rtb_AND_…` becoming `rtb_OR_…` (a different block is driving that buffer now), or `Sub_…_step` becoming `Sub_…_Init` (a different entry point entirely). Digits glued onto a block name, like `rtb_Switch1` versus `rtb_Switch2`, are part of the name rather than a mangle tail, so those stay real too. ### Reorder -Regenerating a model routinely emits the same independent assignments in a -different order — output ports, temporaries — which the text reads as a change -even though the block computes identical values. A `reorder` fold recognises -this, but only where it can be **proven**, never guessed: +Regenerating a model routinely emits the same independent assignments — output ports, temporaries — in a different order, which a plain text diff reads as a change even though the block computes exactly the same values. A `reorder` fold recognises this case, but only where it can actually be **proven**, never guessed at: -- every line on both sides is a side-effect-free scalar assignment - (`ident = expr;` — no call, no store through an array/pointer/field, no - control flow, no declaration with a type); -- the two sides hold the same statements, just permuted; -- the new order preserves **every data dependence** — whenever two statements - share a variable and one writes it, their relative order is unchanged. +- every line on both sides is a side-effect-free scalar assignment (`ident = expr;` — no call, no store through an array/pointer/field, no control flow, no declaration with a type); +- the two sides hold the same statements, just in a different order; +- the new order preserves **every data dependence** — whenever two statements share a variable and one of them writes it, their relative order hasn't changed. -Two straight-line schedules that agree on the order of every dependent pair -compute the same result, so the reorder is behaviour-preserving. Anything that -does not meet all three — a call between the lines, a changed right-hand side, a -flipped dependent pair — leaves the whole block a real change. It errs toward -calling a block real, never toward hiding one. +Two straight-line schedules that agree on the order of every dependent pair are guaranteed to compute the same result, so folding the reorder is behaviour-preserving, not a guess. If any of those three conditions fails — a call sneaks in between the lines, a right-hand side actually changed, a dependent pair got flipped — the whole block stays a real change. The rule errs toward calling a block real rather than toward hiding one; when in doubt, it shows you the diff. ### Comment is its own category -A file whose differences are *only* comments is reported as **Comment**, -separate from **Unimportant** (UUIDs, timestamps, SW-VERSION, descriptions, -renames, whitespace) — a rewritten comment banner triages differently from a -renamed identifier. Separate counts in the CLI summary and its own tree marker -in the viewer. A file mixing comments *with* other noise stays Unimportant. The -viewer has a rule toggle for each; the HTML report gives `Unimportant` a badge -and never renders comment lines at all. +A file whose only differences are comments gets reported as **Comment**, kept separate from **Unimportant** (which covers UUIDs, timestamps, SW-VERSION, descriptions, renames and whitespace) — because "someone rewrote the comment banner" and "an identifier got renamed" are different enough findings that they shouldn't share a bucket. Each gets its own count in the CLI summary and its own tree marker in the viewer. A file that mixes comment changes *with* other noise stays classified as Unimportant, since the narrower Comment label wouldn't be accurate for it. The viewer has a separate rule toggle for each, and the HTML report gives `Unimportant` its own badge while never rendering comment lines at all. ## Moved block detection -A block deleted in one place and reappearing intact elsewhere (Embedded Coder -reorders functions and declarations when a model changes) is labelled `moved` -and coloured **blue** instead of red/green. Still counts as **Modified** — -reordering can change behaviour — it's just easier to see than two large -red/green blocks. +When a block disappears from one place in a file and reappears intact somewhere else — a common side effect of Embedded Coder reordering functions and declarations when a model changes — it's labelled `moved` and coloured **blue** instead of the usual red/green. It still counts as **Modified**, because reordering code can genuinely change behaviour, but it's a lot easier to read at a glance than two large red and green blocks that turn out to be the same thing. -Matching ignores generated-name churn, so a block that moved *and* had its -checksums regenerated is still recognised as one move rather than an unrelated -delete plus insert. +The matching step ignores generated-name churn, so a block that both moved *and* had its checksums regenerated in the process is still recognised as one move, rather than being reported as an unrelated delete plus insert. ## AUTOSAR semantic summary -The tool extracts AUTOSAR information from both sides and reports changes at the -**semantic** level, not just as text: +Alongside the text diff, the tool extracts AUTOSAR information from both sides and reports what changed at the **semantic** level: | Source | Extracted | Reported | |---|---|---| @@ -244,108 +165,63 @@ The tool extracts AUTOSAR information from both sides and reports changes at the | `.arxml`/`.xml` | **SWCs** (APPLICATION, SENSOR-ACTUATOR, SERVICE, CDD, ECU-ABSTRACTION, NV-BLOCK) | added / removed | | `.arxml`/`.xml` | SWC **ports** (P/R/PR + referenced interface), **runnables** (+ SYMBOL), **events** (kind, PERIOD, triggered runnable) | added / removed / **changed** (e.g. a TIMING-EVENT period going `0.01s → 0.02s`, a port pointing at a different interface) | | `.c` | **RTE access points** — every `Rte_Read/Write/Call/IrvRead/IrvWrite/Mode/Switch/…` call (comments stripped before counting) | added / removed | -| `.a2l` | **Calibration objects** — `CHARACTERISTIC` / `MEASUREMENT` by name (comments and strings stripped first, so commented-out blocks do not count) | added / removed | +| `.a2l` | **Calibration objects** — `CHARACTERISTIC` / `MEASUREMENT` by name (comments and strings stripped first, so commented-out blocks don't count) | added / removed | -How it is shown: +Where you see it: -- **CLI**: `ARXML interfaces`, `AUTOSAR behavior`, `RTE access points` and `A2L objects` blocks listing `+`/`-`/`~` entries with the file each belongs to. -- **HTML report**: an **AUTOSAR changes** section at the top of the page, grouped by kind (port interfaces / software components / ports / runnables / events / RTE access points / A2L variables). Clicking a file name jumps to its detailed diff, and each file in Detailed changes carries its own `Interfaces:` / `Behavior:` / `RTE:` / `A2L:` note. The section is always there — with nothing to list it says so, because "no AUTOSAR-level changes" is the finding, and a heading that disappears reads as a check that never ran. -- Whole files added or deleted contribute every interface / SWC / RTE call / A2L object inside them as added or removed. +- **CLI**: `ARXML interfaces`, `AUTOSAR behavior`, `RTE access points` and `A2L objects` blocks, each listing `+`/`-`/`~` entries alongside the file they belong to. +- **HTML report**: an **AUTOSAR changes** section at the top of the page, grouped by kind — port interfaces, software components, ports, runnables, events, RTE access points, A2L variables. Clicking a file name jumps straight to its detailed diff, and every file in Detailed changes carries its own `Interfaces:` / `Behavior:` / `RTE:` / `A2L:` note. This section always renders, even with nothing to list — "no AUTOSAR-level changes" is itself a finding worth stating, and a heading that just disappears would read as a check that never ran. +- Whole files that were added or deleted contribute every interface, SWC, RTE call and A2L object inside them as added or removed, the same as if each had changed individually. -A file whose XML fails to parse is skipped from this summary (its text diff -still shows in full). An unknown `Rte_` call isn't counted here but still -appears in the diff. +A file whose XML fails to parse is skipped from this summary specifically — its text diff still shows in full elsewhere. An `Rte_` call the tool doesn't recognise isn't counted here either, but it still shows up in the ordinary diff. ## Grouping by model / SWC -Files are grouped by **Simulink model** using the Embedded Coder AUTOSAR naming -convention (`X.c`, `X.h`, `X.arxml`, `Rte_X.h`, `X_data.c`, the modular ARXML -set, …). Files that match no model land in a final **Shared / other** group. +Files are grouped by **Simulink model**, following the Embedded Coder AUTOSAR naming convention (`X.c`, `X.h`, `X.arxml`, `Rte_X.h`, `X_data.c`, the modular ARXML set, and so on). Anything that doesn't match a model lands in a final **Shared / other** group instead of getting silently dropped. ## Consistency check -A model's ARXML is its contract and its A2L is its calibration surface; both are -realised by the generated C. So the dependency runs **one way**: if the -interface or the calibration really changed, the code must have changed too — a -new port needs a new RTE access, a new characteristic needs a new symbol. When -an access point moves in the ARXML or A2L with the generated C left identical, -the report (below the AUTOSAR changes), the viewer (bottom-left, under the -quick-changes panel) and the terminal flag the model — the usual sign of a stale -or partial regenerate, and the one thing a file-by-file view cannot show, -because each file is individually fine and the mismatch lives *between* them. - -The trigger is measured at the **access-point level, not the file level**: a -port-interface or SWC port/runnable/event added or removed (ARXML), or a -calibration object added or removed (A2L). The library packages an export -rewrites every time — base types, compu-methods, units — change the file's bytes -without touching an access point, and do **not** raise the advisory. - -A file the tool could not read that far into — malformed XML, or binary content -— is the exception: nothing was proven about its access points, so a change to -it still raises the advisory. Unprovable is never treated as noise. - -The reverse is **not** flagged. Code that changed while the ARXML and A2L did -not is the ordinary case — an internal logic or gain edit touches no interface -and no calibration variable, so there is nothing for them to follow. - -A second, cross-*model* heads-up rides along here. When one model's C gains an -**RTE access point** (`+ Rte_Write_…`) while a *peer* model's C stayed -byte-identical, the batch was a single-model quick regen, not a full one — a -real regenerate rewrites at least a timestamp banner in every model, so an -identical peer is proof it was left untouched. A new RTE access widens the -model's interface, so the RTE layer and the peer SWCs must be regenerated before -the code will integrate; the model is flagged *"gained an RTE access while a peer -model stayed identical — regenerate the architecture before integrating"*. - -It is a **heads-up, not a verdict**: it never folds a file, moves a count, or -changes the exit code. Only a *real* surface change counts — an ARXML that -merely churned UUIDs did not really change, so a stale C is not a desync. +A model's ARXML declares its interface — which ports, runnables and events it has. Its A2L declares the calibration and measurement variables. The generated C has to implement both: add a port in the ARXML and the code needs a matching `Rte_*` call, add a characteristic in the A2L and the code needs a matching variable. + +That relationship only runs **one way**. When a port, runnable, event or calibration variable is added or removed in the ARXML or A2L while that model's C file doesn't change by a single byte, something is wrong: the report (just below the AUTOSAR changes), the viewer (bottom-left, under the quick-changes panel) and the terminal all name that model. The usual cause is a regenerate that didn't finish, or that skipped a model. A file-by-file diff can't catch it, because each file is fine on its own — what's wrong is that the two no longer agree. + +The check is measured **per access point, not per file**: it needs a port interface or an SWC port/runnable/event added or removed in the ARXML, or a calibration object added or removed in the A2L. Every export also rewrites the shared library packages — base types, compu-methods, units — which changes plenty of bytes without touching a single port or runnable, so those alone never raise the advisory. + +There's one deliberate exception: a file the tool can't parse — malformed XML, or binary content — raises the advisory on any change to it, because the tool couldn't read it to find out whether its access points changed. Anything it can't verify is never filed as noise. + +The reverse is *not* flagged: C code changing while the ARXML and A2L stay the same is ordinary — an internal logic or gain edit touches no interface and no calibration variable. + +There's a second check, this one **across models**. When model A's C gains a new `Rte_*` call (`+ Rte_Write_…`) while model B's C doesn't change a byte, you probably regenerated only model A. Why that's a safe read: a full regenerate rewrites at least a timestamp banner in every model, so a model left byte-identical wasn't generated at all. The new `Rte_*` call widens model A's interface, so the RTE layer and the remaining SWCs have to be regenerated before the code will build and integrate. That model gets flagged with *"gained an RTE access while a peer model stayed identical — regenerate the architecture before integrating."* + +Both are **heads-up flags, not verdicts**: they never fold a file, move a count, or touch the exit code. Only a real surface change counts — an ARXML that only churned its UUIDs didn't really change, so an unchanged C file next to it isn't treated as a mismatch. ## HTML report -Self-contained file, one per compare: badge toggles, folder tree, filter box, -collapsible diffs per file. One badge per category — `Modified`, `Added`, -`Deleted`, then `Unimportant`, which is the only one that starts off — so the -page opens on what matters. The code is **syntax-coloured** the same way the -viewer paints it, and the changed characters inside a line are highlighted -across the whole identifier, so `rtb_Sum1` → `rtb_Sum2` reads as one renamed -name rather than one changed digit. +The report is one self-contained file per compare: badge toggles, a folder tree, a filter box, and per-file diffs you can collapse or expand. There's one badge per category — `Modified`, `Added`, `Deleted`, then `Unimportant`, which is the only one that starts collapsed — so the page opens on what actually matters rather than burying it. Code is syntax-coloured the same way the viewer paints it, and the changed characters *inside* a line are highlighted across the whole identifier, so `rtb_Sum1` becoming `rtb_Sum2` reads as one renamed name instead of "one digit changed somewhere in there." ### What is shown, and what collapses -Each real change shows **three lines of context either side** — not the whole file: +Each real change shows three lines of context on either side of it — not the whole surrounding file: -- Comment / Unimportant hunks **inside that window** render in full, greyed. -- Hunks **outside every window** show nothing until you click `Unimportant`, which reveals them flat grey where they sit. -- A file with **no** real change keeps full context, and its collapsed hunks keep a `⋯ N lines hidden` placeholder. +- Comment / Unimportant hunks that fall **inside that window** render in full, just greyed out. +- Hunks that fall **outside every window** show nothing at all until you click `Unimportant`, at which point they appear flat grey exactly where they sit in the file. +- A file with **no** real change at all keeps its full context, and its collapsed hunks keep a `⋯ N lines hidden` placeholder rather than vanishing. -The lines are always in the file; only the screen is quiet. A file whose -differences are *only* comments gets no detail section — it keeps its `≉` mark -and `Comment` count in the tree. (Why the window is this tight → -[architecture](architecture.md#decisions-worth-knowing-before-you-change-something).) +The lines themselves are always in the file — only the screen stays quiet about them. A file whose differences are purely comments doesn't even get a detail section; it just keeps its `≉` mark and `Comment` count in the tree. (If you're curious why the window is kept this tight rather than wider, that's covered in [architecture.md](architecture.md#decisions-worth-knowing-before-you-change-something).) -`Focus on changes`, beside the folder tree, narrows the tree to files that -actually changed — identical, comment-only and Unimportant rows drop out, and a -folder left holding none of them goes with them. Like the viewer's -`Hide identical`, it is a view: verdicts and counts are untouched. A -`☀ Light` / `☾ Dark` button sits in the top right — both palettes are embedded in -the file, so switching fetches nothing and works on a machine with no internet. +`Focus on changes`, next to the folder tree, narrows the tree down to files that actually changed — identical, comment-only and Unimportant rows drop out, and any folder left holding none of them goes with them. Like the viewer's `Hide identical`, this is purely a view: verdicts and counts underneath are untouched. A `☀ Light` / `☾ Dark` button sits in the top right; both palettes are embedded in the file itself, so switching between them fetches nothing and works fine on a machine with no internet connection at all. ## CI integration -Run as a pipeline gate — one command, meaningful exit codes: +Run it as a pipeline gate — one command, and exit codes that actually mean something: ```bash python -m compare_tool old_dir new_dir --exit-zero --exclude compare_report.html ``` -`--exit-zero` keeps the build green on regenerated code; `--exclude` keeps the -previous run's report from counting as a diff. Publish `compare_report.html` as -a build artifact. +`--exit-zero` keeps the build green when the only thing that happened was a regenerate; `--exclude` stops the previous run's own report file from being counted as part of the diff. Publish `compare_report.html` as a build artifact and you've got a clickable record for every run. -A pipeline usually stages the baseline into a scratch directory, which leaves -the report header naming that directory. Name the two sides after what was -actually compared: +A pipeline usually stages the baseline into some scratch directory, which by default leaves the report header naming that scratch directory instead of anything meaningful. Name the two sides after what was actually compared instead: ```bash python -m compare_tool "$OLD_DIR" "$NEW_DIR" \ @@ -353,35 +229,22 @@ python -m compare_tool "$OLD_DIR" "$NEW_DIR" \ --current-name "build $BUILD_NUMBER" ``` -See [azure-pipelines.yml](../azure-pipelines.yml) for a working example (OLD -checked out via `git worktree`, NEW is the working tree). +See [azure-pipelines.yml](../azure-pipelines.yml) for a working example — OLD is checked out via `git worktree`, NEW is just the working tree. ### Machine-readable output -The HTML report is for a human and the exit code is for a gate. For a build that -wants to read *what* changed — annotate a pull request, feed a dashboard, drive -its own policy — write the result as data: +The HTML report is for a human, and the exit code is for a gate. If your build wants to actually read *what* changed — to annotate a pull request, feed a dashboard, drive its own policy on top — write the result out as data instead: ```bash python -m compare_tool old_dir new_dir --json result.json --sarif result.sarif ``` -Both are additive: the HTML report is still written. Either can be given alone. +Both are additive: the HTML report still gets written alongside them, and either flag works fine on its own. -- `--json` writes the whole scan under a versioned `schema`: every file's - verdict, its hunks, renames and AUTOSAR extras, the run summary, the - consistency advisories, and the same `exit_code` the process returns (so the - file and `$?` cannot disagree). Pin `schema` and an internal refactor will not - move the shape under you. -- `--sarif` writes a [SARIF 2.1.0](https://sarifweb.azurewebsites.net/) log of - only the files that need action — modified, added, deleted, error — each with - a level (`error` for a path that could not be compared, `warning` otherwise). - Upload it to GitHub code scanning or Azure DevOps to see the changes annotated - inline on the pull request. Identical and noise-only files are not findings - and are left out. +- `--json` writes the entire scan under a versioned `schema` key: every file's verdict, its hunks, its renames and AUTOSAR extras, the run summary, the consistency advisories, and the same `exit_code` the process itself returns — so the file on disk and `$?` can never disagree with each other. Pin the `schema` value and an internal refactor won't move the shape out from under you later. +- `--sarif` writes a [SARIF 2.1.0](https://sarifweb.azurewebsites.net/) log covering only the files that need action — modified, added, deleted, error — each carrying a level (`error` for a path that couldn't be compared at all, `warning` for everything else). Upload it to GitHub code scanning or Azure DevOps and the changes show up annotated inline on the pull request. Identical and noise-only files aren't findings, so they're left out entirely. -A write that fails is loud: like a missing HTML report, it exits `2` — a -pipeline that asked for the file must not proceed as if it got one. +A write that fails is loud about it: same as a missing HTML report, it exits `2` — a pipeline that specifically asked for one of these files must never proceed as though it actually got one. ## Single-file build @@ -397,8 +260,7 @@ pipeline that asked for the file must not proceed as if it got one. .\build.ps1 -PyzOnly # zipapp only (building it needs no PyInstaller / PySide6) ``` -`dist\compare-tool.exe` is **one binary carrying both front ends**, and it wears -the tool's own icon: +`dist\compare-tool.exe` is one binary carrying both front ends, and it wears the tool's own icon rather than a generic one: | Invocation | What happens | |---|---| @@ -406,13 +268,9 @@ the tool's own icon: | `compare-tool.exe --qt ` | side-by-side viewer, folders already loaded | | double-click (no arguments) | side-by-side viewer, waiting for the two folders | -Built as a **console** application so terminal runs keep their exit code -(`1` = real changes, `2` = compare incomplete) for the CI gate. The viewer hides -the console window at runtime — you'll see a brief flash on double-click. A -crash un-hides the console so the error is visible. +It's built as a **console** application on purpose, so a terminal run keeps its exit code (`1` = real changes, `2` = compare incomplete) intact for the CI gate. The viewer hides the console window at runtime — you'll see a brief flash on double-click and then it's gone — but a crash un-hides it again so the error is actually visible instead of disappearing with the window. -- **`.pyz` (zipapp, stdlib)**: `python compare_tool.pyz [flags]`. Prefer it when Python is available — tiny, no build dependencies, not flagged by antivirus. The CLI works anywhere; the viewer additionally needs PySide6 on that machine (without it, the tool says so instead of opening). -- **`.exe` (PyInstaller onefile, ~47 MB)**: no Python needed on the target. Building needs `pyinstaller` and `PySide6` on the dev machine (`build.ps1` installs them), and the binary only runs on the OS it was built on. PyInstaller executables are sometimes blocked by antivirus or AppLocker — fall back to the `.pyz` there. +- **`.pyz` (zipapp, stdlib)**: `python compare_tool.pyz [flags]`. Prefer this one when Python is available at all — it's tiny, needs no build dependencies, and doesn't tend to get flagged by antivirus the way a PyInstaller binary sometimes does. The CLI works anywhere; the viewer additionally needs PySide6 installed on that machine, and without it the tool just says so rather than failing to open. +- **`.exe` (PyInstaller onefile, ~47 MB)**: no Python needed on the target machine at all. Building it needs `pyinstaller` and `PySide6` on the dev machine (`build.ps1` installs both for you), and the resulting binary only runs on the OS it was built on. PyInstaller executables occasionally get blocked by antivirus or AppLocker — if that happens, fall back to the `.pyz`. -Every CLI flag behaves identically in the packaged builds. `build/` and `dist/` -are already in `.gitignore`. +Every CLI flag behaves identically across every packaged build. `build/` and `dist/` are already in `.gitignore`, so a local build never shows up as something to commit. diff --git a/docs/vi/README.md b/docs/vi/README.md index 4199fc9..55c49af 100644 --- a/docs/vi/README.md +++ b/docs/vi/README.md @@ -3,89 +3,95 @@ > Bản tiếng Việt của [README](../../README.md). Bản tiếng Anh là bản chuẩn — khi > hai bên lệch nhau, tin bản tiếng Anh. -So sánh hai thư mục code-generation AUTOSAR (MATLAB/Simulink Embedded Coder) và -chỉ hiện **những thay đổi thật sự đáng quan tâm**. - -Mỗi lần regenerate một model, Simulink ghi lại timestamp, UUID, comment banner và -tên biến auto-generated dù hành vi không đổi. Tool phân loại từng hunk là *real* -hay *ignorable*, rồi cho hai cách review kết quả — một **HTML report** -self-contained và một **viewer side-by-side** trên desktop, cùng chạy trên một -compare core, nên verdict không phụ thuộc vào việc bạn nhìn bằng đường nào. +Regenerate xong một model Simulink, diff với bản hôm qua có khi lên tới hàng +nghìn dòng — banner timestamp mới, UUID mới toanh trên từng phần tử ARXML, tên +biến bị codegen đánh số lại từ đầu. Đâu đó trong đống đó có thể có một thay đổi +hành vi thật, có thể không, và cách duy nhất để biết là cuộn qua từng dòng — thế +là một buổi review 5 phút thành cả buổi chiều. + +Tool này đọc cả hai thư mục, tách ra dòng nào chỉ là do generator ghi lại +(timestamp, UUID, tên biến tự sinh) và dòng nào là thay đổi thật, rồi chỉ hiện +loại thứ hai. Trỏ vào một bản codegen cũ và một bản mới, nó liệt kê ra cái gì +thực sự đã đổi — ở mức code, và ở mức AUTOSAR. + +Có hai cách để xem kết quả đó: một **viewer desktop** để review bằng tay, và +một **CLI** ghi ra **HTML report** self-contained kèm exit code để pipeline +gate theo. Cả hai chạy trên đúng một compare engine, nên không có chuyện mở +bằng cách này ra kết quả khác, mở bằng cách kia ra kết quả khác. | | Dùng cho | Chạy khi | |---|---|---| -| **Viewer** | review tương tác: cây thư mục, diff hai pane, minimap, note review | không truyền thư mục trên command line (hoặc double-click `.exe`) | -| **CLI** | pipeline và script — ghi report, exit code gate build | truyền đủ hai thư mục trên command line | +| **Viewer** | Review bằng tay — cây thư mục, diff hai pane, minimap, note review | Không truyền thư mục trên command line (hoặc double-click `.exe`) | +| **CLI** | Pipeline và script — ghi report, exit code gate build | Truyền đủ hai thư mục trên command line | + +Phần lõi — scan, luật lọc noise, diff, HTML report — **chỉ dùng standard +library của Python**. Không cần `pip install` gì, không server, không bao giờ +gọi mạng. Viewer là phần duy nhất cần PySide6, và cũng chỉ import đúng lúc nó +mở lên. -**Compare core không phụ thuộc thư viện ngoài** — CLI và HTML report chỉ dùng -standard library của Python 3.8+: không cần `pip install`, không server, không cần -internet. Viewer cần thêm PySide6, và chỉ import khi viewer mở lên. +📖 **[Hướng dẫn sử dụng](usage.md)** — đầy đủ flag, phím tắt của viewer, luật +noise chính xác, cách report dựng trang, CI và đóng gói. -📖 **[Hướng dẫn sử dụng](usage.md)** — đầy đủ flag, phím tắt của viewer, luật noise -chính xác, cách report dựng trang, CI và đóng gói. 🏗 **[Kiến trúc](architecture.md)** — các mảnh ghép với nhau ra sao và tại sao. -## Cài đặt +## Lấy tool Chạy thẳng từ clone, không cần cài gì: ```bash git clone https://github.com/longvo92/codegen-compare-tool.git -``` - -```bash python -m compare_tool --help ``` -Hoặc cài thành lệnh (`compare-tool`): +Hoặc cài thành một lệnh riêng, `compare-tool`: ```bash pip install git+https://github.com/longvo92/codegen-compare-tool.git ``` -Máy không cài được gì thì [build một file](usage.md#build-một-file). +Máy bị khoá không cài được gì hết? Có sẵn [bản build một file](usage.md#build-một-file). -## Bắt đầu nhanh +## Lần compare đầu tiên ```bash python -m compare_tool --report out.html ``` -Ghi ra một HTML report self-contained, mở được bằng browser bất kỳ và gửi mail -như một file đơn. - -Mỗi phía có thể là một `.zip` — ví dụ artifact build tải từ Azure DevOps. Nó -được giải nén read-only vào thư mục tạm, so sánh như một thư mục rồi dọn sạch -sau đó; header report ghi tên zip thay cho đường dẫn tạm: +Lệnh này ghi ra một file HTML self-contained duy nhất — mở bằng browser bất kỳ, +gửi mail thoải mái, không cần gì thêm. Mỗi phía cũng có thể là một `.zip` (ví +dụ artifact build tải thẳng từ Azure DevOps); nó được giải nén read-only vào +thư mục tạm, so sánh như một thư mục bình thường, rồi dọn sạch sau đó. Report +vẫn ghi tên file zip, không phải đường dẫn tạm: ```bash python -m compare_tool baseline.zip current.zip --report out.html ``` -Bỏ hai thư mục ra thì viewer mở lên — kéo thả hai thư mục (hoặc hai `.zip`) vào đó: +Bỏ hai thư mục ra thì viewer mở lên — kéo thả hai thư mục (hoặc hai `.zip`) +vào đó là xong: ```bash python -m compare_tool ``` -Exit code — contract với pipeline của bạn: +Nếu bạn đang gắn vào build, exit code chính là contract: | Code | Ý nghĩa | |---|---| | `0` | Không có thay đổi thật | -| `1` | Có thay đổi thật (CI gate) | +| `1` | Có thay đổi thật — CI gate thường dùng cái này | | `2` | **Compare INCOMPLETE** — có path không list / đọc / so sánh được, hoặc không ghi được report | -Exit `2` luôn hiện rõ: `!!` ngoài terminal, banner đỏ trong report. `--exit-zero` -không dập được nó. Một lần chạy không để lại bản ghi thì không bao giờ được trông -giống một lần chạy sạch. +Exit `2` được làm cho khó bỏ sót: `!!` ngoài terminal, banner đỏ trong report, +và `--exit-zero` cũng không tắt được nó. Một lần chạy không so sánh được đầy đủ +thì không được phép trông giống một lần chạy sạch. -## Cái gì bị lọc +## Cái gì thực sự bị lọc -| Kind | Rule | File | +| Kind | Bắt cái gì | File | |---|---|---| | `comment` | Comment C/C++/A2L (`//`, `/* */`), comment XML (``), comment dòng `#` (Python, YAML) | .c .h .cpp .hpp .arxml .a2l .py .yaml .yml | -| `rename` | Đổi tên 1-1 nhất quán các tên do generator sở hữu. Cái gì mapping không giải thích trọn vẹn thì vẫn là thay đổi thật | .c .h | +| `rename` | Đổi tên 1-1 nhất quán các tên do generator sở hữu — cái gì mapping không giải thích trọn vẹn thì vẫn là thay đổi thật | .c .h | | `uuid` | Attribute `UUID="..."` | .arxml .xml | | `timestamp` | Block ``, `` | .arxml .xml | | `sw-version` | Version stamp ``, tăng mỗi lần regenerate | .arxml .xml | @@ -93,41 +99,69 @@ giống một lần chạy sạch. | `whitespace` | Thụt đầu dòng, khoảng trắng cuối dòng, dòng trống | tất cả | | `line-endings` | CRLF vs LF, BOM | tất cả | -**Cái gì không chứng minh được là noise thì là thay đổi thật.** -`SIG_TORQUE_MIN` → `SIG_TORQUE_MAX` là thay đổi thật; `rtb_AND_c4nxjoom3d` → -`rtb_AND_j2kqp1wxab` là rename. Block bị di chuyển nguyên vẹn được gán nhãn -`moved`, tô xanh dương, và vẫn tính là Modified. File chỉ khác comment là một hạng -mục riêng, tách khỏi Unimportant. +Luật mà tool không bao giờ nới lỏng: **cái gì không chứng minh được là noise +thì là thay đổi thật.** `SIG_TORQUE_MIN` đổi thành `SIG_TORQUE_MAX` là thay đổi +thật; `rtb_AND_c4nxjoom3d` đổi thành `rtb_AND_j2kqp1wxab` là generator tự đặt +tên lại. Một block bị dịch chuyển nguyên vẹn được gán nhãn `moved` riêng, tô +xanh dương, và vẫn tính vào Modified — nó không bị giấu đi, chỉ được nói rõ đó +là di chuyển chứ không phải sửa nội dung. File chỉ khác nhau ở comment được xếp +vào hạng mục riêng, không gộp chung với Unimportant: comment bị viết lại thì +bạn đọc lướt qua được, còn một biến bị đổi tên thì phải kiểm. -→ [luật chính xác](usage.md#cái-gì-bị-tính-là-noise) +→ [luật chính xác, từng cái một](usage.md#cái-gì-bị-tính-là-noise) -## Summary ngữ nghĩa AUTOSAR +## Summary ở mức AUTOSAR, không chỉ là diff text -Cả hai mặt đều mở ra bằng **cái gì đã đổi ở mức AUTOSAR**, không chỉ ở mức text: -port interface, SWC, port, runnable, event (kể cả chu kỳ TIMING-EVENT đi từ -`0.01s → 0.02s`), lời gọi `Rte_*` và đối tượng A2L `CHARACTERISTIC`/`MEASUREMENT`. -File được nhóm theo model Simulink. +Cả viewer lẫn report đều mở ra bằng cái gì đã đổi **ở mức AUTOSAR** trước khi +bạn nhìn vào một dòng C hay XML nào: port interface, SWC, port, runnable, event +(chu kỳ `TIMING-EVENT` đi từ `0.01s` sang `0.02s` hiện ra đúng như vậy), lời +gọi `Rte_*`, và đối tượng A2L `CHARACTERISTIC` / `MEASUREMENT` — nhóm theo đúng +model Simulink mà chúng thuộc về. → [trích ra những gì, hiển thị ra sao](usage.md#summary-ngữ-nghĩa-autosar) -## Viewer side-by-side +## Bắt được một lần regenerate dở dang -```bash -pip install "codegen-compare-tool[viewer]" -``` +ARXML của một model là file khai báo interface của model đó — có những port +nào, runnable nào, event nào. A2L là file khai báo các biến calibration và +measurement. Code C sinh ra phải khớp với cả hai: thêm một port trong ARXML thì +trong code phải có thêm một lời gọi `Rte_*` tương ứng, thêm một characteristic +trong A2L thì trong code phải có thêm biến tương ứng. + +Nên khi ARXML hoặc A2L có thêm/bớt một port, runnable hay biến calibration mà +file C của model đó không đổi một byte nào, tool sẽ cảnh báo — đó thường là +dấu hiệu lần regenerate chạy chưa xong. Một diff xem từng file riêng lẻ không +phát hiện được chuyện này, vì bản thân mỗi file đều bình thường; chỗ sai nằm ở +việc hai file không khớp nhau. + +Tool cũng đối chiếu giữa các model với nhau: nếu code của model A có thêm lời +gọi `Rte_*` mới mà code của model B không đổi gì cả, nhiều khả năng bạn chỉ +regenerate mình model A chứ chưa regenerate lại toàn bộ architecture. Lời gọi +`Rte_*` mới đó cần RTE layer sinh lại thì mới build và tích hợp được. + +Cả hai đều chỉ là cảnh báo để bạn đi kiểm tra — chúng không đổi verdict của +file nào, cũng không đổi exit code. + +→ [consistency check hoạt động thế nào](usage.md#consistency-check) + +## Viewer desktop ```bash +pip install "codegen-compare-tool[viewer]" python -m compare_tool ``` ![Viewer side-by-side](../../resources/pic/main_page.png) -Cây thư mục, diff hai pane có minimap và tô cú pháp, `F7`/`F8` đi hết mọi change -của cả lần compare, `Ctrl+F` xuyên file, note review theo từng change, một -**caption tên hàm** hiện hàm C/C++, class/method Python, SHORT-NAME AUTOSAR hay -block A2L bao quanh và bám -theo lúc cuộn, và một commit picker để so **một** thư mục trong git checkout với -chính lịch sử của nó. `Help` → `User guide` (`F1`) nằm sẵn trong app, chạy offline. +Cây thư mục bên trái, diff hai pane có minimap và tô cú pháp bên phải. +`F7`/`F8` đi hết mọi change trong cả lần compare, `Ctrl+F` tìm xuyên mọi file, +và bạn để lại note review trên từng change riêng lẻ được. Phía trên diff có +một dòng hiện tên hàm C/C++ (hoặc class/method Python, SHORT-NAME AUTOSAR, +block A2L) chứa đoạn code bạn đang xem, cập nhật liên tục khi cuộn — file +codegen dài hàng nghìn dòng thì cái này giúp biết mình đang ở hàm nào. Có cả +commit picker, để so một thư mục trong git checkout với chính lịch sử của nó +thay vì phải có sẵn hai thư mục. Bấm `F1` để mở user guide có sẵn trong app, +chạy offline. → [đọc một lần scan, review mode, mọi phím tắt](usage.md#viewer-side-by-side) @@ -135,42 +169,46 @@ chính lịch sử của nó. `Help` → `User guide` (`F1`) nằm sẵn trong a ![Report viewer](../../resources/pic/report_page.png) -Mỗi lần compare một file self-contained: badge bật/tắt, cây thư mục, ô lọc, diff -xếp gọn được. Nó hiện **ba dòng trên và dưới mỗi change thật**, không phải cả file -— noise ở chỗ khác không chiếm chỗ nào cho tới khi bạn bấm hiện. Mỗi change được -chú thích bằng hàm chứa nó, và một file Modified liệt kê những hàm mà change của -nó đụng tới. Cả hai palette sáng/tối đều nhúng sẵn, nên nút đổi màu không tải gì -trên máy không có internet. +Một file cho mỗi lần compare, và nó self-contained thật sự — badge bật/tắt, +cây thư mục, ô lọc, diff xếp gọn được, tất cả trong một `.html` duy nhất đính +kèm mail thoải mái. Nó hiện ba dòng ngữ cảnh trên và dưới mỗi change thật thay +vì cả file, nên noise xung quanh không chiếm chỗ màn hình nào cho tới khi bạn +chủ động bấm hiện. Mỗi change được chú thích bằng hàm nó nằm trong, và một file +Modified liệt kê mọi hàm mà change của nó đụng tới. Cả hai theme sáng/tối đều +nhúng sẵn, nên đổi theme không tải gì cả — render y hệt trên máy không có +internet như trên máy bạn. → [bố cục, badge, cái gì bị gộp và tại sao](usage.md#html-report) -## Tích hợp CI +## Gắn vào CI ```bash python -m compare_tool old_dir new_dir --exit-zero --exclude compare_report.html ``` -`--exit-zero` giữ build xanh khi code chỉ bị regenerate; `--exclude` không cho -report của lần chạy trước bị tính thành diff. Publish `compare_report.html` như -một build artifact. Xem [azure-pipelines.yml](../../azure-pipelines.yml) để có ví -dụ chạy được. +`--exit-zero` giữ build xanh ngay cả khi việc duy nhất xảy ra là regenerate; +`--exclude` không cho report của lần chạy trước bị tính vào diff. Publish +`compare_report.html` như một build artifact là có luôn bản ghi có thể bấm vào +cho từng lần chạy. [azure-pipelines.yml](../../azure-pipelines.yml) có ví dụ +chạy được từ đầu đến cuối nếu bạn muốn xem. -→ [flag và đóng gói cho máy bị khoá chặt](usage.md#tích-hợp-ci) +→ [flag, exit code, và đóng gói cho máy bị khoá chặt](usage.md#tích-hợp-ci) -## Phát triển +## Đóng góp ```bash python -m unittest discover -s tests ``` -CI chạy bộ test trên Linux và Windows với Python 3.8 và 3.11, cộng thêm một lần -scan headless trên cây fixture để kiểm cả report lẫn exit code. +CI chạy bộ test đó trên Linux và Windows với Python 3.8 và 3.11, cộng thêm một +lần scan headless trên cây fixture kiểm cả report lẫn exit code. -Issue và pull request đều được hoan nghênh. Xin giữ **compare core chỉ dùng -stdlib** — nó phải chạy được trên build server bị khoá chặt, nên PySide6 nằm gọn -trong `compare_tool/qtviewer/` và chỉ được import khi viewer mở — và thêm test -dưới `tests/` cho mọi rule mới. [architecture.md](architecture.md) có bản đồ -module và bảng *sửa cái gì thì đụng vào đâu*. +Issue và pull request đều được hoan nghênh. Luật quan trọng nhất: **compare +core chỉ dùng stdlib** — nó phải chạy được trên build server bị khoá chặt, nên +PySide6 nằm gọn trong `compare_tool/qtviewer/` và chỉ import đúng lúc viewer +mở lên. Thêm luật lọc noise mới thì nhớ thêm test dưới `tests/`. +[architecture.md](architecture.md) có bản đồ module và bảng *sửa cái gì thì +đụng vào đâu*. ## Tác giả diff --git a/docs/vi/usage.md b/docs/vi/usage.md index d603fc2..b53275f 100644 --- a/docs/vi/usage.md +++ b/docs/vi/usage.md @@ -3,9 +3,10 @@ > Bản tiếng Việt của [docs/usage.md](../usage.md). Bản tiếng Anh là bản chuẩn — > khi hai bên lệch nhau, tin bản tiếng Anh. -Toàn bộ phần chi tiết mà [README](README.md) trỏ ra: đầy đủ flag, phím tắt của -viewer, luật noise chính xác, report hiện cái gì và tại sao, CI và đóng gói. Còn -*code được ghép lại ra sao* thì xem [architecture.md](architecture.md). +Đây là bản đầy đủ của mọi thứ mà [README](README.md) trỏ ra: từng flag, từng +phím tắt của viewer, luật noise chính xác, tại sao report hiện đúng những gì nó +hiện, và cách gắn cả cục này vào CI. Còn nếu bạn muốn biết *code được tổ chức ra +sao* thay vì chạy nó thế nào, phần đó nằm ở [architecture.md](architecture.md). - [Command line](#command-line) - [Viewer side-by-side](#viewer-side-by-side) @@ -13,6 +14,7 @@ viewer, luật noise chính xác, report hiện cái gì và tại sao, CI và - [Phát hiện block bị di chuyển](#phát-hiện-block-bị-di-chuyển) - [Summary ngữ nghĩa AUTOSAR](#summary-ngữ-nghĩa-autosar) - [Nhóm theo model / SWC](#nhóm-theo-model--swc) +- [Consistency check](#consistency-check) - [HTML report](#html-report) - [Tích hợp CI](#tích-hợp-ci) - [Build một file](#build-một-file) @@ -23,12 +25,13 @@ viewer, luật noise chính xác, report hiện cái gì và tại sao, CI và python -m compare_tool [--report out.html] ``` -Mỗi vị trí có thể là một file `.zip` (ví dụ artifact build tải từ Azure DevOps). -Nó được giải nén read-only vào thư mục tạm, so sánh như một thư mục, rồi xoá khi -thoát; nếu archive chỉ có đúng một thư mục bọc ngoài thì tự đi vào trong, và -header report ghi tên zip thay cho đường dẫn tạm (`--baseline-name` / -`--current-name` vẫn thắng). Zip không đọc được thì dừng lớn tiếng — không bao -giờ rơi xuống một thư mục rỗng. +Mỗi vị trí có thể là một file `.zip` thay vì thư mục — ví dụ artifact build tải +từ Azure DevOps. Tool giải nén nó read-only vào một thư mục tạm, so sánh như một +thư mục bình thường, rồi xoá thư mục tạm đó khi thoát. Nếu archive chỉ có đúng +một thư mục bọc ngoài, tool tự đi thẳng vào trong. Header report ghi tên zip +thay cho đường dẫn tạm (`--baseline-name` / `--current-name` vẫn ghi đè được +nếu bạn muốn tên khác). Zip không đọc được thì dừng chạy lớn tiếng — không bao +giờ âm thầm rơi xuống so sánh một thư mục rỗng. | Flag | Ý nghĩa | |---|---| @@ -42,18 +45,21 @@ giờ rơi xuống một thư mục rỗng. | `--theme dark\|light` | Bảng màu lúc mở của report và viewer (mặc định `dark`). Report mang sẵn **cả hai** và có nút đổi riêng, nên cờ này chỉ quyết định người đọc thấy màu nào trước | | `--qt`, `--viewer` | Mở viewer trên hai thư mục truyền ở command line, thay vì so sánh trong terminal. Cần extra `viewer` | -Bỏ `old_dir`/`new_dir` thì viewer mở. `--gui` (panel tkinter) đã bị bỏ ở 1.1.0. +Bỏ hẳn `old_dir`/`new_dir` thì viewer mở lên thay thế. (Panel tkinter cũ, +`--gui`, đã biến mất từ 1.1.0 — nếu bạn đang đọc tài liệu cũ nào còn nhắc tới +nó thì đó là lỗi thời.) -Đường dẫn report không ghi được (thiếu thư mục, file đang mở trong browser, -read-only) là exit `2` kèm một dòng lý do — không bao giờ là traceback, và không -bao giờ là exit `1`, vì pipeline đọc `1` thành "có thay đổi thật" bình thường. -Những gì lần scan tìm được vẫn được in ra. +Đường dẫn report không ghi được — thiếu thư mục, file đang mở trong browser, ổ +đĩa read-only — là exit `2` kèm một dòng lý do. Không bao giờ là traceback, và +cũng không bao giờ là exit `1`: pipeline hiểu `1` là "có thay đổi thật", trong +khi thực tế ở đây là không có report nào được ghi ra. Những gì lần scan đó tìm +được vẫn in ra trước khi tiến trình thoát. ## Viewer side-by-side -App desktop (PySide6): cây thư mục, diff hai pane có minimap và tô màu syntax, -note review theo từng change, và một commit picker khi thư mục nằm trong git -checkout. +Viewer là một app desktop chạy PySide6: cây thư mục, diff hai pane có minimap +và tô màu syntax, note review để lại theo từng change, và một commit picker +cho lúc thư mục bạn đang xem tình cờ là một git checkout. ```bash pip install "codegen-compare-tool[viewer]" # hoặc: pip install PySide6 @@ -67,31 +73,32 @@ python -m compare_tool # rồi kéo thả python -m compare_tool --qt # hoặc mở sẵn ``` -Hai đường vào: `Open folders…` cho hai thư mục tự chọn, và `Git compare…` cho -**một** thư mục nằm trong git checkout — nó liệt kê các commit từng đụng tới thư -mục đó, lấy commit bạn chọn ra một thư mục tạm (read-only — working copy không bị -đụng tới), rồi so sánh như bình thường. +Có hai đường vào một lần compare. `Open folders…` dành cho hai thư mục bạn tự +chọn. `Git compare…` dành cho lúc bạn chỉ có **một** thư mục và nó là git +checkout — nó liệt kê các commit từng đụng tới thư mục đó, checkout commit bạn +chọn ra một thư mục tạm (read-only — working copy của bạn không bị đụng tới), +rồi so sánh như bình thường. -Mỗi phía cũng có thể là một `.zip` thay cho thư mục: kéo thả vào cửa sổ, hoặc -dùng nút `Zip…` trong `Open folders…`. Nó được giải nén vào thư mục tạm và pane -được gán nhãn theo tên zip, không phải đường dẫn tạm. +Mỗi phía cũng có thể là một `.zip` thay cho thư mục: kéo thả thẳng vào cửa sổ, +hoặc dùng nút `Zip…` trong `Open folders…`. Nó được giải nén vào thư mục tạm và +pane được gán nhãn theo tên zip, không phải đường dẫn tạm. -Khi một file đang mở, một **caption cạnh tên file** cho biết bạn đang ở hàm nào — -hàm C/C++, class/method Python, SHORT-NAME của AUTOSAR hay block A2L bao quanh — -và bám theo lúc cuộn, nên -luôn biết mình đang ở đâu trong một file sinh dài. Với **file C**, khi dòng -signature của hàm cuộn khuất lên trên, nó còn được **ghim lên đỉnh mỗi pane** -(như sticky scroll của VS Code) cho tới khi bạn rời khỏi hàm. +Khi một file đang mở, một caption nhỏ cạnh tên file bám theo bất cứ chỗ nào bạn +đang nhìn — hàm C/C++ bao quanh, class/method Python, SHORT-NAME AUTOSAR, block +A2L — và cập nhật theo lúc cuộn, nên bạn không bao giờ lạc trong một file sinh +dài. Riêng với file C, khi dòng signature của hàm cuộn khuất lên trên đỉnh +pane, nó vẫn được ghim ở đó (giống sticky scroll của VS Code) cho tới khi bạn +thật sự rời khỏi hàm. ### Đọc một lần scan -- Scan **mở sẵn ở change đầu tiên** — pane không bao giờ trống trong khi cây bên cạnh đầy kết quả. -- `F8` / `F7` nhảy qua các change trong file đang mở rồi **đi tiếp sang file có change kế (trước)**, hết thì vòng lại. `Ctrl+Home` / `Ctrl+End` giữ nguyên trong file. File comment / noise vẫn nằm trong lộ trình chừng nào rule của nó còn tick, nhưng dừng ở đó thì không ký duyệt được gì — chỉ change thật và block moved mới vào bản ghi review. -- `Ctrl+F` **tìm text trong file đang mở** (cả hai bên, `F3` / `Shift+F3` để nhảy, `Esc` để đóng). Query còn nguyên khi chuyển sang file khác, nên truy một identifier xuyên suốt lần compare được. -- `Hide identical` chỉ để lại các file có khác biệt trên cây. Đây là view: verdict, số đếm và report export ra đều không đổi. -- Bỏ tick `Comment` / `Unimportant` sẽ **làm mờ các dòng đó** chứ không xoá đi: chúng ở nguyên chỗ cũ, giữ số dòng, mất màu đỏ/xanh, và biến khỏi minimap lẫn `F7`/`F8`. Để nguyên tick (mặc định) thì chúng giữ màu và là điểm dừng `F7`/`F8` như mọi change khác. -- Change đang đứng được đánh dấu bằng **mũi tên nhỏ trong cột số dòng**, ở cả hai pane — nên `F7`/`F8` vẫn thấy rõ là có nhảy kể cả khi file ngắn, không có gì để cuộn. -- `☀ Light` / `☾ Dark` trên toolbar đổi bảng màu; `--theme` chọn màu lúc mở. C, C++, ARXML/XML, A2L, Python, JSON và YAML đều được tô cú pháp ở cả hai theme. +- Scan **mở sẵn ở change đầu tiên** — bạn không bao giờ rơi vào một pane trống trong khi cây bên cạnh đầy kết quả. +- `F8` / `F7` nhảy qua các change trong file đang mở, rồi đi tiếp sang file có change kế tiếp (hoặc trước đó) một khi hết, vòng lại khi tới cuối. `Ctrl+Home` / `Ctrl+End` giữ nguyên trong file hiện tại. File comment / noise vẫn nằm trong lộ trình đó chừng nào rule của nó còn tick, nhưng dừng ở một file như vậy không ký duyệt được gì — chỉ change thật và block moved mới vào bản ghi review. +- `Ctrl+F` tìm text trong file đang mở, cả hai bên, với `F3` / `Shift+F3` để nhảy qua các kết quả và `Esc` để đóng. Query còn nguyên khi bạn chuyển sang file khác, nên truy một identifier xuyên suốt cả lần compare được. +- `Hide identical` thu cây lại còn đúng các file thật sự khác nhau. Đây thuần là một view — verdict, số đếm và report export ra đều không đổi vì nó. +- Bỏ tick `Comment` / `Unimportant` làm mờ các dòng đó chứ không xoá đi: chúng ở nguyên chỗ cũ, giữ số dòng, chỉ mất màu đỏ/xanh và biến khỏi minimap lẫn lộ trình `F7`/`F8`. Để nguyên tick — mặc định là vậy — chúng giữ màu và là điểm dừng như mọi change khác. +- Chỗ bạn đang đứng được đánh dấu bằng một mũi tên nhỏ trong cột số dòng, ở cả hai pane, nên `F7`/`F8` vẫn thấy rõ là có nhảy kể cả trong một file ngắn tới mức không có gì để cuộn. +- `☀ Light` / `☾ Dark` trên toolbar đổi bảng màu ngay lập tức; `--theme` chỉ chọn màu lúc mở. C, C++, ARXML/XML, A2L, Python, JSON và YAML đều được tô cú pháp ở cả hai theme. | Marker | Verdict | Nghĩa | |---|---|---| @@ -105,39 +112,43 @@ signature của hàm cuộn khuất lên trên, nó còn được **ghim lên đ ### File bị đổi tên hoặc chuyển chỗ -Đổi tên model, chuyển `Foo.c` từ `swc_a/` sang `swc_b/`, hay tái cấu trúc thư mục -output — file sẽ hiện ra thành một Added cộng một Deleted. Tool ghép hai cái đó -lại và báo như một lần di chuyển: +Đổi tên model, chuyển `Foo.c` từ `swc_a/` sang `swc_b/`, hay tái cấu trúc thư +mục output theo cách nào đó — nếu để riêng, nó chỉ trông như một file Added và +một file khác Deleted. Tool ghép hai cái đó lại và báo như một lần di chuyển +duy nhất: > `swc_b/Sub.c` **Added** *(moved from swc_a/Sub.c — and changed, 89% alike)* -Entry Added khi đó hiện **diff so với file nó đi ra** thay vì toàn bộ nội dung, -còn entry Deleted trỏ sang đó chứ không in lại đúng ngần ấy dòng lần nữa. +Entry Added khi đó hiện diff so với file nó đi ra, thay vì dump toàn bộ nội +dung; entry Deleted chỉ trỏ sang đó chứ không in lại đúng ngần ấy dòng lần nữa. -Trong viewer, hai dòng đó hiện `Added (moved)` / `Deleted (moved)` ở cột Status, -đường dẫn và độ giống nằm ở tooltip khi rê chuột. Dòng không bị move thì nhãn y -như cũ. +Trong viewer, hai dòng đó hiện `Added (moved)` / `Deleted (moved)` ở cột +Status, đường dẫn gốc và độ giống nằm sẵn ở tooltip khi rê chuột. File nào thật +sự không di chuyển thì vẫn giữ nhãn như trước giờ. -Để ghép được, hai file phải cùng phần mở rộng, phải cùng chọn nhau là khớp nhất, -và phải hơn hẳn cái đứng thứ hai — file codegen giống nhau đủ để một tỉ số sát sao -không phải là câu trả lời. File không ghép được thì vẫn báo Added / Deleted như cũ. +Để ghép được, hai file phải cùng phần mở rộng, phải cùng chọn nhau là khớp +nhất, và độ giống phải cao hơn hẳn ứng viên xếp thứ hai. Lý do phải chặt: các +file codegen vốn giống nhau sẵn, nên nếu hai ứng viên có điểm xấp xỉ nhau thì +kết quả ghép không đáng tin. File nào không ghép được thì báo Added / Deleted +như bình thường. -Hai file giữ nguyên verdict và vẫn được đếm, và **exit code không đổi**: file -chuyển chỗ vẫn là một thay đổi của cây, nên pipeline đang gate theo Added/Deleted -vẫn chạy đúng. +Cả hai file vẫn giữ verdict riêng và vị trí riêng trong số đếm, và exit code +không đổi vì một lần move — file di chuyển vẫn là một thay đổi của cây, nên +pipeline đang gate theo Added/Deleted vẫn chạy đúng như trước. ### Review mode -`Review mode` bật hộp note và cột `Review` trên cây — xanh khi mọi change trong -dòng đã ký duyệt, hổ phách khi mới một phần, xám khi chưa cái nào. Ký duyệt một -change (`Ctrl+R`) hoặc cả file (`Ctrl+Shift+R`); note đi theo *nội dung* của -change chứ không theo số dòng, nên sống sót qua lần scan sau. Lưu vào -`codegen-review.json` cạnh thư mục CURRENT. +Bật `Review mode` thêm hộp note và cột `Review` trên cây — xanh khi mọi change +trong dòng đã ký duyệt, hổ phách khi mới một phần, xám khi chưa cái nào. Bạn ký +duyệt một change bằng `Ctrl+R`, hoặc cả file cùng lúc bằng `Ctrl+Shift+R`. Note +đi theo *nội dung* của change chứ không theo số dòng, nên sống sót qua lần +rescan sau thay vì trôi sang nhầm dòng. Mọi thứ lưu vào `codegen-review.json` +cạnh thư mục CURRENT. -`Export report…` (`Ctrl+E`) ghi đúng cái HTML report self-contained mà CLI ghi, -kèm note review. Nó **luôn dựng từ toàn bộ lần scan**, không bao giờ từ cái đang -hiện trên màn hình — category bạn thu gọn trên cây vẫn nằm trong file với verdict -thật của nó. +`Export report…` (`Ctrl+E`) ghi đúng cái HTML report self-contained mà CLI +ghi, kèm note review của bạn gộp vào. Nó luôn dựng từ toàn bộ lần scan, không +bao giờ từ cái đang hiện trên màn hình lúc đó — nên category bạn từng thu gọn +trên cây vẫn nằm trong file export với verdict thật của nó. | Phím tắt | Việc | |---|---| @@ -157,61 +168,91 @@ thật của nó. |---|---|---| | `comment` | Comment C/C++/A2L (`//`, `/* */`), comment XML (``), comment dòng `#` (Python, YAML). Docstring Python và JSON **không** được gộp — chuỗi triple-quote là code, còn JSON không có comment | .c .h .cpp .hpp .arxml .a2l .py .yaml .yml | | `rename` | Đổi tên biến 1-1 nhất quán (tên MATLAB auto-generated). Cái gì mapping không giải thích trọn vẹn thì vẫn là thay đổi thật | .c .h | +| `reorder` | Các câu lệnh độc lập được sinh ra theo thứ tự khác (Embedded Coder sắp lịch lại). Chỉ gộp khi block toàn phép gán scalar tuần tự **và** thứ tự mới giữ nguyên mọi phụ thuộc dữ liệu — ngoài ra vẫn là thay đổi thật | .c .h | | `uuid` | Attribute `UUID="..."` | .arxml .xml | | `timestamp` | Block ``, `` | .arxml .xml | | `sw-version` | Version stamp `` (tăng mỗi lần regenerate). Regex có anchor, nên `` và các thẻ tương tự không bị đụng | .arxml .xml | -| `description` | ``, ``, `` — phần văn xuôi mà một Identifiable mang theo (schema 4.2 và 4.4 giống nhau). `` và `` **không** nằm trong diện này: cái đầu mang ngữ nghĩa, cái sau có thể chứa payload của tool | .arxml .xml | +| `description` | ``, ``, `` — các thẻ chứa mô tả bằng chữ, không ảnh hưởng hành vi (áp dụng cho cả schema 4.2 và 4.4). `` và `` **không** được lọc: `` ảnh hưởng cách phần tử được hiểu, còn `` có thể chứa dữ liệu do tool khác ghi vào | .arxml .xml | | `whitespace` | Thụt đầu dòng, khoảng trắng cuối dòng, dòng trống | tất cả | | `line-endings` | CRLF vs LF, BOM | tất cả | ### Rename -Tên auto-generated đổi lung tung được nhận là `rename`. Hai identifier chỉ được -coi là cùng một tên khi code generator sở hữu cả hai — một prefix do generator -sinh (`rtb_`, `rtu_`, `rty_`, `rtDW`, `rtP`, `rtC`, `rtZC`, `localB`, `localDW`, -…), một field DWork (`_DSTATE`, `_PreviousInput`, `_MODE`, `_SubsysRanBC`, …), -hoặc một checksum block-path nằm trong tên (`Sub_c4nxjoom3d_step` → -`Sub_j2kqp1wxab_step`) — **và** hai bên chung gốc sau khi bỏ phần do generator -sinh ra. Phần đó là đuôi mangling (`_c`, `_o4`) hoặc checksum -(`rtb_AND_c4nxjoom3d` → `rtb_AND_j2kqp1wxab`); biến tạm của MATLAB Coder bị đánh -số lại (`tmp`, `idx`, `loop_ub`, `i`) cũng nằm trong diện này. - -Tên ngắn hơn có thể làm một argument không còn phải xuống dòng ở cột 80, nên hai -bên chứa cùng các câu lệnh nhưng trải trên số dòng khác nhau. Hunk kiểu đó được so -như một chuỗi token — chỗ xuống dòng hết quan trọng, còn thứ tự token vẫn phải -khớp chính xác. - -Ngoài ra mọi hậu tố đều mang nghĩa. `SIG_TORQUE_MIN` → `SIG_TORQUE_MAX` và -`CFG_TIMEOUT_MS` → `CFG_TIMEOUT_US` là thay đổi thật, `rtb_AND_…` → `rtb_OR_…` -cũng vậy (block khác đang đẩy vào buffer đó), và `Sub_…_step` → `Sub_…_Init` cũng -vậy (entry point khác). Chữ số dính liền tên block (`rtb_Switch1` vs -`rtb_Switch2`) là một phần của tên, không phải đuôi mangle. +Tên biến do generator tự đặt, mỗi lần regenerate lại đổi, được nhận là +`rename` — nhưng điều kiện khá chặt. Hai identifier chỉ được coi là cùng một +biến khi cả hai đều do code generator đặt tên, tức là có ít nhất một trong ba +dấu hiệu sau: + +- prefix do generator sinh (`rtb_`, `rtu_`, `rty_`, `rtDW`, `rtP`, `rtC`, `rtZC`, `localB`, `localDW`, …); +- tên field DWork (`_DSTATE`, `_PreviousInput`, `_MODE`, `_SubsysRanBC`, …); +- checksum của block path nằm trong tên (`Sub_c4nxjoom3d_step` → `Sub_j2kqp1wxab_step`). + +**Và** sau khi bỏ phần do generator sinh ra, hai tên phải còn chung gốc. Phần +bỏ đi là đuôi mangling (`_c`, `_o4`) hoặc checksum (`rtb_AND_c4nxjoom3d` → +`rtb_AND_j2kqp1wxab`). Biến tạm của MATLAB Coder bị đánh số lại (`tmp`, `idx`, +`loop_ub`, `i`) cũng nằm trong luật này. + +Có một trường hợp phụ: tên mới ngắn hơn tên cũ có thể làm một lời gọi hàm không +còn phải xuống dòng ở cột 80, nên hai bên chứa đúng các câu lệnh như nhau nhưng +số dòng khác nhau. Với hunk kiểu này, tool so theo chuỗi token thay vì so từng +dòng — vị trí xuống dòng không còn quan trọng, nhưng thứ tự token vẫn phải khớp +chính xác. + +Ngoài những trường hợp trên, mọi hậu tố đều được coi là mang ý nghĩa. Đây là +các thay đổi thật, không phải rename: + +- `SIG_TORQUE_MIN` → `SIG_TORQUE_MAX` và `CFG_TIMEOUT_MS` → `CFG_TIMEOUT_US`: hậu tố là một phần ý nghĩa của tên. +- `rtb_AND_…` → `rtb_OR_…`: một block khác đang ghi vào buffer đó. +- `Sub_…_step` → `Sub_…_Init`: entry point khác hẳn. +- `rtb_Switch1` → `rtb_Switch2`: chữ số dính liền tên block là một phần của tên, không phải đuôi mangling. + +### Reorder + +Regenerate một model thường xuyên sinh ra cùng những phép gán độc lập — output +port, biến tạm — theo thứ tự khác, thứ mà một text diff thuần đọc ra là thay +đổi dù block tính ra đúng y hệt giá trị cũ. Một lần gộp `reorder` nhận ra +trường hợp này, nhưng chỉ khi có thể **chứng minh** được, không bao giờ đoán: + +- mọi dòng ở cả hai bên đều là phép gán scalar không side-effect (`ident = expr;` — không call, không ghi qua array/pointer/field, không control flow, không khai báo kèm kiểu); +- hai bên chứa đúng cùng các câu lệnh, chỉ đảo thứ tự; +- thứ tự mới giữ nguyên **mọi phụ thuộc dữ liệu** — hễ hai câu lệnh chung một biến và một trong hai ghi vào biến đó, thứ tự tương đối của chúng không đổi. + +Hai đoạn code tuần tự mà giữ nguyên thứ tự của mọi cặp lệnh phụ thuộc nhau thì +chắc chắn cho cùng kết quả, nên gộp reorder trong trường hợp này không làm mất +thay đổi nào. Nếu một trong ba điều kiện trên không thoả — có lời gọi hàm chen +vào giữa, vế phải của một phép gán thật sự đổi, hay một cặp lệnh phụ thuộc bị +đảo thứ tự — thì cả block vẫn tính là thay đổi thật. Khi không chắc, tool luôn +chọn hiện diff ra chứ không giấu đi. ### Comment là hạng mục riêng -File mà khác biệt *chỉ* nằm ở comment được báo là **Comment**, tách khỏi -**Unimportant** (UUID, timestamp, SW-VERSION, description, rename, whitespace) — -một banner comment bị viết lại triage khác hẳn một identifier bị đổi tên. Đếm -riêng trong summary của CLI và có marker riêng trên cây của viewer. File trộn -comment *với* noise loại khác thì vẫn là Unimportant. Trong viewer, `Comment` và -`Unimportant` mỗi cái có rule bật/tắt riêng; trong HTML report, comment không bao -giờ hiện dòng nào cả và chỉ `Unimportant` có badge để bấm hiện. +File mà khác biệt *chỉ* nằm ở comment được báo là **Comment**, tách riêng khỏi +**Unimportant** (UUID, timestamp, SW-VERSION, description, rename, whitespace). +Lý do tách: hai loại này cần xử lý khác nhau — comment bị viết lại thì đọc lướt +là xong, còn một identifier bị đổi tên thì phải kiểm xem có đúng là generator +đổi không. Mỗi loại có số đếm riêng trong summary của CLI và marker riêng trên +cây của viewer. File vừa đổi comment vừa có noise loại khác thì xếp vào +Unimportant, vì gọi nó là Comment sẽ không đúng. Viewer có rule bật/tắt riêng +cho từng loại; HTML report cho `Unimportant` một badge để bấm hiện, còn dòng +comment thì không bao giờ render. ## Phát hiện block bị di chuyển -Một block bị xoá ở chỗ này và xuất hiện nguyên vẹn ở chỗ khác (Embedded Coder sắp -xếp lại hàm và khai báo khi model đổi) được gán nhãn `moved` và tô **xanh dương** -thay vì đỏ/xanh lá. Vẫn tính là **Modified** — đảo thứ tự có thể đổi hành vi — -chỉ là dễ nhìn hơn hai khối đỏ/xanh lá to đùng. +Khi một block biến mất khỏi chỗ này và xuất hiện nguyên vẹn ở chỗ khác — +Embedded Coder hay sắp xếp lại thứ tự hàm và khai báo mỗi khi model đổi — nó +được gán nhãn `moved` và tô **xanh dương** thay vì đỏ/xanh lá. Vẫn tính là +**Modified**, vì đổi thứ tự code vẫn có thể đổi hành vi. Cái được ở đây là bạn +nhìn ra ngay đó là một block bị dịch chỗ, thay vì thấy một khối đỏ và một khối +xanh lá to đùng rồi phải tự đối chiếu xem hai bên có phải cùng nội dung không. -Việc so khớp bỏ qua tên auto-generated đổi lung tung, nên một block vừa bị di -chuyển vừa bị regenerate checksum vẫn được nhận là một lần move, không phải một -cặp delete cộng insert không liên quan. +Bước so khớp bỏ qua phần tên do generator tự đặt, nên một block vừa bị dịch chỗ +vừa bị đổi checksum trong tên vẫn được nhận ra là một lần di chuyển, thay vì bị +báo thành một cặp xoá + thêm không liên quan. ## Summary ngữ nghĩa AUTOSAR -Tool trích thông tin AUTOSAR từ cả hai bên và báo thay đổi ở mức **ngữ nghĩa**, -không chỉ ở mức text: +Song song với text diff, tool trích thông tin AUTOSAR từ cả hai bên và báo +thay đổi ở mức **ngữ nghĩa**: | Nguồn | Trích ra | Báo cáo | |---|---|---| @@ -221,104 +262,120 @@ không chỉ ở mức text: | `.c` | **RTE access point** — mọi lời gọi `Rte_Read/Write/Call/IrvRead/IrvWrite/Mode/Switch/…` (comment bị bóc trước khi đếm) | thêm / bớt | | `.a2l` | **Đối tượng calibration** — `CHARACTERISTIC` / `MEASUREMENT` theo tên (comment và chuỗi bị bóc trước, nên block bị comment-out không bị đếm) | thêm / bớt | -Cách hiển thị: +Chỗ bạn thấy nó: -- **CLI**: các khối `ARXML interfaces`, `AUTOSAR behavior`, `RTE access points` và `A2L objects` liệt kê mục `+`/`-`/`~` kèm file tương ứng. -- **HTML report**: một mục **AUTOSAR changes** ở đầu trang, nhóm theo loại (port interface / software component / port / runnable / event / RTE access point / A2L variables). Bấm vào tên file thì nhảy tới diff chi tiết của nó, và mỗi file trong Detailed changes mang note `Interfaces:` / `Behavior:` / `RTE:` / `A2L:` của riêng mình. Mục này luôn có mặt — không có gì để liệt kê thì nó nói ra điều đó, vì "không có thay đổi mức AUTOSAR" chính là kết luận người đọc cần, còn một cái tiêu đề biến mất thì đọc ra là chưa hề kiểm tra. -- File bị thêm hoặc xoá nguyên cái đóng góp toàn bộ interface / SWC / lời gọi RTE / đối tượng A2L bên trong nó vào danh sách thêm hoặc bớt. +- **CLI**: các khối `ARXML interfaces`, `AUTOSAR behavior`, `RTE access points` và `A2L objects`, mỗi khối liệt kê mục `+`/`-`/`~` kèm file tương ứng. +- **HTML report**: một mục **AUTOSAR changes** ở đầu trang, nhóm theo loại — port interface, software component, port, runnable, event, RTE access point, A2L variables. Bấm vào tên file thì nhảy thẳng tới diff chi tiết của nó, và mỗi file trong Detailed changes mang note `Interfaces:` / `Behavior:` / `RTE:` / `A2L:` của riêng mình. Mục này luôn được render kể cả khi rỗng, và ghi rõ là không có thay đổi nào — nếu để nó biến mất, người đọc không phân biệt được "đã kiểm và không có gì" với "chưa hề kiểm". +- File bị thêm hoặc xoá nguyên cái đóng góp toàn bộ interface, SWC, lời gọi RTE và đối tượng A2L bên trong nó vào danh sách thêm hoặc bớt, y như khi từng cái đổi riêng lẻ. -File có XML parse lỗi bị bỏ khỏi phần summary này (text diff của nó vẫn hiện đầy -đủ). Một lời gọi `Rte_` lạ không được đếm ở đây nhưng vẫn xuất hiện trong diff. +File có XML parse lỗi bị bỏ khỏi riêng phần summary này — text diff của nó vẫn +hiện đầy đủ ở chỗ khác. Một lời gọi `Rte_` mà tool không nhận ra cũng không +được đếm ở đây, nhưng vẫn xuất hiện trong diff bình thường. ## Nhóm theo model / SWC -File được nhóm theo **model Simulink** dựa trên quy ước đặt tên AUTOSAR của -Embedded Coder (`X.c`, `X.h`, `X.arxml`, `Rte_X.h`, `X_data.c`, bộ ARXML modular, -…). File không khớp model nào rơi vào nhóm cuối **Shared / other**. +File được nhóm theo **model Simulink**, dựa trên quy ước đặt tên AUTOSAR của +Embedded Coder (`X.c`, `X.h`, `X.arxml`, `Rte_X.h`, `X_data.c`, bộ ARXML +modular, …). File không khớp model nào rơi vào nhóm cuối **Shared / other** +thay vì bị âm thầm bỏ qua. ## Consistency check -ARXML của một model là contract của nó, A2L là bề mặt calibration; cả hai đều -được hiện thực hoá bằng code C sinh ra. Nên phụ thuộc chạy **một chiều**: nếu -interface hoặc calibration thật sự đổi thì code bắt buộc phải đổi theo — port mới -cần RTE access mới, characteristic mới cần symbol mới. Khi một access point dịch -chuyển trong ARXML hoặc A2L mà code C vẫn identical, report (ngay dưới phần -AUTOSAR changes), viewer (góc dưới bên trái, dưới panel quick-changes) và -terminal đều nêu tên model đó — dấu hiệu quen thuộc của một lần regen thiếu hoặc -sót, và là thứ duy nhất mà cách xem từng-file-một không chỉ ra được, vì mỗi file -tách riêng đều bình thường, chỗ lệch nằm *giữa* chúng. - -Điều kiện kích hoạt tính ở **mức access point, không phải mức file**: một port -interface hoặc port/runnable/event của SWC bị thêm hoặc bớt (ARXML), hoặc một đối -tượng calibration bị thêm hoặc bớt (A2L). Các package thư viện mà mỗi lần export -đều ghi lại — base type, compu-method, unit — làm đổi byte của file nhưng không -đụng tới access point nào, và **không** làm nổi cảnh báo. - -Ngoại lệ là file mà tool không đọc được tới mức đó — XML hỏng, hoặc nội dung -binary: không chứng minh được gì về access point của nó, nên file đó đổi thì vẫn -nổi cảnh báo. Không chứng minh được thì không bao giờ bị coi là noise. - -Chiều ngược lại **không** bị nêu. Code đổi mà ARXML với A2L không đổi là trường -hợp bình thường — sửa logic hay sửa gain bên trong không đụng interface, không -đụng biến calibration, nên chẳng có gì để chúng phải chạy theo. - -Kèm theo đó là một cảnh báo thứ hai, xét **giữa các model**. Khi code C của một -model có thêm **RTE access point** (`+ Rte_Write_…`) mà code C của một model -*khác* vẫn identical từng byte, thì mẻ đó là gen nhanh một model chứ không phải -gen đầy đủ — một lần regen thật ghi lại ít nhất cái banner timestamp ở mọi model, -nên một model còn identical chính là bằng chứng nó bị bỏ qua. RTE access mới làm -rộng interface của model, nên phải gen lại architecture (RTE layer và các SWC còn -lại) thì code mới tích hợp được; model đó bị nêu kèm câu *"gained an RTE access -while a peer model stayed identical — regenerate the architecture before -integrating"*. - -Đây là **cảnh báo, không phải verdict**: nó không gộp file nào, không đổi số đếm, -không đổi exit code. Chỉ thay đổi *thật* trên bề mặt mới tính — một ARXML chỉ -churn UUID thì không thật sự đổi, nên code C cũ không phải là desync. +ARXML của một model là file khai báo interface của model — có những port nào, +runnable nào, event nào. A2L là file khai báo các biến calibration và +measurement. Code C sinh ra phải hiện thực đúng cả hai file đó: thêm một port +trong ARXML thì trong code phải có thêm lời gọi `Rte_*` tương ứng, thêm một +characteristic trong A2L thì trong code phải có thêm biến tương ứng. + +Quan hệ này chỉ đi **một chiều**. Khi ARXML hoặc A2L có thêm/bớt một port, +runnable, event hay biến calibration mà file C của model đó không đổi một byte +nào, thì có gì đó sai: report (ngay dưới phần AUTOSAR changes), viewer (góc +dưới bên trái, dưới panel quick-changes) và terminal đều nêu tên model đó ra. +Nguyên nhân thường gặp là lần regenerate chạy chưa xong hoặc chạy thiếu model. +Một diff xem từng file riêng lẻ không phát hiện được, vì bản thân từng file +đều bình thường — cái sai nằm ở chỗ hai file không khớp nhau. + +Cảnh báo này tính theo **access point, không phải theo file**: phải có một port +interface hoặc port/runnable/event của SWC bị thêm/bớt trong ARXML, hoặc một +đối tượng calibration bị thêm/bớt trong A2L. Mỗi lần export, công cụ ghi lại cả +các package thư viện dùng chung (base type, compu-method, unit) — đổi nhiều byte +trong file nhưng không đụng tới port hay runnable nào, nên riêng chúng không +làm nổi cảnh báo. + +Có một ngoại lệ cố ý: file mà tool không parse được — XML hỏng, hoặc nội dung +binary — thì mọi thay đổi trên nó đều nổi cảnh báo, vì tool không đọc được để +biết access point của nó có đổi hay không. Không kiểm chứng được thì không bao +giờ được xếp vào noise. + +Chiều ngược lại **không** bị cảnh báo: code C đổi mà ARXML và A2L giữ nguyên là +chuyện bình thường — sửa logic hay sửa gain bên trong một khối không đụng gì +tới interface hay biến calibration. + +Còn một cảnh báo thứ hai, đối chiếu **giữa các model với nhau**. Khi code C của +model A có thêm một lời gọi `Rte_*` mới (`+ Rte_Write_…`) mà code C của model B +không đổi một byte nào, nhiều khả năng bạn chỉ regenerate mình model A. Lý do +tin được điều đó: một lần regenerate đầy đủ luôn ghi lại ít nhất banner +timestamp trong mọi model, nên model B còn y nguyên nghĩa là nó không được +generate lại. Lời gọi `Rte_*` mới mở rộng interface của model A, nên RTE layer +và các SWC còn lại phải được sinh lại thì code mới build và tích hợp được. Model +đó được gắn kèm dòng *"gained an RTE access while a peer model stayed identical +— regenerate the architecture before integrating."* + +Cả hai đều là **cảnh báo để đi kiểm tra, không phải kết luận**: chúng không gộp +file nào, không đổi số đếm, không đổi exit code. Chỉ thay đổi thật mới tính — +một ARXML chỉ đổi UUID thì coi như không đổi, nên file C giữ nguyên bên cạnh nó +không bị xem là lệch. ## HTML report -File self-contained, mỗi lần compare một file: badge bật/tắt, cây thư mục, ô lọc, -diff xếp gọn được theo từng file. Mỗi hạng mục một badge — `Modified`, `Added`, -`Deleted`, rồi `Unimportant`, cái duy nhất mặc định tắt — nên mở ra là thấy ngay -cái đáng xem. Code được **tô cú pháp** đúng như cách viewer tô, và các ký tự thay -đổi trong một dòng được highlight trọn cả định danh, nên `rtb_Sum1` → `rtb_Sum2` -đọc ra là một cái tên bị đổi chứ không phải một chữ số bị đổi. +Report là một file self-contained cho mỗi lần compare: badge bật/tắt, cây thư +mục, ô lọc, và diff từng file có thể gộp lại hoặc mở ra. Mỗi hạng mục có một +badge riêng — `Modified`, `Added`, `Deleted`, rồi `Unimportant`, cái duy nhất +mặc định gộp lại — nên mở trang ra là thấy ngay cái đáng xem. Code được tô cú +pháp đúng như cách viewer tô. Khi một dòng có ký tự thay đổi, tool highlight +trọn cả định danh chứ không chỉ ký tự khác nhau: `rtb_Sum1` đổi thành +`rtb_Sum2` hiện lên như một cái tên bị đổi, thay vì chỉ một chữ số bị bôi đậm +giữa dòng. ### Hiện cái gì, gộp cái gì -Mỗi change thật hiện ra **ba dòng ngữ cảnh trên và dưới** — không phải cả file: +Mỗi change thật hiện ra ba dòng ngữ cảnh trên và dưới nó — không phải cả file +xung quanh: -- Hunk comment / Unimportant **nằm trong cửa sổ đó** thì hiện đầy đủ, tô xám. -- Hunk **nằm ngoài mọi cửa sổ** thì không hiện gì cho tới khi bấm `Unimportant`, lúc đó chúng hiện ra tô xám phẳng đúng vị trí của nó. -- File **không có** change thật nào thì giữ nguyên ngữ cảnh, và hunk bị gộp giữ placeholder `⋯ N lines hidden`. +- Hunk comment / Unimportant nằm **trong cửa sổ đó** thì hiện đầy đủ, chỉ tô xám. +- Hunk nằm **ngoài mọi cửa sổ** thì không hiện gì cả cho tới khi bạn bấm `Unimportant`, lúc đó chúng hiện ra tô xám phẳng đúng vị trí của nó trong file. +- File **không có** change thật nào thì giữ nguyên toàn bộ ngữ cảnh, và hunk bị gộp giữ placeholder `⋯ N lines hidden` thay vì biến mất. -Các dòng luôn nằm trong file; chỉ có màn hình là yên tĩnh. File mà khác biệt *chỉ* -là comment thì không có mục chi tiết riêng — vẫn giữ marker `≉` và đếm vào -`Comment` trên cây. (Vì sao cửa sổ hẹp vậy → -[architecture](architecture.md#những-quyết-định-nên-biết-trước-khi-sửa).) +Các dòng đó không bị xoá khỏi file — chỉ là report không hiện chúng ra. File mà +khác biệt *chỉ* là comment thì không có mục chi tiết riêng; nó chỉ giữ marker +`≉` và được đếm vào `Comment` trên cây. (Vì sao cửa sổ ngữ cảnh hẹp như vậy chứ +không rộng hơn: +[architecture.md](architecture.md#những-quyết-định-nên-biết-trước-khi-sửa).) -`Focus on changes` cạnh cây thư mục thu gọn cây lại còn đúng các file thật sự có -thay đổi — dòng identical, comment-only và Unimportant biến mất, thư mục nào chỉ -còn lại những loại đó thì biến theo. Giống `Hide identical` bên viewer, đây là -view: verdict và số đếm không đổi. Nút `☀ Light` / `☾ Dark` nằm ở góc trên bên -phải — cả hai palette đều nhúng sẵn trong file, nên đổi màu không tải gì và chạy -được trên máy không có internet. +`Focus on changes`, cạnh cây thư mục, thu cây lại còn đúng các file thật sự có +thay đổi — dòng identical, comment-only và Unimportant biến mất, thư mục nào +chỉ còn lại toàn những loại đó thì biến theo luôn. Giống `Hide identical` bên +viewer, đây thuần là một view: verdict và số đếm bên dưới không đổi gì cả. Nút +`☀ Light` / `☾ Dark` nằm ở góc trên bên phải; cả hai palette đều nhúng sẵn +trong chính file đó, nên đổi màu không tải gì hết và chạy tốt trên máy không +có internet. ## Tích hợp CI -Chạy như một gate của pipeline — một lệnh, exit code có nghĩa: +Chạy như một gate của pipeline — một lệnh, exit code có nghĩa thật sự: ```bash python -m compare_tool old_dir new_dir --exit-zero --exclude compare_report.html ``` -`--exit-zero` giữ build xanh khi code chỉ bị regenerate; `--exclude` không cho -report của lần chạy trước bị tính thành diff. Publish `compare_report.html` như -một build artifact. +`--exit-zero` giữ build xanh khi việc duy nhất xảy ra là regenerate; +`--exclude` không cho report của lần chạy trước bị tính vào diff. Publish +`compare_report.html` như một build artifact là có luôn bản ghi bấm vào được +cho từng lần chạy. -Pipeline thường dựng bản baseline vào một thư mục tạm, khiến header report ghi -tên thư mục đó. Đặt tên hai phía theo đúng cái đã được so sánh: +Pipeline thường dựng bản baseline vào một thư mục tạm, mặc định khiến header +report ghi tên thư mục tạm đó thay vì thứ gì có ý nghĩa. Đặt tên hai phía theo +đúng cái đã thực sự được so sánh: ```bash python -m compare_tool "$OLD_DIR" "$NEW_DIR" \ @@ -326,8 +383,8 @@ python -m compare_tool "$OLD_DIR" "$NEW_DIR" \ --current-name "build $BUILD_NUMBER" ``` -Xem [azure-pipelines.yml](../../azure-pipelines.yml) để có ví dụ chạy được (OLD -lấy ra bằng `git worktree`, NEW là working tree). +Xem [azure-pipelines.yml](../../azure-pipelines.yml) để có ví dụ chạy được từ +đầu đến cuối — OLD lấy ra bằng `git worktree`, NEW chỉ là working tree. ## Build một file @@ -343,8 +400,8 @@ lấy ra bằng `git worktree`, NEW là working tree). .\build.ps1 -PyzOnly # chỉ zipapp (build cái này không cần PyInstaller / PySide6) ``` -`dist\compare-tool.exe` là **một binary mang cả hai front end**, và mang icon -riêng của tool: +`dist\compare-tool.exe` là một binary mang cả hai front end, và mang icon +riêng của tool chứ không phải icon generic: | Cách gọi | Chuyện gì xảy ra | |---|---| @@ -352,13 +409,15 @@ riêng của tool: | `compare-tool.exe --qt ` | viewer side-by-side, hai thư mục đã nạp sẵn | | double-click (không tham số) | viewer side-by-side, chờ kéo thả hai thư mục | -Build dưới dạng ứng dụng **console** có chủ đích, để lần chạy trong terminal giữ -được exit code (`1` = có thay đổi thật, `2` = compare không trọn vẹn) cho CI gate. -Viewer ẩn cửa sổ console lúc runtime — double-click sẽ thấy nó loé lên một cái. -Khi crash thì console được hiện lại để thấy lỗi. +Nó được build dưới dạng ứng dụng **console** có chủ đích, để khi chạy trong +terminal thì exit code (`1` = có thay đổi thật, `2` = compare không trọn vẹn) +còn nguyên cho CI gate dùng. Lúc mở viewer, cửa sổ console được ẩn đi — +double-click sẽ thấy nó loé lên một cái rồi biến mất. Khi app crash thì console +được hiện lại để bạn đọc được lỗi, thay vì cửa sổ đóng luôn và không thấy gì. -- **`.pyz` (zipapp, stdlib)**: `python compare_tool.pyz [flags]`. Ưu tiên dùng khi máy có sẵn Python — nhỏ gọn, không cần dependency lúc build, không bị antivirus tuýt còi. CLI chạy ở đâu cũng được; viewer cần thêm PySide6 trên máy đó (không có thì tool nói ra, không mở). -- **`.exe` (PyInstaller onefile, ~47 MB)**: máy đích không cần Python. Build cần `pyinstaller` và `PySide6` trên máy dev (`build.ps1` tự cài), và binary chỉ chạy trên đúng OS đã build ra nó. File PyInstaller đôi khi bị antivirus hoặc AppLocker chặn — chỗ đó lùi về dùng `.pyz`. +- **`.pyz` (zipapp, stdlib)**: `python compare_tool.pyz [flags]`. Ưu tiên dùng cái này khi máy có sẵn Python — nhỏ gọn, build không cần dependency, và ít bị antivirus chặn hơn file PyInstaller. CLI chạy ở đâu cũng được; viewer cần cài thêm PySide6 trên máy đó, nếu chưa có thì tool in ra một dòng báo thiếu chứ không đổ traceback. +- **`.exe` (PyInstaller onefile, ~47 MB)**: máy đích hoàn toàn không cần Python. Build cần `pyinstaller` và `PySide6` trên máy dev (`build.ps1` tự cài cả hai cho bạn), và binary chỉ chạy trên đúng OS đã build ra nó. File PyInstaller thỉnh thoảng bị antivirus hoặc AppLocker chặn — gặp vậy thì lùi về dùng `.pyz`. -Mọi flag của CLI hành xử y hệt trong bản đóng gói. `build/` và `dist/` đã nằm sẵn -trong `.gitignore`. +Mọi flag của CLI hành xử y hệt nhau trong mọi bản đóng gói. `build/` và +`dist/` đã nằm sẵn trong `.gitignore`, nên một lần build ở máy local không bao +giờ hiện ra như thứ cần commit. From ba7abfb15e9243e17397a7d722f559acdd755ecb Mon Sep 17 00:00:00 2001 From: longvo920 Date: Sun, 23 Aug 2026 11:21:10 +0700 Subject: [PATCH 2/2] docs: record how the shipped documentation is written The rewrite that preceded this commit fixed the same four faults over and over, so they are worth stating once rather than rediscovering: define a concept before leaning on it, drop metaphor where a plain description fits, give the user's reason instead of the internal design rule, and break chained conditions into a list. The translation rule is the one that cost the most: docs/vi renders the meaning, not the words, and because the English file is the source of truth a fix that changes what a sentence claims has to land on both sides or they diverge silently. --- CLAUDE.md | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index a81095e..d4e4efb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -211,7 +211,42 @@ facts a file cannot know: the ref is `main`, and the tag is not taken. Releases are not moved. A published version is refused, never overwritten. -## 10. Workflow +## 10. Docs are written for someone who did not build the tool + +The README, `docs/usage.md`, the CHANGELOG and the `docs/vi/` translations ship +with the product. The reader is an engineer who knows AUTOSAR and Simulink but +has never seen this codebase — not a reviewer who already knows why a rule +exists. + +- **Define the thing before leaning on it.** "A model's ARXML is its interface + contract, its A2L is its calibration surface" tells that reader nothing. + "Its ARXML declares which ports, runnables and events the model has; its A2L + declares the calibration and measurement variables" tells them what the files + hold, so the rule built on top of it lands. +- **No metaphor where a plain description fits.** "the fingerprint of a quick + regen", "only the screen is quiet", "a near-tie is not an answer" all read as + writing. Say what happens: "you probably regenerated only model A", "the lines + are still in the file, the report just doesn't render them", "generated files + resemble each other, so a close second match isn't trustworthy". +- **Give the user's reason, not the internal one.** *Why* Comment is its own + category is rule 2's problem. The doc's job is "a rewritten comment you can + skim; a renamed identifier you have to check." +- **Three conditions in one paragraph become a list.** Prose that chains + "and… and… but only when…" is where a reader loses the thread. The rename and + reorder rules are both lists for that reason. +- **CHANGELOG entries lead with the outcome**, one bold sentence naming what + the user now gets, then a sentence or two on when they hit it. Same language + rules as above. + +`docs/vi/` translates the **meaning**, never the words. "Hợp đồng interface" is +what "interface contract" turns into when it is translated literally, and it is +meaningless in Vietnamese. Terms that live in the tool and in the industry — +port, runnable, calibration, noise, hunk, verdict — stay in English rather than +being forced into a Vietnamese word nobody uses. The English file is the +source of truth: when a fix to the Vietnamese actually changes what a sentence +claims, fix the English in the same change or the two versions quietly diverge. + +## 11. Workflow - **Commit per phase / per goal batch.** The message explains *why*, not what the diff already shows.