diff --git a/docs/superpowers/specs/2026-07-17-build-number-versioning-design.md b/docs/superpowers/specs/2026-07-17-build-number-versioning-design.md new file mode 100644 index 0000000..ff13ba8 --- /dev/null +++ b/docs/superpowers/specs/2026-07-17-build-number-versioning-design.md @@ -0,0 +1,194 @@ +# Progressive Build Number in Version Field 4 — Design + +**Date:** 2026-07-17 +**Status:** Approved (brainstorming) + +## Goal + +Turn the 4th version field (`revision`) of the executable's `VERSIONINFO` into a +**progressive build counter** that increments on every build, so two `.exe` files can be +compared at a finer granularity than the semantic `major.minor.build` (currently `3.0.1`) +to confirm they came from the same build. + +- Fields 1–3 (`3.0.1`) stay **manual/semantic** and remain the only part shown in the + About dialog. +- Field 4 is **not** shown in the About dialog; it is only a build fingerprint visible in + the file's Windows "Details" property page and the binary `FILEVERSION`. +- The counter is **committed to the repository** so it keeps progressing when the project + is built from different machines. + +## Approach + +Approach **B** (editor-safe): the entire `VS_VERSION_INFO` block is moved out of the +Visual Studio resource-editor–managed area into a hand-maintained `.rc2` file that uses +preprocessor macros. The counter lives in a committed plain-text file and is regenerated +into a header by a pre-build MSBuild target. + +## Files + +### `version.h` (committed) — semantic version, single source of truth +```c +#pragma once + +#define VER_MAJOR 3 +#define VER_MINOR 0 +#define VER_BUILD 1 + +// Per-build counter. version_build.h is regenerated on every build by the +// IncrementBuildCounter MSBuild target (BeforeTargets="ResourceCompile"), so it always +// exists at compile time. The #ifndef is a safety fallback only. +#include "version_build.h" +#ifndef VER_BUILDNUM +# define VER_BUILDNUM 0 +#endif +``` +The manual version is edited **here** from now on, not in the VS resource editor. + +### `BuildCounter.txt` (committed) — the counter's source of truth +A single integer on one line. Seeded at `1` to match the current `3.0.1.1`. This is the +file that travels between machines; each build rewrites it with the incremented value. + +### `version_build.h` (gitignored, generated) — derived, never committed +Regenerated every build from `BuildCounter.txt`: +```c +#define VER_BUILDNUM +``` +Derived artifact → excluded from git (added to `.gitignore`). + +### `TaskbarCalculator.rc2` (committed) — the hand-maintained version resource +Holds the whole `VS_VERSION_INFO` block, built from the macros. Skeleton: +```c +#include "version.h" + +#define VER_STRINGIZE2(x) #x +#define VER_STRINGIZE(x) VER_STRINGIZE2(x) +#define VER_VERSION_STR \ + VER_STRINGIZE(VER_MAJOR) "." VER_STRINGIZE(VER_MINOR) "." \ + VER_STRINGIZE(VER_BUILD) "." VER_STRINGIZE(VER_BUILDNUM) + +LANGUAGE LANG_NEUTRAL, SUBLANG_DEFAULT + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VER_MAJOR,VER_MINOR,VER_BUILD,VER_BUILDNUM + PRODUCTVERSION VER_MAJOR,VER_MINOR,VER_BUILD,VER_BUILDNUM + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040004b0" + BEGIN + VALUE "CompanyName", "Marco Maroni" + VALUE "FileDescription", "Taskbar Calculator" + VALUE "FileVersion", VER_VERSION_STR + VALUE "InternalName", "TaskbarCalculator.exe" + VALUE "LegalCopyright", "Copyright (C) 2001-2019" + VALUE "OriginalFilename", "TaskbarCalculator.exe" + VALUE "ProductName", "Taskbar Calculator" + VALUE "ProductVersion", VER_VERSION_STR + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x400, 1200 + END +END +``` +`rc.exe` supports `#`-stringize and adjacent string-literal concatenation, so +`VER_VERSION_STR` compiles to e.g. `"3.0.1.42"`. (The stale `LegalCopyright` year is +preserved verbatim — out of scope here.) + +### `TaskbarCalculator.rc` (edited) — wiring, editor-safe +1. Remove the `VS_VERSION_INFO` block (current lines ~21–57, the `// Version` comment + through the closing `END`) from the neutral-resources section. +2. Register the include in the (currently empty) `TEXTINCLUDE 3` block so the editor + preserves it: + ``` + 3 TEXTINCLUDE + BEGIN + "#include ""TaskbarCalculator.rc2""\r\n" + "\0" + END + ``` +3. Add the real include in the bottom `#ifndef APSTUDIO_INVOKED` region ("Generated from + the TEXTINCLUDE 3 resource"): + ```c + #ifndef APSTUDIO_INVOKED + #include "TaskbarCalculator.rc2" + #endif // not APSTUDIO_INVOKED + ``` +When VS opens the `.rc` it defines `APSTUDIO_INVOKED`, so it skips the include and never +parses `.rc2`/`version.h`/macros — the editor stays happy. `rc.exe` (no `APSTUDIO_INVOKED`) +compiles the include normally. + +### `TaskbarCalculator.vcxproj` (edited) — the increment target +```xml + + + $(MSBuildProjectDirectory)\BuildCounter.txt + $(MSBuildProjectDirectory)\version_build.h + + + + + + <_OldBuildCounter>@(_BuildCounterLines) + <_OldBuildCounter Condition="'$(_OldBuildCounter)' == ''">0 + <_NewBuildCounter>$([MSBuild]::Add($(_OldBuildCounter), 1)) + + + + + +``` +Runs before every `ResourceCompile`; rewriting `version_build.h` makes it newer than the +compiled resource, so `rc.exe` recompiles and the fresh number is baked into the `.exe`. +The counter is shared across Debug and Release (one file in the project directory). + +### `.gitignore` (edited) +Add `version_build.h` (generated). `BuildCounter.txt` stays **tracked**. + +### `AboutDialog.cpp` — unchanged +Already reads only `major.minor.build` from the runtime `FILEVERSION`, so it keeps showing +`3.0.1`; the new field 4 stays invisible there. It reflects `version.h` automatically. + +## Constraints / accepted side effects + +- **16-bit fields:** `VERSIONINFO` numbers are `0..65535`; a simple counter fits for a very + long time. (This is why field 4 is a plain counter, not an encoded timestamp.) +- **Always-dirty counter:** every build rewrites `BuildCounter.txt`, so it shows as + modified in `git status`. It is committed together with real work; on another machine a + `pull` continues the sequence. Building on two machines before syncing can cause a trivial + conflict on `BuildCounter.txt`, resolved by keeping the higher number. +- **VS fast up-to-date check:** command-line `msbuild` (the documented build path) always + runs the target and increments. The VS IDE "fast up-to-date check" may occasionally skip + a no-op build without incrementing — acceptable, since the counter only needs to advance + on real builds. +- **First build / fresh clone:** `version_build.h` is absent until the first build, but the + target generates it before `ResourceCompile`, so the first build succeeds. Nothing but + `rc.exe` (at build time) ever parses `version.h`/`.rc2`, so a missing generated header + never affects the C++ IntelliSense or the resource editor. + +## Testing + +- Engine regression: `tests\build-and-run.cmd` → 16/16 (unaffected; no engine change). +- Build twice with `msbuild ... /p:Configuration=Release /p:Platform=x64` and confirm the + `.exe` "Details" tab shows the version's 4th field incrementing (e.g. `3.0.1.2` then + `3.0.1.3`), and that `BuildCounter.txt` advances in lockstep. +- Confirm the About dialog still shows `3.0.1` (field 4 not shown). +- Confirm opening/closing the `.rc` in the VS resource editor leaves `.rc2` and the macros + untouched. + +## Out of scope (YAGNI) + +- Showing the build number in the About dialog. +- Timestamp/date encoding, git-hash embedding, or CI integration. +- Fixing the stale `LegalCopyright` year or other version strings. +- Bumping the semantic version — that remains a manual edit in `version.h`.