From 9f822fd12525233c02050fdd5b8bb4a5a98dd5c3 Mon Sep 17 00:00:00 2001
From: Zaldaryon <273555259+Zaldaryon@users.noreply.github.com>
Date: Wed, 23 Sep 2026 17:15:03 -0300
Subject: [PATCH] fix(bootstrap): reject incomplete decompile cache (#116)
---
.github/workflows/ci-platform-bootstrap.yml | 8 ++++++
scripts/bootstrap.ps1 | 30 ++++++++++++++-------
scripts/bootstrap.sh | 19 ++++++++++---
3 files changed, 45 insertions(+), 12 deletions(-)
diff --git a/.github/workflows/ci-platform-bootstrap.yml b/.github/workflows/ci-platform-bootstrap.yml
index 89b9fd24..c564082f 100644
--- a/.github/workflows/ci-platform-bootstrap.yml
+++ b/.github/workflows/ci-platform-bootstrap.yml
@@ -80,6 +80,10 @@ jobs:
restore-keys: |
nuget-windows-
+ - name: Seed incomplete decompile cache
+ shell: bash
+ run: mkdir -p build/snapshot/VintagestoryLib
+
- name: Bootstrap
timeout-minutes: 20
shell: pwsh
@@ -368,6 +372,10 @@ jobs:
restore-keys: |
nuget-linux-
+ - name: Seed incomplete decompile cache
+ shell: bash
+ run: mkdir -p build/snapshot/VintagestoryLib
+
- name: Bootstrap
timeout-minutes: 20
shell: bash
diff --git a/scripts/bootstrap.ps1 b/scripts/bootstrap.ps1
index 9d461879..67ef1bae 100644
--- a/scripts/bootstrap.ps1
+++ b/scripts/bootstrap.ps1
@@ -769,25 +769,37 @@ try {
if (-not $dllPath) { Write-Warning "Skipping $dllBase.dll (not found)"; continue }
$out = Join-Path $snapshotDir $dllBase
- if (-not (Test-Path $out) -or $Refresh) {
- $verLine = Invoke-NativeStep { & ilspycmd --version 2>$null | Select-Object -First 1 }
+ $manifest = Join-Path (Join-Path $snapshotDir '.decompile-manifests') "$dllBase.manifest"
+ $dllHash = (Get-FileHash -LiteralPath $dllPath.FullName -Algorithm SHA256).Hash
+ $verLine = Invoke-NativeStep { & ilspycmd --version 2>$null | Select-Object -First 1 }
+ $expectedManifest = "$dllHash`n$verLine"
+
+ $cachedProjects = @(Get-ChildItem -Path $out -Filter '*.csproj' -File -ErrorAction SilentlyContinue)
+ $cachedSource = @(Get-ChildItem -Path $out -Recurse -Filter '*.cs' -File -ErrorAction SilentlyContinue | Select-Object -First 1)
+ $cacheValid = $cachedProjects.Count -gt 0 -and $cachedSource.Count -gt 0 -and (Test-Path -LiteralPath $manifest -PathType Leaf)
+ if ($cacheValid) {
+ $cacheValid = (Get-Content -LiteralPath $manifest -Raw).TrimEnd() -eq $expectedManifest
+ }
+
+ if (-not $cacheValid -or $Refresh) {
Write-Host "Decompiling $dllBase.dll with $verLine"
if (Test-Path $out) { Remove-Item -Recurse -Force $out }
New-Item -ItemType Directory -Force -Path $out | Out-Null
Invoke-NativeStep { ilspycmd $dllPath.FullName --project -o $out 2>$null | Out-Null }
- # Don't trust ilspycmd's exit code alone: it has a known bug
- # (icsharpcode/ILSpy#3101) where it reports failure via a bogus
- # "not using the latest version" self-check even after a fully
- # successful decompile. Verify the real artifact instead.
+ # ILSpy can report a spurious version warning after successful output;
+ # validate both the project and source before stamping this cache.
$producedProjects = @(Get-ChildItem -Path $out -Filter '*.csproj' -File -ErrorAction SilentlyContinue)
- if ($producedProjects.Count -eq 0) {
- throw "ilspycmd produced no .csproj for $dllBase.dll (exit code $LASTEXITCODE). Delete $out and retry, or reinstall ilspycmd."
+ $producedSource = @(Get-ChildItem -Path $out -Recurse -Filter '*.cs' -File -ErrorAction SilentlyContinue | Select-Object -First 1)
+ if ($producedProjects.Count -eq 0 -or $producedSource.Count -eq 0) {
+ throw "ilspycmd produced an incomplete source tree for $dllBase.dll (exit code $LASTEXITCODE). Delete $out and retry, or reinstall ilspycmd."
}
$producedProjects | ForEach-Object {
Update-FileInPlace $_.FullName { param($t) $t -creplace '15\.0', 'latest' }
}
+ $manifestDirectory = Split-Path -Parent $manifest
+ New-Item -ItemType Directory -Force -Path $manifestDirectory | Out-Null
+ [System.IO.File]::WriteAllText($manifest, $expectedManifest, [System.Text.Encoding]::UTF8)
}
-
Convert-ToLf $out
Copy-TreeFresh $out (Join-Path $repoRoot $workDir)
Convert-ToLf (Join-Path $repoRoot $workDir)
diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh
index b85fba7b..d91b09ed 100644
--- a/scripts/bootstrap.sh
+++ b/scripts/bootstrap.sh
@@ -509,12 +509,25 @@ for entry in "${decompile_targets[@]}"; do
fi
out="$snapshot_dir/$dll_base"
- if [[ ! -d "$out" || "$refresh" == "1" ]]; then
- echo "Decompiling $dll_base.dll with $(ilspycmd --version | head -1)"
+ manifest="$snapshot_dir/.decompile-manifests/$dll_base.manifest"
+ dll_hash="$(hash_files "$dll_path" | cut -d ' ' -f1)"
+ ilspy_version="$(ilspycmd --version | head -1)"
+ if [[ "$refresh" == "1" || ! -d "$out" || ! -f "$manifest" ]] ||
+ ! cmp -s "$manifest" <(printf '%s\n' "$dll_hash" "$ilspy_version"); then
+ echo "Decompiling $dll_base.dll with $ilspy_version"
rm -rf "$out"
mkdir -p "$out"
ilspycmd "$dll_path" --project -o "$out" >/dev/null 2>&1
- find "$out" -maxdepth 1 -name '*.csproj' -exec perl -0pi -e 's#15\.0#latest#g' {} \;
+ if [[ ! -f "$out/$dll_base.csproj" ]]; then
+ echo "ILSpy produced no project for $dll_base.dll; the decompile cache will not be reused." >&2
+ exit 1
+ fi
+ if [[ -z "$(find "$out" -type f -name '*.cs' -print -quit)" ]]; then
+ echo "ILSpy produced no source files for $dll_base.dll; the decompile cache will not be reused." >&2
+ exit 1
+ fi
+ mkdir -p "$(dirname "$manifest")"
+ printf '%s\n' "$dll_hash" "$ilspy_version" > "$manifest"
fi
normalize_lf "$out"