diff --git a/.gitignore b/.gitignore index f621c59..dfa48fa 100644 --- a/.gitignore +++ b/.gitignore @@ -49,6 +49,11 @@ mfd_layout.cfg # at this machine, which is not the repo's business. pilot.cfg +# Generated by stamp-version.ps1 as RP_L4's pre-build step. The patch +# number in it IS this repository's commit count, so a committed copy +# would be stale the moment it was committed. +/RP_L4/rpl4build.h + # Build-output static libs that land in lib/ (the two committed dependency # libs, OpenAL32.lib and libsndfile-1.lib, stay tracked). /lib/Munga_L4.lib diff --git a/BUILD.md b/BUILD.md index 5355c7f..023357f 100644 --- a/BUILD.md +++ b/BUILD.md @@ -50,10 +50,36 @@ The solution is [WinTesla.sln](WinTesla.sln) with four v143 projects: Build order is resolved by `ProjectReference` (RP_L4 and RPL4TOOL both reference Munga_L4). +**Versioning:** the patch number *is* the repository's commit count, so a +build always names the commit it came from and there is no question about +which changes a given binary contains. +[stamp-version.ps1](stamp-version.ps1) runs as RP_L4's pre-build step and +writes the generated, uncommitted `RP_L4\rpl4build.h`: + +``` +#define RP412_VERSION "4.12.96" +#define RP412_VERSION_LONG "4.12.96 (a1b2c3d)" +``` + +The game logs the long form on its first line. A trailing `+` on the hash +means the tree had uncommitted changes to tracked files when it was built — +useful when a test machine reports something a clean build cannot reproduce. +Only the `4.12` product line is set by hand, at the top of the script. + +The header is deliberately not committed: the commit that recorded a +hardcoded number would itself change the count, so the file would be stale +the moment it landed. It is rewritten only when the stamp actually changes, +so ordinary rebuilds do not recompile `RPL4.CPP` for nothing. Building +outside a git checkout stamps `4.12.x (no repository)` rather than inventing +a number that would sort against real ones. + **Packaging:** [pack-dist.ps1](pack-dist.ps1) assembles a runnable game into `dist\` (exe + PDB, game data, OpenAL/libsndfile runtimes, desktop `environ.ini`, `start-windowed.bat`, README). Pass `-Zip` to also produce -`dist\RedPlanet412-prototype.zip` for handing to someone else. +`RedPlanet-.zip` for handing to someone else. It reads the version +from `rpl4build.h` rather than asking git again, so the package and the +binary inside it cannot disagree, and it warns if the build it is packing +came from a modified tree. ## 3. VS2022 migration notes (what changed and why) diff --git a/RP_L4/RPL4.CPP b/RP_L4/RPL4.CPP index 6ea588e..f0deb20 100644 --- a/RP_L4/RPL4.CPP +++ b/RP_L4/RPL4.CPP @@ -29,6 +29,7 @@ #include "..\munga_l4\l4mfdview.h" // RPWindowLayout_* #include "..\munga_l4\l4joy.h" // RPJoyConfigWizard #include "rpl4ver.h" +#include "rpl4build.h" // generated: RP412_VERSION / RP412_VERSION_LONG #include "..\munga\resver.h" #include "..\munga\resource.h" // added for game status drawing support @@ -188,7 +189,13 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine fclose(file); } - DEBUG_STREAM << "Red Planet 4.12.7" << std::endl << std::flush; + // + // The patch number is this repository's commit count and the hash beside + // it names the commit, so a log from a test machine says exactly which + // build it came from. A trailing '+' means the tree had uncommitted + // changes when it was built. See stamp-version.ps1. + // + DEBUG_STREAM << "Red Planet " << RP412_VERSION_LONG << std::endl << std::flush; DEBUG_STREAM << "L4CONTROLS=" << getenv("L4CONTROLS") << std::endl << std::flush; #ifdef RP412_STEAM diff --git a/RP_L4/RP_L4.vcxproj b/RP_L4/RP_L4.vcxproj index 8740dfb..86f02c8 100644 --- a/RP_L4/RP_L4.vcxproj +++ b/RP_L4/RP_L4.vcxproj @@ -70,6 +70,15 @@ false true + + + powershell -NoProfile -ExecutionPolicy Bypass -File "$(ProjectDir)..\stamp-version.ps1" + Stamping the build version from git + diff --git a/pack-dist.ps1 b/pack-dist.ps1 index 9024bfd..5cbac6f 100644 --- a/pack-dist.ps1 +++ b/pack-dist.ps1 @@ -27,6 +27,23 @@ if (-not (Test-Path $exe)) { throw "Release\rpl4opt.exe not found - build first (see BUILD.md 2)." } +# --- version --------------------------------------------------------------- +# Read the stamp the exe was BUILT with rather than asking git again: a +# commit between the build and the pack would otherwise have the package +# claiming a version the binary inside it does not report. +$buildHeader = Join-Path $root 'RP_L4\rpl4build.h' +if (-not (Test-Path $buildHeader)) { + throw "RP_L4\rpl4build.h not found - build first, or run stamp-version.ps1." +} +$stamp = Get-Content $buildHeader -Raw +$version = ([regex]::Match($stamp, '#define\s+RP412_VERSION\s+"([^"]+)"')).Groups[1].Value +$versionLong = ([regex]::Match($stamp, '#define\s+RP412_VERSION_LONG\s+"([^"]+)"')).Groups[1].Value +if (-not $version) { throw "could not read RP412_VERSION from $buildHeader" } +if ($stamp -match '#define\s+RP412_BUILD_DIRTY\s+1') { + Write-Warning "packing a build made from a modified tree ($versionLong)" +} +Write-Host "Version $versionLong" + # Refuse to touch a dist the game is currently running from. $running = Get-Process rpl4opt -ErrorAction SilentlyContinue | Where-Object { $_.Path -like "$dist\*" } @@ -83,6 +100,10 @@ Set-Content -Path "$dist\CONTROLS.txt" -Encoding ascii -Value $controls # without a charset the typography arrives as mojibake. Written without a # BOM so the charset declaration is the only thing speaking. $controlsPage = Get-Content (Join-Path $root 'docs\rp412-controls.html') -Raw -Encoding UTF8 +# Stamp the shipped copy with the build's own version. The source keeps a +# readable one for publishing; only "4.12." is touched, which on this +# page is always the version and never anything else. +$controlsPage = [regex]::Replace($controlsPage, '4\.12\.\d+', $version) $page = @" @@ -401,7 +422,7 @@ set RP412JOYCONFIG= "@ Set-Content -Path "$dist\README.txt" -Encoding ascii -Value @" -Red Planet 4.12.7 +Red Planet $version ================= Run start-fullscreen.bat for borderless over the whole monitor, or @@ -471,7 +492,7 @@ $size = (Get-ChildItem $dist -Recurse | Measure-Object Length -Sum).Sum Write-Host ("dist ready: {0:N1} MB" -f ($size / 1MB)) if ($Zip) { - $zipPath = Join-Path $root 'RedPlanet-4.12.7.zip' + $zipPath = Join-Path $root "RedPlanet-$version.zip" Write-Host "zipping to $zipPath..." # Everything lives under a single RP412\ folder inside the zip, so diff --git a/stamp-version.ps1 b/stamp-version.ps1 new file mode 100644 index 0000000..4e1f9a1 --- /dev/null +++ b/stamp-version.ps1 @@ -0,0 +1,103 @@ +# ============================================================================ +# stamp-version.ps1 - write RP_L4\rpl4build.h from the repository's own state +# ============================================================================ +# +# The patch number IS the commit count, so a build names the commit it came +# from and there is never a question about which changes are in a binary +# somebody is holding. Run as RP_L4's pre-build step; also readable by +# pack-dist.ps1, so the package and the exe cannot disagree. +# +# A hardcoded version could not do this: the commit that records "4.12.96" +# is itself commit 96, so the file is stale the moment it is committed. +# +# Usage: powershell -ExecutionPolicy Bypass -File stamp-version.ps1 +# +$ErrorActionPreference = 'Stop' + +# The product line. Bump this by hand when the line moves; the patch +# number after it looks after itself. +$line = '4.12' + +$root = Split-Path -Parent $MyInvocation.MyCommand.Path +$header = Join-Path $root 'RP_L4\rpl4build.h' + +$count = 0 +$commit = 'nogit' +$dirty = 0 + +# +# Every git call goes through cmd so that stderr never reaches PowerShell's +# error stream. Windows PowerShell turns a native command's stderr into +# ErrorRecords, and with $ErrorActionPreference = 'Stop' git's routine +# "LF will be replaced by CRLF" warning is enough to throw - which silently +# skipped the dirty check and stamped every modified build as clean. +# +try { + Push-Location $root + $c = cmd /c "git rev-list --count HEAD 2>NUL" + if ($LASTEXITCODE -eq 0 -and $c) { + $count = [int]$c + $commit = (cmd /c "git rev-parse --short HEAD 2>NUL").Trim() + # + # Tracked modifications only. An untracked scratch file in the tree + # is not in the binary, and marking every build dirty for one would + # make the marker mean nothing. + # + cmd /c "git diff --quiet HEAD -- 2>NUL" + if ($LASTEXITCODE -ne 0) { $dirty = 1 } + } +} catch { + # no git, or not a repository: fall through to the placeholder below +} finally { + Pop-Location +} + +if ($count -eq 0) { + # Built outside the repository (a source drop, say). Say so plainly + # rather than inventing a number that would sort against real ones. + $version = "$line.x" + $long = "$line.x (no repository)" +} else { + $version = "$line.$count" + $long = "$version ($commit$(if ($dirty) { '+' } else { '' }))" +} + +$content = @" +//===========================================================================// +// File: rpl4build.h GENERATED - do not edit, do not commit // +//---------------------------------------------------------------------------// +// Written by stamp-version.ps1 as RP_L4's pre-build step. The patch number // +// is the repository's commit count and the hash beside it names the exact // +// commit, so a running build always says where it came from. A trailing '+' // +// means the tree had uncommitted changes to tracked files when it was built. // +//===========================================================================// + +#pragma once + +#define RP412_BUILD_COUNT $count +#define RP412_BUILD_COMMIT "$commit" +#define RP412_BUILD_DIRTY $dirty +#define RP412_VERSION "$version" +#define RP412_VERSION_LONG "$long" +"@ + +# +# Only rewrite when something actually changed: an unconditional write +# would touch the header on every build and drag RPL4.CPP through a +# recompile each time. +# +$existing = if (Test-Path $header) { [IO.File]::ReadAllText($header) } else { '' } +if ($existing -ne $content) { + [IO.File]::WriteAllText($header, $content, (New-Object System.Text.ASCIIEncoding)) + Write-Host "stamp-version: $long" +} else { + Write-Host "stamp-version: $long (unchanged)" +} + +# +# Explicitly: this runs as a pre-build step, and the last thing above it is +# "git diff --quiet", which exits 1 to mean "there are changes". Letting that +# escape would fail the build on every modified tree - the exact case a +# developer builds in. +# +exit 0