From a2bf2ea4a0fc3ce2897334c10eb400eac5b31ec3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Wed, 5 Aug 2026 07:29:11 +0200 Subject: [PATCH 1/2] fix(ci): satisfy the phantom xml2s.lib the LLVM Windows tarball demands LLVM's official Windows release script builds a static libxml2 into a scratch directory and points cmake at it with -DLLVM_ENABLE_LIBXML2=FORCE_ON -DLIBXML2_LIBRARIES=%libxmldir%/lib/libxml2s.lib. %libxmldir% is never installed, so the published clang+llvm-*-pc-windows-msvc tarball carries the dependency but not the library. llvm-config --system-libs --link-static reports xml2s.lib, llvm-sys forwards every system lib verbatim with no knob to filter one out, and link.exe dies with LNK1181 before resolving a symbol. Synthesize an empty archive at the LLVM libdir when llvm-config reports xml2s.lib AND the libdir lacks it. It is a name dependency, not a symbol dependency: libxml2 is reachable only from LLVMWindowsManifest, which the LLVM-C surface inkwell drives never touches, and rustc bundles the component archives into libllvm_sys.rlib where link.exe pulls members lazily. If that stops being true the link fails loudly with LNK2019 rather than silently dropping manifest support. Checking both conditions makes the workaround self-deleting once a release ships or stops reporting the library. Latent since #7353 made the in-process LLVM backend the default and statically linked, not caused by #7388 (which touches only the Linux arm). It became visible when #7393's concurrency group let gc-native-roots.yml's windows-latest arm reach a runner for the first time. Fixing it in the composite action also unblocks test.yml's windows-build. Claude-Session: https://claude.ai/code/session_019EHcmXKArA7m42SihYCcgH --- .github/actions/setup-llvm22/action.yml | 67 +++++++++++++++++++ changelog.d/PLACEHOLDER-windows-xml2s-stub.md | 7 ++ 2 files changed, 74 insertions(+) create mode 100644 changelog.d/PLACEHOLDER-windows-xml2s-stub.md diff --git a/.github/actions/setup-llvm22/action.yml b/.github/actions/setup-llvm22/action.yml index 89e5e097e8..a1d1b1b893 100644 --- a/.github/actions/setup-llvm22/action.yml +++ b/.github/actions/setup-llvm22/action.yml @@ -97,4 +97,71 @@ runs: tar -xf "$env:RUNNER_TEMP\llvm.tar.xz" -C C:\llvm --strip-components=1 $v = & "C:\llvm\bin\llvm-config.exe" --version if (-not $v.StartsWith("22.")) { Write-Error "LLVM is not 22.x ($v)"; exit 1 } + + # ── libxml2: the release links one it does not ship ───────────────── + # + # LLVM's own Windows release script (llvm/utils/release/ + # build_llvm_release.bat) builds a STATIC libxml2 into a scratch + # directory and points cmake at it: + # + # -DLLVM_ENABLE_LIBXML2=FORCE_ON + # -DLIBXML2_LIBRARIES=%libxmldir%/lib/libxml2s.lib + # + # %libxmldir% is never installed, so the published tarball carries the + # dependency but not the library. `llvm-config --system-libs + # --link-static` duly reports `xml2s.lib`; llvm-sys forwards every + # system lib verbatim (build.rs `get_system_libraries`, emitted as + # cargo:rustc-link-lib) and there is no env knob to filter one out — + # LLVM_SYS_221_* offers PREFIX, IGNORE_BLOCKLIST, STRICT_VERSIONING, + # NO_CLEAN_CFLAGS, USE_DEBUG_MSVCRT and FFI_WORKAROUND, and nothing + # else. So `xml2s.lib` lands on the link.exe command line and every + # Windows job that links perry.exe dies with + # + # LINK : fatal error LNK1181: cannot open input file 'xml2s.lib' + # + # before the linker ever resolves a symbol. This became fatal when + # #7353 made the in-process LLVM backend the default and statically + # linked; it is not specific to any one workflow (test.yml's + # windows-build and gc-native-roots.yml's PE arm fail identically). + # + # It is a NAME dependency, not a symbol dependency. Inside LLVM, + # libxml2 is reachable only from LLVMWindowsManifest + # (WindowsManifestMerger.cpp), which serves lld-link and llvm-mt; the + # LLVM-C surface inkwell drives never touches it, and rustc bundles + # the LLVM component archives into libllvm_sys.rlib where link.exe + # pulls members lazily. Satisfying the input with an empty archive is + # therefore sufficient — and if that ever stops being true the link + # fails LOUDLY with LNK2019 unresolved externals rather than quietly + # dropping manifest support. + # + # Both conditions are checked, so this self-deletes: when a future + # release either stops reporting libxml2 or starts shipping the + # library, the block becomes a no-op instead of fabricating over it. + $sysLibs = (& "C:\llvm\bin\llvm-config.exe" --system-libs --link-static) -join ' ' + $libDir = ((& "C:\llvm\bin\llvm-config.exe" --libdir) -join '').Trim() + $xml2 = Join-Path $libDir 'xml2s.lib' + if (($sysLibs -match 'xml2s\.lib') -and -not (Test-Path -LiteralPath $xml2)) { + Write-Host "llvm-config reports xml2s.lib but $libDir does not ship it; synthesizing an empty archive" + foreach ($tool in @("C:\llvm\bin\clang-cl.exe", "C:\llvm\bin\llvm-lib.exe")) { + if (-not (Test-Path -LiteralPath $tool)) { + Write-Error "$tool missing from the LLVM tarball — cannot synthesize xml2s.lib"; exit 1 + } + } + # One exported symbol, never referenced: it keeps the archive's + # symbol table non-empty so neither llvm-lib nor link.exe has to + # cope with a degenerate zero-member library. + $stubC = Join-Path $env:RUNNER_TEMP 'xml2s_stub.c' + $stubObj = Join-Path $env:RUNNER_TEMP 'xml2s_stub.obj' + Set-Content -LiteralPath $stubC -Encoding ascii -Value 'int perry_xml2s_stub(void) { return 0; }' + & "C:\llvm\bin\clang-cl.exe" /nologo /c $stubC "/Fo$stubObj" + if ($LASTEXITCODE -ne 0 -or -not (Test-Path -LiteralPath $stubObj)) { + Write-Error "failed to compile the xml2s.lib stub object"; exit 1 + } + & "C:\llvm\bin\llvm-lib.exe" "/OUT:$xml2" $stubObj + if ($LASTEXITCODE -ne 0 -or -not (Test-Path -LiteralPath $xml2)) { + Write-Error "failed to archive $xml2"; exit 1 + } + Write-Host "wrote $xml2" + } + "LLVM_SYS_221_PREFIX=C:\llvm" | Out-File -FilePath $env:GITHUB_ENV -Append diff --git a/changelog.d/PLACEHOLDER-windows-xml2s-stub.md b/changelog.d/PLACEHOLDER-windows-xml2s-stub.md new file mode 100644 index 0000000000..5dabb7567a --- /dev/null +++ b/changelog.d/PLACEHOLDER-windows-xml2s-stub.md @@ -0,0 +1,7 @@ +### Fixed + +- **Windows CI could not link `perry.exe`: `LNK1181: cannot open input file 'xml2s.lib'`.** Every Windows job that links the compiler died at the link step. `xml2s.lib` is a phantom dependency baked into LLVM's *official* Windows release, not anything Perry asks for. LLVM's own release script (`llvm/utils/release/build_llvm_release.bat`) builds a static libxml2 into a scratch directory and points cmake at it with `-DLLVM_ENABLE_LIBXML2=FORCE_ON -DLIBXML2_LIBRARIES=%libxmldir%/lib/libxml2s.lib`; `%libxmldir%` is never installed, so the published `clang+llvm-*-pc-windows-msvc` tarball carries the dependency but not the library. `llvm-config --system-libs --link-static` duly reports `xml2s.lib`, and llvm-sys forwards every system lib verbatim (`get_system_libraries` → `cargo:rustc-link-lib`) with **no knob to filter one out** — `LLVM_SYS_221_*` offers `PREFIX`, `IGNORE_BLOCKLIST`, `STRICT_VERSIONING`, `NO_CLEAN_CFLAGS`, `USE_DEBUG_MSVCRT` and `FFI_WORKAROUND`, and nothing else. So the name reached `link.exe` and the link failed before resolving a single symbol. + + `.github/actions/setup-llvm22` now synthesizes an empty archive at the LLVM libdir when — and only when — `llvm-config` reports `xml2s.lib` *and* the libdir does not contain it. It is a **name** dependency, not a symbol dependency: inside LLVM, libxml2 is reachable only from `LLVMWindowsManifest` (`WindowsManifestMerger.cpp`, serving lld-link and llvm-mt), which the LLVM-C surface inkwell drives never touches, and rustc bundles the LLVM component archives into `libllvm_sys.rlib` where `link.exe` pulls members lazily. If that ever stops being true the link fails loudly with `LNK2019` unresolved externals rather than quietly dropping manifest support. Because both conditions are checked, the workaround self-deletes: a future release that either ships the library or stops reporting it turns the block into a no-op instead of fabricating over the real thing. + + **Latent since #7353**, which made the in-process LLVM backend the default and statically linked — before that, nothing on Windows linked llvm-sys at all. It was not caused by #7388, whose diff touches only the Linux arm; the Windows arm was byte-identical to the day the action was created. The failure only became *visible* when #7393 added a `concurrency` group to `gc-native-roots.yml` and its permanently-queued `windows-latest, x86-64, PE` arm reached a runner for the first time. Fixing it in the composite action rather than in that one workflow also unblocks `test.yml`'s `windows-build` job, which had been failing identically on every run. From 12538113929dc3da5d3a7ab710e4c0dd6cfab4d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Wed, 5 Aug 2026 07:34:56 +0200 Subject: [PATCH 2/2] docs: name the fragment for its real PR (#7418) --- ...ACEHOLDER-windows-xml2s-stub.md => 7418-windows-xml2s-stub.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{PLACEHOLDER-windows-xml2s-stub.md => 7418-windows-xml2s-stub.md} (100%) diff --git a/changelog.d/PLACEHOLDER-windows-xml2s-stub.md b/changelog.d/7418-windows-xml2s-stub.md similarity index 100% rename from changelog.d/PLACEHOLDER-windows-xml2s-stub.md rename to changelog.d/7418-windows-xml2s-stub.md