build: reject dirty Rayforge source trees
This commit is contained in:
parent
a5a8e4086d
commit
7b817f6339
8 changed files with 54 additions and 6 deletions
|
|
@ -15,7 +15,8 @@ negative cases, cancellation/recovery and synthetic LightBurn imports.
|
||||||
The repository now provides:
|
The repository now provides:
|
||||||
|
|
||||||
- an on-demand source/build entry point that never installs the Windows
|
- an on-demand source/build entry point that never installs the Windows
|
||||||
toolchain;
|
toolchain, rejects modified upstream source and replaces only the validated
|
||||||
|
addon staging directory;
|
||||||
- a non-executing verifier for externally produced Windows artifacts;
|
- a non-executing verifier for externally produced Windows artifacts;
|
||||||
it requires an exact repository-to-manifest-to-ZIP addon file set;
|
it requires an exact repository-to-manifest-to-ZIP addon file set;
|
||||||
- a checksummed acceptance-kit generator containing only allowlisted public
|
- a checksummed acceptance-kit generator containing only allowlisted public
|
||||||
|
|
|
||||||
|
|
@ -39,8 +39,12 @@ toolchain preparation remains a separate, explicitly invoked upstream action
|
||||||
or should happen on a dedicated build runner.
|
or should happen on a dedicated build runner.
|
||||||
|
|
||||||
Before staging, the script rejects transport imports in the addon. The build
|
Before staging, the script rejects transport imports in the addon. The build
|
||||||
manifest records the exact upstream commit and hashes every addon source file,
|
entry point also rejects tracked Rayforge modifications and any unexpected
|
||||||
the portable archive and installer. It does not claim the GUI has been tested.
|
untracked source. It removes and recreates only the validated addon staging
|
||||||
|
directory, preventing stale files from a previous run from entering the
|
||||||
|
bundle. The build manifest records the exact upstream commit and hashes every
|
||||||
|
addon source file, the portable archive and installer. It does not claim the
|
||||||
|
GUI has been tested.
|
||||||
|
|
||||||
Copy the ZIP, installer and JSON manifest together, then verify an externally
|
Copy the ZIP, installer and JSON manifest together, then verify an externally
|
||||||
produced checkpoint without launching either binary:
|
produced checkpoint without launching either binary:
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,12 @@ revision into the ignored `.build/upstreams/` cache only when explicitly
|
||||||
invoked. Its default action prepares source only; it neither installs the
|
invoked. Its default action prepares source only; it neither installs the
|
||||||
Windows toolchain nor starts a build.
|
Windows toolchain nor starts a build.
|
||||||
|
|
||||||
|
Source preparation also verifies the Git worktree state. Tracked changes and
|
||||||
|
unexpected untracked source fail closed. The Windows checkpoint wrapper allows
|
||||||
|
only its known addon staging subtree, validates that path, removes it before
|
||||||
|
copying, and then rechecks the worktree before invoking upstream's build.
|
||||||
|
Ignored upstream build outputs remain outside this source-integrity decision.
|
||||||
|
|
||||||
The repository scripts do not install Windows dependencies. Bundle creation
|
The repository scripts do not install Windows dependencies. Bundle creation
|
||||||
requires the explicit `-BuildWindowsBundle` switch and an already reviewed,
|
requires the explicit `-BuildWindowsBundle` switch and an already reviewed,
|
||||||
prepared runtime. This keeps a multi-gigabyte MSYS2/UCRT64 toolchain out of
|
prepared runtime. This keeps a multi-gigabyte MSYS2/UCRT64 toolchain out of
|
||||||
|
|
|
||||||
|
|
@ -321,7 +321,9 @@ The transport-free portions are now consolidated by the
|
||||||
[M1 no-device acceptance checkpoint](research/m1-no-device-acceptance.md).
|
[M1 no-device acceptance checkpoint](research/m1-no-device-acceptance.md).
|
||||||
It reruns the public suite and leak guard and hashes the validated regression
|
It reruns the public suite and leak guard and hashes the validated regression
|
||||||
reports. The on-demand build entry point fetches pinned source and stages the
|
reports. The on-demand build entry point fetches pinned source and stages the
|
||||||
addon but deliberately refuses to install a missing Windows toolchain.
|
addon but deliberately refuses to install a missing Windows toolchain. It
|
||||||
|
rejects modified upstream source and replaces the exact addon staging tree so
|
||||||
|
stale files cannot survive between builds.
|
||||||
An artifact produced elsewhere can be checked locally without execution: the
|
An artifact produced elsewhere can be checked locally without execution: the
|
||||||
verifier rejects unsafe paths or changed bytes and confirms the pin plus every
|
verifier rejects unsafe paths or changed bytes and confirms the pin plus every
|
||||||
packaged addon source before the manual GUI checklist may begin. It also
|
packaged addon source before the manual GUI checklist may begin. It also
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,8 @@ if (-not $ArtifactRoot) {
|
||||||
}
|
}
|
||||||
|
|
||||||
& (Join-Path $PSScriptRoot "prepare_rayforge_build.ps1") `
|
& (Join-Path $PSScriptRoot "prepare_rayforge_build.ps1") `
|
||||||
-CacheRoot $CacheRoot
|
-CacheRoot $CacheRoot `
|
||||||
|
-AllowStagedPirateRayAddon
|
||||||
if ($LASTEXITCODE -ne 0) {
|
if ($LASTEXITCODE -ne 0) {
|
||||||
throw "Pinned Rayforge source preparation failed"
|
throw "Pinned Rayforge source preparation failed"
|
||||||
}
|
}
|
||||||
|
|
@ -48,6 +49,17 @@ $addonDestination = Join-Path $checkout (
|
||||||
"rayforge\builtin_addons\rayforge-addon-the-pirate-ray-jcz"
|
"rayforge\builtin_addons\rayforge-addon-the-pirate-ray-jcz"
|
||||||
)
|
)
|
||||||
if ($PSCmdlet.ShouldProcess($addonDestination, "Stage no-device addon")) {
|
if ($PSCmdlet.ShouldProcess($addonDestination, "Stage no-device addon")) {
|
||||||
|
$resolvedCheckout = [IO.Path]::GetFullPath($checkout).TrimEnd("\")
|
||||||
|
$resolvedAddonDestination = [IO.Path]::GetFullPath($addonDestination)
|
||||||
|
$expectedAddonDestination = Join-Path $resolvedCheckout (
|
||||||
|
"rayforge\builtin_addons\rayforge-addon-the-pirate-ray-jcz"
|
||||||
|
)
|
||||||
|
if ($resolvedAddonDestination -ne $expectedAddonDestination) {
|
||||||
|
throw "Refusing to replace an unexpected addon staging path"
|
||||||
|
}
|
||||||
|
if (Test-Path -LiteralPath $resolvedAddonDestination) {
|
||||||
|
Remove-Item -LiteralPath $resolvedAddonDestination -Recurse -Force
|
||||||
|
}
|
||||||
New-Item -ItemType Directory -Force -Path $addonDestination | Out-Null
|
New-Item -ItemType Directory -Force -Path $addonDestination | Out-Null
|
||||||
Copy-Item -Path (Join-Path $addonSource "*") `
|
Copy-Item -Path (Join-Path $addonSource "*") `
|
||||||
-Destination $addonDestination -Recurse -Force
|
-Destination $addonDestination -Recurse -Force
|
||||||
|
|
@ -56,6 +68,7 @@ if ($PSCmdlet.ShouldProcess($addonDestination, "Stage no-device addon")) {
|
||||||
$prepareArguments = @{
|
$prepareArguments = @{
|
||||||
CacheRoot = $CacheRoot
|
CacheRoot = $CacheRoot
|
||||||
BuildWindowsBundle = $true
|
BuildWindowsBundle = $true
|
||||||
|
AllowStagedPirateRayAddon = $true
|
||||||
}
|
}
|
||||||
& (Join-Path $PSScriptRoot "prepare_rayforge_build.ps1") @prepareArguments
|
& (Join-Path $PSScriptRoot "prepare_rayforge_build.ps1") @prepareArguments
|
||||||
if ($LASTEXITCODE -ne 0) {
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
[CmdletBinding(SupportsShouldProcess)]
|
[CmdletBinding(SupportsShouldProcess)]
|
||||||
param(
|
param(
|
||||||
[string]$CacheRoot,
|
[string]$CacheRoot,
|
||||||
[switch]$BuildWindowsBundle
|
[switch]$BuildWindowsBundle,
|
||||||
|
[switch]$AllowStagedPirateRayAddon
|
||||||
)
|
)
|
||||||
|
|
||||||
$ErrorActionPreference = "Stop"
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
@ -54,8 +55,23 @@ if ($verifiedCommit -ne $manifest.commit) {
|
||||||
throw "Rayforge pin mismatch: expected $($manifest.commit), got $verifiedCommit"
|
throw "Rayforge pin mismatch: expected $($manifest.commit), got $verifiedCommit"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$statusLines = @(& git -C $checkout status --porcelain=v1 --untracked-files=all)
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
throw "Could not inspect the Rayforge checkout state"
|
||||||
|
}
|
||||||
|
$allowedAddonPrefix = "?? rayforge/builtin_addons/rayforge-addon-the-pirate-ray-jcz/"
|
||||||
|
$unexpectedChanges = @(
|
||||||
|
$statusLines | Where-Object {
|
||||||
|
$_ -and (-not $AllowStagedPirateRayAddon -or -not $_.StartsWith($allowedAddonPrefix))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if ($unexpectedChanges.Count -gt 0) {
|
||||||
|
throw "Rayforge checkout contains unreviewed tracked or untracked changes"
|
||||||
|
}
|
||||||
|
|
||||||
Write-Output "Rayforge source ready at $checkout"
|
Write-Output "Rayforge source ready at $checkout"
|
||||||
Write-Output "Verified commit: $verifiedCommit"
|
Write-Output "Verified commit: $verifiedCommit"
|
||||||
|
Write-Output "Verified checkout state: clean except explicitly staged PirateRay addon"
|
||||||
|
|
||||||
if (-not $BuildWindowsBundle) {
|
if (-not $BuildWindowsBundle) {
|
||||||
Write-Output "No dependencies were installed and no build was started."
|
Write-Output "No dependencies were installed and no build was started."
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,9 @@ class M1AcceptancePipelineTests(unittest.TestCase):
|
||||||
self.assertIn("schema_version = 2", source)
|
self.assertIn("schema_version = 2", source)
|
||||||
self.assertIn("Rayforge-LICENSE.txt", source)
|
self.assertIn("Rayforge-LICENSE.txt", source)
|
||||||
self.assertIn("license_files", source)
|
self.assertIn("license_files", source)
|
||||||
|
self.assertIn("GetFullPath", source)
|
||||||
|
self.assertIn("Remove-Item -LiteralPath $resolvedAddonDestination", source)
|
||||||
|
self.assertIn("AllowStagedPirateRayAddon", source)
|
||||||
|
|
||||||
def test_acceptance_has_no_hardware_commands(self):
|
def test_acceptance_has_no_hardware_commands(self):
|
||||||
source = ACCEPTANCE.read_text(encoding="utf-8").lower()
|
source = ACCEPTANCE.read_text(encoding="utf-8").lower()
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,9 @@ class UpstreamBuildReferenceTests(unittest.TestCase):
|
||||||
self.assertNotIn("InstallWindowsDependencies", script)
|
self.assertNotIn("InstallWindowsDependencies", script)
|
||||||
self.assertIn("BuildWindowsBundle", script)
|
self.assertIn("BuildWindowsBundle", script)
|
||||||
self.assertIn("No dependencies were installed", script)
|
self.assertIn("No dependencies were installed", script)
|
||||||
|
self.assertIn("status --porcelain=v1 --untracked-files=all", script)
|
||||||
|
self.assertIn("unreviewed tracked or untracked changes", script)
|
||||||
|
self.assertIn("AllowStagedPirateRayAddon", script)
|
||||||
|
|
||||||
def test_prepare_script_has_no_hardware_path(self):
|
def test_prepare_script_has_no_hardware_path(self):
|
||||||
script = PREPARE.read_text(encoding="utf-8").lower()
|
script = PREPARE.read_text(encoding="utf-8").lower()
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue