Skip to content

Commit abdb945

Browse files
committed
Fix Windows LLVM CI linking
1 parent 52da048 commit abdb945

4 files changed

Lines changed: 133 additions & 6 deletions

File tree

.github/workflows/rust-test.yml

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,4 +294,26 @@ jobs:
294294
295295
- name: Run tests (Windows)
296296
if: runner.os == 'Windows'
297-
run: cargo run --locked -p pecos-cli --release -- rust test
297+
shell: bash
298+
run: |
299+
set -euo pipefail
300+
# Windows CI uses the official LLVM development archive, which is
301+
# static-only for MSVC. Keep this lane targeted so it still exercises
302+
# LLVM without fanning out many static HUGR linker jobs at once.
303+
cargo test --locked --workspace --release \
304+
--exclude pecos-rslib \
305+
--exclude pecos-rslib-cuda \
306+
--exclude pecos-julia-ffi \
307+
--exclude pecos-go-ffi \
308+
--exclude pecos-rslib-exp \
309+
--exclude pecos-rslib-llvm \
310+
--exclude pecos-cuquantum \
311+
--exclude pecos-cli \
312+
--exclude pecos-decoders \
313+
--exclude pecos-gpu-sims \
314+
--exclude pecos-hugr-qis \
315+
--exclude pecos-llvm
316+
cargo test --locked -p pecos-cli --features=runtime --release
317+
cargo test --locked -p pecos-llvm --release
318+
cargo test --locked -p pecos-qis --no-default-features --features=llvm --release
319+
cargo test --locked -p pecos-hugr-qis --release

Justfile

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,9 @@ ci-env: _msvc-bootstrap
7171
{{pecos}} llvm configure "$(brew --prefix llvm@21)"
7272
;;
7373
Windows*|MINGW*|MSYS*|CYGWIN*)
74-
if ! {{pecos}} llvm find >/dev/null 2>&1; then
75-
LLVM_PREFIX="${USERPROFILE:-$HOME}\\.pecos\\deps\\llvm-21.1"
76-
powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts/ci/install-llvm-21-windows.ps1 -InstallDir "$LLVM_PREFIX" -Version "$LLVM_RELEASE_VERSION"
77-
fi
78-
{{pecos}} llvm configure
74+
LLVM_PREFIX="${USERPROFILE:-$HOME}\\.pecos\\deps\\llvm-21.1"
75+
powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts/ci/install-llvm-21-windows.ps1 -InstallDir "$LLVM_PREFIX" -Version "$LLVM_RELEASE_VERSION"
76+
{{pecos}} llvm configure "$LLVM_PREFIX"
7977
;;
8078
*)
8179
{{pecos}} llvm ensure --managed --no-configure

scripts/ci/install-llvm-21-windows.ps1

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ $RequiredVersion = "21.1"
1919
$Asset = "clang+llvm-$Version-x86_64-pc-windows-msvc.tar.xz"
2020
$Url = "https://github.com/llvm/llvm-project/releases/download/llvmorg-$Version/$Asset"
2121
$LlvmConfig = Join-Path $InstallDir "bin\llvm-config.exe"
22+
$LlvmConfigReal = Join-Path $InstallDir "bin\llvm-config.real.exe"
2223

2324
function Find-SevenZip {
2425
foreach ($Name in @("7z.exe", "7zz.exe", "7za.exe")) {
@@ -66,9 +67,44 @@ function Get-Sha256Hex {
6667
}
6768
}
6869

70+
function Install-LlvmConfigWrapper {
71+
$WrapperSource = Join-Path $PSScriptRoot "llvm-config-wrapper.rs"
72+
if (-not (Test-Path $WrapperSource)) {
73+
throw "LLVM config wrapper source not found: $WrapperSource"
74+
}
75+
76+
if (-not (Test-Path $LlvmConfigReal)) {
77+
if (-not (Test-Path $LlvmConfig)) {
78+
throw "llvm-config.exe not found at $LlvmConfig"
79+
}
80+
Move-Item -Force -Path $LlvmConfig -Destination $LlvmConfigReal
81+
}
82+
elseif (Test-Path $LlvmConfig) {
83+
Remove-Item -Force $LlvmConfig
84+
}
85+
86+
$Rustc = Get-Command rustc.exe -ErrorAction SilentlyContinue
87+
if (-not $Rustc) {
88+
throw "rustc.exe is required to repair the LLVM Windows llvm-config system library output"
89+
}
90+
91+
Write-Host "Installing PECOS llvm-config wrapper"
92+
& $Rustc.Source --edition=2021 -O -o $LlvmConfig $WrapperSource
93+
if ($LASTEXITCODE -ne 0) {
94+
throw "rustc failed to build llvm-config wrapper with exit code $LASTEXITCODE"
95+
}
96+
97+
$SystemLibs = (& $LlvmConfig --system-libs --link-static).Trim()
98+
$Libxml2Static = Join-Path $InstallDir "lib\libxml2s.lib"
99+
if ((-not (Test-Path $Libxml2Static)) -and $SystemLibs -match "(^|\s)libxml2s\.lib($|\s)") {
100+
throw "LLVM config wrapper did not filter missing libxml2s.lib from --system-libs"
101+
}
102+
}
103+
69104
if (Test-Path $LlvmConfig) {
70105
$FoundVersion = (& $LlvmConfig --version).Trim()
71106
if ($FoundVersion.StartsWith($RequiredVersion)) {
107+
Install-LlvmConfigWrapper
72108
Write-Host "LLVM $FoundVersion already installed at $InstallDir"
73109
exit 0
74110
}
@@ -134,6 +170,7 @@ try {
134170

135171
& $LlvmConfig --version
136172
& $LlvmConfig --shared-mode
173+
Install-LlvmConfigWrapper
137174
Write-Host "Installed LLVM $Version to $InstallDir"
138175
}
139176
finally {

scripts/ci/llvm-config-wrapper.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
use std::env;
2+
use std::io::{self, Write};
3+
use std::path::Path;
4+
use std::process::{Command, ExitCode};
5+
6+
fn llvm_lib_exists(current_exe: &Path, lib_name: &str) -> bool {
7+
let Some(bin_dir) = current_exe.parent() else {
8+
return false;
9+
};
10+
let Some(prefix_dir) = bin_dir.parent() else {
11+
return false;
12+
};
13+
prefix_dir.join("lib").join(lib_name).exists()
14+
}
15+
16+
fn filter_system_libs(current_exe: &Path, stdout: &[u8]) -> Vec<u8> {
17+
let output = String::from_utf8_lossy(stdout);
18+
let mut filtered = output
19+
.split_whitespace()
20+
.filter(|token| {
21+
!token.eq_ignore_ascii_case("libxml2s.lib") || llvm_lib_exists(current_exe, token)
22+
})
23+
.collect::<Vec<_>>()
24+
.join(" ");
25+
26+
if output.ends_with('\n') {
27+
filtered.push('\n');
28+
}
29+
filtered.into_bytes()
30+
}
31+
32+
fn main() -> ExitCode {
33+
let args = env::args_os().skip(1).collect::<Vec<_>>();
34+
let current_exe = match env::current_exe() {
35+
Ok(path) => path,
36+
Err(error) => {
37+
let _ = writeln!(
38+
io::stderr(),
39+
"failed to locate llvm-config wrapper: {error}"
40+
);
41+
return ExitCode::FAILURE;
42+
}
43+
};
44+
let real_config = current_exe.with_file_name("llvm-config.real.exe");
45+
let output = match Command::new(&real_config).args(&args).output() {
46+
Ok(output) => output,
47+
Err(error) => {
48+
let _ = writeln!(
49+
io::stderr(),
50+
"failed to run {}: {error}",
51+
real_config.display()
52+
);
53+
return ExitCode::FAILURE;
54+
}
55+
};
56+
57+
let _ = io::stderr().write_all(&output.stderr);
58+
let is_system_libs = args.iter().any(|arg| arg == "--system-libs");
59+
let stdout = if is_system_libs && output.status.success() {
60+
filter_system_libs(&current_exe, &output.stdout)
61+
} else {
62+
output.stdout
63+
};
64+
let _ = io::stdout().write_all(&stdout);
65+
66+
match output.status.code() {
67+
Some(code) if (0..=255).contains(&code) => ExitCode::from(code as u8),
68+
Some(_) | None => ExitCode::FAILURE,
69+
}
70+
}

0 commit comments

Comments
 (0)