From a12fd088eceb40eca62090e1f1274601dc5f8c44 Mon Sep 17 00:00:00 2001 From: Fedor Chelnokov Date: Thu, 23 Apr 2026 14:59:59 +0300 Subject: [PATCH 1/2] ci(windows): add DLL-not-found diagnostic steps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit STATUS_DLL_NOT_FOUND (exit code -1073741515 / 0xC0000135) at MeshViewer.exe launch kills the Run Start-and-Exit Tests step with zero useful output — the Windows loader aborts before main() and CI sees only a bare ##[error]Process completed with exit code ... Observed on the zlib-ng branch run 24802707812, job 72596857457 (msvc-2019 Debug CMake with the x64-windows-meshlib-iterator-debug triplet): vcpkg installed zlib-ng successfully but libz-ng.dll didn't end up next to MeshViewer.exe. Three other Windows legs in the same run with the default triplet succeeded, so the gap is specifically in the iterator-debug triplet's DLL-copy chain. Add two unconditional diagnostic steps so the next run of any Windows job self-documents the state the loader will see: 1. After vcpkg-integrate-install, "Diagnostic — vcpkg installed tree": lists the contents of C:\vcpkg\installed\\{bin,debug\bin,lib,debug\lib} so we see which DLLs vcpkg actually installed and under what names (e.g. libz-ng.dll vs libz-ngd.dll). Confirms or rules out "vcpkg didn't install the package" as the cause. 2. Right before "Run Start-and-Exit Tests", "Diagnostic — output bin DLL inventory + MRMesh imports": - Lists all .dll/.exe under source\x64\\ so we see which DLLs were copied next to MeshViewer.exe - Runs dumpbin /dependents on MeshViewer.exe, MRMesh.dll, MRTest.exe so we see which DLL names the loader is actually looking for at process start - Dumps the PATH so we can tell whether the vcpkg install dir would be a fallback lookup location Together, these three data points are enough to turn any DLL-not-found failure into a one-line diagnosis. Both steps have `if: always()` and `continue-on-error: true`, so they run even when the main Build step failed (often when you need the diagnostic most) and never mask a real failure themselves. Net cost: ~30 lines of pwsh output per Windows job when everything works; ~100 lines when a DLL is missing. Trivial relative to the multi-gigabyte Windows CI logs already emitted. --- .github/workflows/build-test-windows.yml | 70 ++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/.github/workflows/build-test-windows.yml b/.github/workflows/build-test-windows.yml index 43cfe15ddc03..4b875605f16c 100644 --- a/.github/workflows/build-test-windows.yml +++ b/.github/workflows/build-test-windows.yml @@ -111,6 +111,30 @@ jobs: working-directory: C:\vcpkg run: C:\vcpkg\vcpkg.exe integrate install + # Diagnostic step: inventory the vcpkg installed tree so DLL-not-found + # failures later in the build can be traced back to whether the DLL was + # (a) never installed, (b) installed under an unexpected name, or + # (c) installed fine but not copied to the build output. + - name: Diagnostic — vcpkg installed tree + if: ${{ always() }} + shell: pwsh + continue-on-error: true + run: | + $triplet = "${{ matrix.vcpkg_triplet || 'x64-windows-meshlib' }}" + Write-Host "=== Triplet: $triplet ===" + foreach ($sub in 'bin','debug\bin','lib','debug\lib') { + $dir = "C:\vcpkg\installed\$triplet\$sub" + Write-Host "" + Write-Host "--- $dir ---" + if (Test-Path $dir) { + Get-ChildItem $dir -File -ErrorAction SilentlyContinue | + Select-Object Name, @{N='SizeKB';E={[int]($_.Length/1KB)}} | + Sort-Object Name | Format-Table -AutoSize + } else { + Write-Host "(directory not present)" + } + } + - name: Restore CUDA Cache uses: actions/cache@v5 id: cuda-cache @@ -213,6 +237,52 @@ jobs: call "${{matrix.vc-path}}\Common7\Tools\VsDevCmd.bat" -arch=amd64 ${{ fromJSON('["", "-vcvars_ver=14.2"]')[matrix.cxx_compiler == 'msvc-2019'] }} call ./scripts/mrbind/generate_win.bat -B --trace MODE=none VS_MODE=${{matrix.config}} + # Diagnostic step: before launching MeshViewer.exe, dump the state the + # Windows loader will actually see. Prints the DLLs present next to the + # .exe, the import tables of the key binaries (so we know which DLL + # names the loader wants), and the PATH at launch. Lets DLL-not-found + # failures self-diagnose from the CI log. + - name: Diagnostic — output bin DLL inventory + MRMesh imports + if: ${{ always() }} + shell: pwsh + continue-on-error: true + working-directory: source\x64\${{ matrix.config }} + run: | + Write-Host "=== DLLs / EXEs in $(Get-Location) ===" + Get-ChildItem . -File -Include *.dll,*.exe -Recurse -ErrorAction SilentlyContinue | + Select-Object Name, @{N='SizeKB';E={[int]($_.Length/1KB)}} | + Sort-Object Name | Format-Table -AutoSize + + # Locate dumpbin.exe via the VC path hint passed in via matrix, then + # fall back to whatever is on PATH (Visual Studio Integration step + # earlier populates MSBuild PATH, not VC bin). + $vcpath = '${{ matrix.vc-path }}' + $dumpbin = (Get-ChildItem "$vcpath\VC\Tools\MSVC\*\bin\Hostx64\x64\dumpbin.exe" -ErrorAction SilentlyContinue | + Select-Object -First 1).FullName + if (-not $dumpbin) { + $dumpbin = (Get-Command dumpbin.exe -ErrorAction SilentlyContinue).Source + } + + if ($dumpbin) { + Write-Host "" + Write-Host "Using dumpbin at: $dumpbin" + foreach ($target in 'MeshViewer.exe','MRMesh.dll','MRTest.exe') { + if (Test-Path $target) { + Write-Host "" + Write-Host "=== Import table of $target (dumpbin /dependents) ===" + & $dumpbin /dependents $target | + Where-Object { $_ -match '\.dll$' } | + ForEach-Object { " " + $_.Trim() } + } + } + } else { + Write-Warning "dumpbin.exe not found; skipping import-table dump" + } + + Write-Host "" + Write-Host "=== PATH at diagnostic step ===" + $env:PATH -split ';' | ForEach-Object { " $_" } + - name: Run Start-and-Exit Tests timeout-minutes: 3 working-directory: source\x64\${{ matrix.config }} From 3a6d2a12a130890295bf0bf8265d9026e32d64bc Mon Sep 17 00:00:00 2001 From: Fedor Chelnokov Date: Fri, 24 Apr 2026 13:38:04 +0300 Subject: [PATCH 2/2] ci(windows): extract diagnostic PowerShell into scripts/diagnostics/ Moves the two inline diagnostic blocks added to build-test-windows.yml into dedicated .ps1 files under scripts/diagnostics/, shrinking the workflow by ~60 lines while keeping the exact same behavior. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/build-test-windows.yml | 61 +-------------------- scripts/diagnostics/windows-bin-imports.ps1 | 45 +++++++++++++++ scripts/diagnostics/windows-vcpkg-tree.ps1 | 22 ++++++++ 3 files changed, 69 insertions(+), 59 deletions(-) create mode 100644 scripts/diagnostics/windows-bin-imports.ps1 create mode 100644 scripts/diagnostics/windows-vcpkg-tree.ps1 diff --git a/.github/workflows/build-test-windows.yml b/.github/workflows/build-test-windows.yml index 4b875605f16c..2a83cea31c98 100644 --- a/.github/workflows/build-test-windows.yml +++ b/.github/workflows/build-test-windows.yml @@ -111,29 +111,11 @@ jobs: working-directory: C:\vcpkg run: C:\vcpkg\vcpkg.exe integrate install - # Diagnostic step: inventory the vcpkg installed tree so DLL-not-found - # failures later in the build can be traced back to whether the DLL was - # (a) never installed, (b) installed under an unexpected name, or - # (c) installed fine but not copied to the build output. - name: Diagnostic — vcpkg installed tree if: ${{ always() }} shell: pwsh continue-on-error: true - run: | - $triplet = "${{ matrix.vcpkg_triplet || 'x64-windows-meshlib' }}" - Write-Host "=== Triplet: $triplet ===" - foreach ($sub in 'bin','debug\bin','lib','debug\lib') { - $dir = "C:\vcpkg\installed\$triplet\$sub" - Write-Host "" - Write-Host "--- $dir ---" - if (Test-Path $dir) { - Get-ChildItem $dir -File -ErrorAction SilentlyContinue | - Select-Object Name, @{N='SizeKB';E={[int]($_.Length/1KB)}} | - Sort-Object Name | Format-Table -AutoSize - } else { - Write-Host "(directory not present)" - } - } + run: ./scripts/diagnostics/windows-vcpkg-tree.ps1 -Triplet "${{ matrix.vcpkg_triplet || 'x64-windows-meshlib' }}" - name: Restore CUDA Cache uses: actions/cache@v5 @@ -237,51 +219,12 @@ jobs: call "${{matrix.vc-path}}\Common7\Tools\VsDevCmd.bat" -arch=amd64 ${{ fromJSON('["", "-vcvars_ver=14.2"]')[matrix.cxx_compiler == 'msvc-2019'] }} call ./scripts/mrbind/generate_win.bat -B --trace MODE=none VS_MODE=${{matrix.config}} - # Diagnostic step: before launching MeshViewer.exe, dump the state the - # Windows loader will actually see. Prints the DLLs present next to the - # .exe, the import tables of the key binaries (so we know which DLL - # names the loader wants), and the PATH at launch. Lets DLL-not-found - # failures self-diagnose from the CI log. - name: Diagnostic — output bin DLL inventory + MRMesh imports if: ${{ always() }} shell: pwsh continue-on-error: true working-directory: source\x64\${{ matrix.config }} - run: | - Write-Host "=== DLLs / EXEs in $(Get-Location) ===" - Get-ChildItem . -File -Include *.dll,*.exe -Recurse -ErrorAction SilentlyContinue | - Select-Object Name, @{N='SizeKB';E={[int]($_.Length/1KB)}} | - Sort-Object Name | Format-Table -AutoSize - - # Locate dumpbin.exe via the VC path hint passed in via matrix, then - # fall back to whatever is on PATH (Visual Studio Integration step - # earlier populates MSBuild PATH, not VC bin). - $vcpath = '${{ matrix.vc-path }}' - $dumpbin = (Get-ChildItem "$vcpath\VC\Tools\MSVC\*\bin\Hostx64\x64\dumpbin.exe" -ErrorAction SilentlyContinue | - Select-Object -First 1).FullName - if (-not $dumpbin) { - $dumpbin = (Get-Command dumpbin.exe -ErrorAction SilentlyContinue).Source - } - - if ($dumpbin) { - Write-Host "" - Write-Host "Using dumpbin at: $dumpbin" - foreach ($target in 'MeshViewer.exe','MRMesh.dll','MRTest.exe') { - if (Test-Path $target) { - Write-Host "" - Write-Host "=== Import table of $target (dumpbin /dependents) ===" - & $dumpbin /dependents $target | - Where-Object { $_ -match '\.dll$' } | - ForEach-Object { " " + $_.Trim() } - } - } - } else { - Write-Warning "dumpbin.exe not found; skipping import-table dump" - } - - Write-Host "" - Write-Host "=== PATH at diagnostic step ===" - $env:PATH -split ';' | ForEach-Object { " $_" } + run: ${{ github.workspace }}\scripts\diagnostics\windows-bin-imports.ps1 -VcPath "${{ matrix.vc-path }}" - name: Run Start-and-Exit Tests timeout-minutes: 3 diff --git a/scripts/diagnostics/windows-bin-imports.ps1 b/scripts/diagnostics/windows-bin-imports.ps1 new file mode 100644 index 000000000000..50db742b2bbf --- /dev/null +++ b/scripts/diagnostics/windows-bin-imports.ps1 @@ -0,0 +1,45 @@ +# Before launching MeshViewer.exe, dump the state the Windows loader will +# actually see. Prints the DLLs present in the current directory, the +# import tables of the key binaries (so we know which DLL names the loader +# wants), and the PATH at launch. Lets DLL-not-found failures self-diagnose +# from the CI log. +# +# Run from the directory containing MeshViewer.exe / MRMesh.dll / MRTest.exe. +[CmdletBinding()] +param( + [Parameter(Mandatory = $true)][string]$VcPath +) + +Write-Host "=== DLLs / EXEs in $(Get-Location) ===" +Get-ChildItem . -File -Include *.dll,*.exe -Recurse -ErrorAction SilentlyContinue | + Select-Object Name, @{N='SizeKB';E={[int]($_.Length/1KB)}} | + Sort-Object Name | Format-Table -AutoSize + +# Locate dumpbin.exe via the VC path hint, then fall back to whatever is on +# PATH (Visual Studio Integration step earlier populates MSBuild PATH, not +# VC bin). +$dumpbin = (Get-ChildItem "$VcPath\VC\Tools\MSVC\*\bin\Hostx64\x64\dumpbin.exe" -ErrorAction SilentlyContinue | + Select-Object -First 1).FullName +if (-not $dumpbin) { + $dumpbin = (Get-Command dumpbin.exe -ErrorAction SilentlyContinue).Source +} + +if ($dumpbin) { + Write-Host "" + Write-Host "Using dumpbin at: $dumpbin" + foreach ($target in 'MeshViewer.exe','MRMesh.dll','MRTest.exe') { + if (Test-Path $target) { + Write-Host "" + Write-Host "=== Import table of $target (dumpbin /dependents) ===" + & $dumpbin /dependents $target | + Where-Object { $_ -match '\.dll$' } | + ForEach-Object { " " + $_.Trim() } + } + } +} else { + Write-Warning "dumpbin.exe not found; skipping import-table dump" +} + +Write-Host "" +Write-Host "=== PATH at diagnostic step ===" +$env:PATH -split ';' | ForEach-Object { " $_" } diff --git a/scripts/diagnostics/windows-vcpkg-tree.ps1 b/scripts/diagnostics/windows-vcpkg-tree.ps1 new file mode 100644 index 000000000000..567aa193adfb --- /dev/null +++ b/scripts/diagnostics/windows-vcpkg-tree.ps1 @@ -0,0 +1,22 @@ +# Inventory the vcpkg installed tree so DLL-not-found failures later in the +# build can be traced back to whether the DLL was (a) never installed, +# (b) installed under an unexpected name, or (c) installed fine but not +# copied to the build output. +[CmdletBinding()] +param( + [Parameter(Mandatory = $true)][string]$Triplet +) + +Write-Host "=== Triplet: $Triplet ===" +foreach ($sub in 'bin', 'debug\bin', 'lib', 'debug\lib') { + $dir = "C:\vcpkg\installed\$Triplet\$sub" + Write-Host "" + Write-Host "--- $dir ---" + if (Test-Path $dir) { + Get-ChildItem $dir -File -ErrorAction SilentlyContinue | + Select-Object Name, @{N='SizeKB';E={[int]($_.Length/1KB)}} | + Sort-Object Name | Format-Table -AutoSize + } else { + Write-Host "(directory not present)" + } +}