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"