Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
113 changes: 61 additions & 52 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,142 +1,151 @@
# CodeGen Compare Tool
<p align="center">
<img src="resources/logo/logo-full.png" alt="CodeGen Compare Tool" width="360">
</p>

[![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)
<p align="center">
<a href="https://github.com/longvo92/codegen-compare-tool/actions/workflows/test.yml"><img src="https://github.com/longvo92/codegen-compare-tool/actions/workflows/test.yml/badge.svg" alt="Test"></a>
<a href="https://www.python.org/downloads/"><img src="https://img.shields.io/badge/python-3.8%2B-blue.svg" alt="Python 3.8+"></a>
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green.svg" alt="License: MIT"></a>
<a href="https://github.com/longvo92/codegen-compare-tool/releases/latest"><img src="https://img.shields.io/github/v/release/longvo92/codegen-compare-tool?label=release&color=blue" alt="Release"></a>
</p>

🇻🇳 **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)
<p align="center">🇻🇳 <b>Tiếng Việt:</b> <a href="docs/vi/README.md">README</a> · <a href="docs/vi/usage.md">Hướng dẫn</a> · <a href="docs/vi/architecture.md">Kiến trúc</a></p>

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 <old_gen_folder> <new_gen_folder> --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` | `<ADMIN-DATA>` blocks, `<DATE>` | .arxml .xml |
| `sw-version` | `<SW-VERSION>` stamps, bumped on every regenerate | .arxml .xml |
| `sw-version` | `<SW-VERSION>` stamps, which bump on every regenerate | .arxml .xml |
| `description` | `<DESC>`, `<LONG-NAME>`, `<INTRODUCTION>` | .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.01s0.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` objectsgrouped 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

Expand Down
Loading