# ============================================================================ # 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' # How many days a build stays good for. Test builds go stale so nobody is # still racing a fortnight-old binary and reporting things that were fixed # a week ago - the expired build says so and stops, rather than quietly # wasting everyone's afternoon. # # SET THIS TO 0 FOR A REAL RELEASE. A shipped build that expires is a # catastrophe, and this is the one line that decides it. $expireDays = 14 $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 { '' }))" } # # Expiry, from the day it was BUILT rather than the day the code was # written: rebuilding an old commit to chase something should give a # usable binary, not one that was born stale. # # The date is what makes this header change from one day to the next, so # the first build of each day recompiles RPL4.CPP and the rest of that # day's builds do not. One file, a couple of seconds. # # Only 0 means "never". A NEGATIVE count backdates the expiry, which is how # the refusal gets tested without touching the machine's clock. if ($expireDays -ne 0) { $expiry = (Get-Date).Date.AddDays($expireDays) $expires = 1 $expiryY = $expiry.Year $expiryM = $expiry.Month $expiryD = $expiry.Day $expiryTxt = $expiry.ToString('d MMMM yyyy', [Globalization.CultureInfo]::InvariantCulture) } else { $expires = 0 $expiryY = 0 $expiryM = 0 $expiryD = 0 $expiryTxt = 'never' } $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. // // // // EXPIRES is the test-build shelf life - see \$expireDays in the script. A // // release must be built with it at 0. // //===========================================================================// #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" #define RP412_EXPIRES $expires #define RP412_EXPIRY_YEAR $expiryY #define RP412_EXPIRY_MONTH $expiryM #define RP412_EXPIRY_DAY $expiryD #define RP412_EXPIRY_TEXT "$expiryTxt" "@ # # 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 { '' } $note = if ($expires) { "expires $expiryTxt" } else { "no expiry" } if ($existing -ne $content) { [IO.File]::WriteAllText($header, $content, (New-Object System.Text.ASCIIEncoding)) Write-Host "stamp-version: $long, $note" } else { Write-Host "stamp-version: $long, $note (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