diff --git a/.github/actions/_probe-versioning-fastfail/action.yml b/.github/actions/_probe-versioning-fastfail/action.yml new file mode 100644 index 0000000..baf4b2b --- /dev/null +++ b/.github/actions/_probe-versioning-fastfail/action.yml @@ -0,0 +1,60 @@ +name: "_probe versioning fast-fail" +description: > + THROWAWAY probe for CI_Toolkit ci-versioning H1 fix. Exercises the fast-fail + step's blast radius in isolation: real gh release download of the baseline + IncludedDLLs.txt, and the vendored Assert-VersioningPrerequisites.ps1 resolved + via GITHUB_ACTION_PATH and invoked with -Force:$force under pwsh 7. Assemblies + dir is deliberately empty so every canonical DLL is "missing", which is what + drives the force / deletion-only guards. Not for production use. + +inputs: + force: + required: true + baseline_installer_repo: + required: true + changed_list_path: + description: Path to the ACMRT change list produced by compute-changed-files. + required: true + +runs: + using: composite + steps: + - name: Fast-fail on missing versioning-critical DLLs (probe) + shell: pwsh + env: + GH_TOKEN: ${{ github.token }} + run: | + $baselineRepo = '${{ inputs.baseline_installer_repo }}' + $baselineDir = Join-Path $env:RUNNER_TEMP 'versioning-baseline' + New-Item -ItemType Directory -Force -Path $baselineDir | Out-Null + + $downloaded = $false + try { + $latestTag = gh release list --repo $baselineRepo --limit 1 --json tagName --jq '.[0].tagName' 2>$null + if ($LASTEXITCODE -eq 0 -and $latestTag) { + gh release download $latestTag --repo $baselineRepo ` + --pattern 'IncludedDLLs.txt' ` + --dir $baselineDir 2>$null + if ($LASTEXITCODE -eq 0 -and (Test-Path (Join-Path $baselineDir 'IncludedDLLs.txt'))) { + $downloaded = $true + } + } + } catch { } + + if (-not $downloaded) { + Write-Host "::error::PROBE PRECONDITION FAILED: no IncludedDLLs.txt on $baselineRepo (cannot exercise fast-fail)." + exit 1 + } + Write-Host "Baseline: $baselineRepo@$latestTag" + + # Empty assemblies dir => every canonical expected DLL is missing. + $emptyAsm = Join-Path $env:RUNNER_TEMP 'empty-assemblies' + New-Item -ItemType Directory -Force -Path $emptyAsm | Out-Null + + $force = '${{ inputs.force }}' -eq 'true' + & "$env:GITHUB_ACTION_PATH\scripts\Assert-VersioningPrerequisites.ps1" ` + -BaselineFile (Join-Path $baselineDir 'IncludedDLLs.txt') ` + -AssembliesDir $emptyAsm ` + -ChangedListPath '${{ inputs.changed_list_path }}' ` + -Force:$force + exit $LASTEXITCODE diff --git a/.github/actions/_probe-versioning-fastfail/scripts/Assert-VersioningPrerequisites.ps1 b/.github/actions/_probe-versioning-fastfail/scripts/Assert-VersioningPrerequisites.ps1 new file mode 100644 index 0000000..0989b77 --- /dev/null +++ b/.github/actions/_probe-versioning-fastfail/scripts/Assert-VersioningPrerequisites.ps1 @@ -0,0 +1,60 @@ +[CmdletBinding()] +param( + # IncludedDLLs.txt from the baseline installer's latest published release. + [Parameter(Mandatory)][string]$BaselineFile, + # Folder produced by dependency resolution + primary build. + [Parameter(Mandatory)][string]$AssembliesDir, + # ACMRT change list (deletions excluded) written by compute-changed-files. + # An existing-but-empty file means every matched change in the PR was a + # deletion. Absent (non-PR run) or non-empty means apply the fast-fail. + [string]$ChangedListPath = '', + # Skip the fast-fail and run versioning regardless. Mirrors BHoMBot's + # -force: used when a PR legitimately reduces the produced DLL set. + [switch]$Force +) + +$ErrorActionPreference = 'Stop' + +# Filter to canonical BHoM DLLs. Third-party shared deps (Newtonsoft.Json, WiX +# bundles etc.) legitimately vary between builds; the canonical set is what +# versioning genuinely depends on. Same regex intent as BHoMBot's Versioning.cs:187. +$canonicalRegex = '(?i)(oM|_Engine|_Adapter)\.dll$|(?i)Revit_.+20\d{2}\.dll$' + +$expected = Get-Content $BaselineFile | + ForEach-Object { [System.IO.Path]::GetFileName($_.Trim()) } | + Where-Object { $_ -match $canonicalRegex } | + Sort-Object -Unique + +$present = @(Get-ChildItem $AssembliesDir -Filter '*.dll' -ErrorAction SilentlyContinue | + ForEach-Object { $_.Name }) + +$missing = @($expected | Where-Object { $_ -notin $present }) + +if ($missing.Count -eq 0) { + Write-Host "::notice::Versioning prerequisites present ($($expected.Count) canonical DLLs matched from baseline)." + exit 0 +} + +$missingList = $missing -join ', ' + +if ($Force) { + Write-Host "::warning::Versioning prerequisites missing ($missingList), but force=true, so running versioning anyway." + exit 0 +} + +# Deletion-only guard: mirrors BHoMBot Versioning.cs:209 (csFiles.Any(File.Exists)). +# A PR that only removes source legitimately produces fewer DLLs, so a missing +# DLL is expected: run versioning rather than blocking it here. The check applies +# only when the change list exists (PR run) and is empty (all matched changes +# were deletions); an absent list (non-PR run) or a non-empty list falls through +# to the fast-fail. +if ($ChangedListPath -and (Test-Path $ChangedListPath)) { + $nonDeleted = @(Get-Content $ChangedListPath | Where-Object { $_.Trim() }) + if ($nonDeleted.Count -eq 0) { + Write-Host "::warning::Versioning prerequisites missing ($missingList), but this PR only deletes source files, so fewer DLLs is expected. Skipping fast-fail and running versioning." + exit 0 + } +} + +Write-Host "::error title=Versioning prerequisites missing::The following DLLs are expected (per the last published baseline release) but were not produced by dependency resolution + primary build: $missingList. Fix the upstream failure before re-running versioning." +exit 1 diff --git a/.github/workflows/_probe-versioning-fastfail.yml b/.github/workflows/_probe-versioning-fastfail.yml new file mode 100644 index 0000000..5779403 --- /dev/null +++ b/.github/workflows/_probe-versioning-fastfail.yml @@ -0,0 +1,77 @@ +name: _probe versioning fast-fail + +# THROWAWAY validation harness for the CI_Toolkit ci-versioning H1 fix. +# Runs on PRs that touch this branch's probe files. Delete after validating. + +on: + pull_request: + paths: + - '.github/actions/_probe-versioning-fastfail/**' + - '.github/workflows/_probe-versioning-fastfail.yml' + - 'probe-src/**' + +permissions: + contents: read + +jobs: + probe: + runs-on: windows-latest + strategy: + fail-fast: false + matrix: + include: + - name: missing-fail # canonical DLLs missing, no force, real source change -> fail + force: 'false' + empty: 'false' + expect: failure + - name: force-skip # force bypass -> succeed + force: 'true' + empty: 'false' + expect: success + - name: deletion-only-skip # empty ACMRT list (deletion-only PR) -> succeed + force: 'false' + empty: 'true' + expect: success + steps: + - name: Checkout PR branch + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + + - name: Compute changed files + uses: BuroHappoldEngineeringAdmin/CI_Toolkit/.github/actions/compute-changed-files@develop + with: + patterns: '*.cs' + skip_if_not_pr: 'false' + output_file: changed_versioning_files.txt + + - name: Show / tamper change list + shell: bash + run: | + echo "--- changed_versioning_files.txt (as produced) ---" + cat changed_versioning_files.txt || echo "(absent)" + if [ "${{ matrix.empty }}" = "true" ]; then + : > changed_versioning_files.txt + echo "--- emptied to simulate a deletion-only PR ---" + fi + + - name: Run fast-fail probe + id: probe + continue-on-error: true + uses: ./.github/actions/_probe-versioning-fastfail + with: + force: ${{ matrix.force }} + baseline_installer_repo: BuroHappoldEngineeringSandbox/BHoM_Installer + changed_list_path: ${{ github.workspace }}/changed_versioning_files.txt + + - name: Assert outcome + shell: bash + run: | + got='${{ steps.probe.outcome }}' + want='${{ matrix.expect }}' + echo "leg=${{ matrix.name }} force=${{ matrix.force }} empty=${{ matrix.empty }} outcome=$got expected=$want" + if [ "$got" != "$want" ]; then + echo "::error::leg ${{ matrix.name }}: probe outcome '$got' != expected '$want'" + exit 1 + fi + echo "leg ${{ matrix.name }}: OK" diff --git a/probe-src/Dummy.cs b/probe-src/Dummy.cs new file mode 100644 index 0000000..5606393 --- /dev/null +++ b/probe-src/Dummy.cs @@ -0,0 +1 @@ +namespace Probe { public class Dummy { public int V => 1; } }