diff --git a/BUILD.md b/BUILD.md index 32d6650..5777104 100644 --- a/BUILD.md +++ b/BUILD.md @@ -66,6 +66,24 @@ 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. +**Test builds expire.** `$expireDays` at the top of the same script is the +shelf life in days (currently **14**, counted from the day it was *built*, +not the day the code was written — rebuilding an old commit gives a usable +binary rather than one born stale). An expired build says so in a dialog, +names its version and expiry date, points at the releases page, and exits +without running. It stops a tester spending an afternoon on something that +was fixed a week ago. + +> ⚠️ **Set `$expireDays = 0` for a real release.** A shipped build that +> expires is a catastrophe, and that one line is what decides it. + +`RP412NOEXPIRY=1` waives the check when an old build has to be run on +purpose, and says so in the log so a waived build is never mistaken for a +current one. It is deliberately **not** listed in `environ.ini` — a bypass +every tester can see is a bypass every tester will use. Negative +`$expireDays` backdates the expiry, which is how the refusal gets tested +without touching the machine's clock. + 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, diff --git a/RP_L4/RPL4.CPP b/RP_L4/RPL4.CPP index c8cbe63..f8a780e 100644 --- a/RP_L4/RPL4.CPP +++ b/RP_L4/RPL4.CPP @@ -42,6 +42,7 @@ #include #include #include +#include // the test-build expiry check #define SPOOL_SIZE 0x600000 @@ -182,6 +183,98 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine // RPL4Environ_Load(); + // + //------------------------------------------------------------------------- + // Test builds have a shelf life. + // + // A tester still racing a fortnight-old binary reports things that were + // fixed a week ago, and the afternoon spent chasing them is gone. So the + // build says plainly that it is out of date and stops, rather than + // running on and being quietly wrong about what it is. + // + // This is a nudge, not a lock: the date comes from the machine's own + // clock, so anyone determined can wind it back, and RP412NOEXPIRY=1 is + // there for us when an old build has to be run on purpose. It is + // deliberately not listed in environ.ini - a bypass every tester can see + // is a bypass every tester will use, and then the build never goes stale + // for the one person it was meant to stop. + // + // $expireDays in stamp-version.ps1 is what sets this, and 0 turns it off + // for a real release. + //------------------------------------------------------------------------- + // +#if RP412_EXPIRES + { + const char *no_expiry = getenv("RP412NOEXPIRY"); + Logical overridden = (no_expiry != NULL && atoi(no_expiry) != 0); + + __time64_t raw_now = _time64(NULL); + struct tm today; + if (!overridden && _localtime64_s(&today, &raw_now) == 0) + { + int now_stamp = + (today.tm_year + 1900) * 10000 + (today.tm_mon + 1) * 100 + today.tm_mday; + int expiry_stamp = + RP412_EXPIRY_YEAR * 10000 + RP412_EXPIRY_MONTH * 100 + RP412_EXPIRY_DAY; + + if (now_stamp > expiry_stamp) + { + DEBUG_STREAM << "Build expired on " << RP412_EXPIRY_TEXT + << " - refusing to run\n" << std::flush; + + char notice[512]; + sprintf(notice, + "This Red Planet test build has expired.\n\n" + " Build %s\n" + " Expired %s\n\n" + "Test builds are good for a fortnight so that nobody spends an " + "afternoon chasing something that was fixed a week ago.\n\n" + "Grab the current one:\n" + "https://gitea.mysticmachines.com/VWE/RP412/releases", + RP412_VERSION_LONG, RP412_EXPIRY_TEXT); + MessageBoxA(NULL, notice, "Red Planet - test build expired", + MB_OK | MB_ICONWARNING | MB_SETFOREGROUND); + return 1; + } + + // + // The last few days get a line in the log, so somebody reading a + // report can see the build was nearly out rather than wondering. + // + struct tm expiry_day; + memset(&expiry_day, 0, sizeof(expiry_day)); + expiry_day.tm_year = RP412_EXPIRY_YEAR - 1900; + expiry_day.tm_mon = RP412_EXPIRY_MONTH - 1; + expiry_day.tm_mday = RP412_EXPIRY_DAY; + // + // The END of the expiry day, because that is the rule the check + // above enforces - the build is good for all of that date and + // refuses the morning after. Anchoring at midday instead would + // report one day fewer than the build actually has left. + // + expiry_day.tm_hour = 23; + expiry_day.tm_min = 59; + expiry_day.tm_sec = 59; + expiry_day.tm_isdst = -1; + __time64_t expiry_time = _mktime64(&expiry_day); + if (expiry_time != (__time64_t) -1) + { + int days_left = (int)((expiry_time - raw_now) / (24 * 60 * 60)); + if (days_left <= 3) + { + DEBUG_STREAM << "Build expires " << RP412_EXPIRY_TEXT << " (" + << days_left << " day(s) left)\n" << std::flush; + } + } + } + else if (overridden) + { + DEBUG_STREAM << "Build expiry (" << RP412_EXPIRY_TEXT + << ") waived by RP412NOEXPIRY\n" << std::flush; + } + } +#endif + DEBUG_STREAM << "L4CONTROLS=" << getenv("L4CONTROLS") << std::endl << std::flush; #ifdef RP412_STEAM diff --git a/pack-dist.ps1 b/pack-dist.ps1 index be0e441..fbbb41d 100644 --- a/pack-dist.ps1 +++ b/pack-dist.ps1 @@ -282,6 +282,16 @@ through several updates stops being offered new options - rpl4.log names any it has not heard of, and deleting the file brings back the fully documented current one. +THIS IS A TEST BUILD AND IT GOES STALE. Two weeks after it was built it +will tell you so and refuse to start, rather than let you spend an +afternoon on something that was fixed the week before. When that happens, +grab the current build: + + https://gitea.mysticmachines.com/VWE/RP412/releases + +Unzip it over this folder - your environ.ini, bindings.txt, pilot.cfg and +mfd_layout.cfg all survive. + Known prototype notes: pods race untextured (the player1-8 skins come from the presets system, not shipped data), and text drawn on the plasma glass may appear rotated. diff --git a/stamp-version.ps1 b/stamp-version.ps1 index 4e1f9a1..b7d004b 100644 --- a/stamp-version.ps1 +++ b/stamp-version.ps1 @@ -18,6 +18,15 @@ $ErrorActionPreference = 'Stop' # 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' @@ -62,6 +71,32 @@ if ($count -eq 0) { $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 // @@ -70,6 +105,9 @@ $content = @" // 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 @@ -79,6 +117,12 @@ $content = @" #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" "@ # @@ -87,11 +131,12 @@ $content = @" # 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" + Write-Host "stamp-version: $long, $note" } else { - Write-Host "stamp-version: $long (unchanged)" + Write-Host "stamp-version: $long, $note (unchanged)" } #