🇻🇳 Tiếng Việt: README · Hướng dẫn · Kiến trúc
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.
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.
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 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 |
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 — every flag, the viewer's shortcuts, the exact noise rules, how the report is laid out, CI and packaging.
🏗 Architecture — how the pieces fit together and why.
You can run it straight out of a clone, nothing to install:
git clone https://github.com/longvo92/codegen-compare-tool.git
python -m compare_tool --helpOr install it as a proper command, compare-tool:
pip install git+https://github.com/longvo92/codegen-compare-tool.gitStuck on a machine that won't let you install anything at all? There's a single-file build for that.
python -m compare_tool <old_gen_folder> <new_gen_folder> --report out.htmlThat 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:
python -m compare_tool baseline.zip current.zip --report out.htmlLeave the folders off entirely and the viewer opens instead — just drag the two folders (or two .zips) onto it:
python -m compare_toolIf you're wiring this into a build, the exit code is the contract:
| Code | Meaning |
|---|---|
0 |
No real changes |
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 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.
| 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 |
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, 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 |
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.
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 gets extracted, and how it's shown
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
pip install "codegen-compare-tool[viewer]"
python -m compare_toolA 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
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
python -m compare_tool old_dir new_dir --exit-zero --exclude compare_report.html--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 has a working example if you want to see it end to end.
→ flags, exit codes, and packaging for locked-down build machines
python -m unittest discover -s testsCI 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. 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 has the module map and a table of what to touch for what kind of change.
Long Vo Thien
Released under the MIT License © 2026 Long Vo Thien.


