From df86a35512eaa6cbafca5729557e054787a71631 Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Wed, 15 Jul 2026 10:56:59 +0000 Subject: [PATCH 1/3] Auto-configure wasixcc for C/C++ compilation (rebase of #71) Squashed rebase of the original PR onto current main: env::set_var now needs unsafe (edition 2024), and the surrounding dependency-check code was replaced by the registry config write in the meantime. Original change: when wasixcc/wasixcc++ are on PATH and CC/CXX are not already set, set them; for the -dl target, default WASIXCC_PIC=1 and WASIXCC_WASM_EXCEPTIONS=1. Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-Authored-By: Claude Fable 5 --- src/lib.rs | 30 +++++++++++++ tests/tests/main.rs | 104 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 41a6306..7635811 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -257,6 +257,36 @@ fn rmain(config: &mut Config) -> Result<()> { } } + // Set CC to wasixcc if it's available and not already set + if std::env::var("CC").is_err() && which::which("wasixcc").is_ok() { + // SAFETY: not safe in multi-threaded environment + unsafe { env::set_var("CC", "wasixcc") }; + config.verbose(|| config.info("Set CC=wasixcc")); + } + + // Set CXX to wasixcc++ if it's available and not already set + if std::env::var("CXX").is_err() && which::which("wasixcc++").is_ok() { + // SAFETY: not safe in multi-threaded environment + unsafe { env::set_var("CXX", "wasixcc++") }; + config.verbose(|| config.info("Set CXX=wasixcc++")); + } + + // For the -dl target, set wasixcc-specific environment variables + if target.ends_with("-dl") { + // Enable position-independent code for dynamic linking + if std::env::var("WASIXCC_PIC").is_err() { + // SAFETY: not safe in multi-threaded environment + unsafe { env::set_var("WASIXCC_PIC", "1") }; + config.verbose(|| config.info("Set WASIXCC_PIC=1 for -dl target")); + } + // Enable WASM exceptions for better performance and C++ exception support + if std::env::var("WASIXCC_WASM_EXCEPTIONS").is_err() { + // SAFETY: not safe in multi-threaded environment + unsafe { env::set_var("WASIXCC_WASM_EXCEPTIONS", "1") }; + config.verbose(|| config.info("Set WASIXCC_WASM_EXCEPTIONS=1 for -dl target")); + } + } + // Make sure the project resolves crates through the WASIX overlay // registry before running cargo. Every subcommand from here on resolves // the dependency graph (`download-toolchain` returned above), including diff --git a/tests/tests/main.rs b/tests/tests/main.rs index 81a3323..779a380 100644 --- a/tests/tests/main.rs +++ b/tests/tests/main.rs @@ -1136,3 +1136,107 @@ fn registry_config_respects_existing_replacement() -> Result<()> { assert_eq!(written, existing); Ok(()) } +#[test] +fn wasixcc_env_vars_set() -> Result<()> { + // Test that CC and CXX are set to wasixcc/wasixcc++ when available + let p = support::project() + .file("src/main.rs", "fn main() {}") + .file( + "build.rs", + r#" + fn main() { + if let Ok(cc) = std::env::var("CC") { + println!("cargo:warning=CC is set to: {}", cc); + } else { + println!("cargo:warning=CC is not set"); + } + if let Ok(cxx) = std::env::var("CXX") { + println!("cargo:warning=CXX is set to: {}", cxx); + } else { + println!("cargo:warning=CXX is not set"); + } + } + "#, + ) + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = '1.0.0' + "#, + ) + .build(); + + let output = p.cargo_wasix("build").assert().success(); + let stderr = String::from_utf8_lossy(&output.get_output().stderr); + + // If wasixcc is available, CC should be set to wasixcc + if which::which("wasixcc").is_ok() { + assert!( + stderr.contains("CC is set to: wasixcc"), + "Expected CC to be set to wasixcc when wasixcc is available, stderr:\n{}", + stderr + ); + } + + // If wasixcc++ is available, CXX should be set to wasixcc++ + if which::which("wasixcc++").is_ok() { + assert!( + stderr.contains("CXX is set to: wasixcc++"), + "Expected CXX to be set to wasixcc++ when wasixcc++ is available, stderr:\n{}", + stderr + ); + } + + Ok(()) +} + +#[test] +fn wasixcc_pic_and_exceptions_for_dl_target() -> Result<()> { + // Test that WASIXCC_PIC and WASIXCC_WASM_EXCEPTIONS are set for -dl target + let p = support::project() + .file("src/main.rs", "fn main() {}") + .file( + "build.rs", + r#" + fn main() { + if let Ok(pic) = std::env::var("WASIXCC_PIC") { + println!("cargo:warning=WASIXCC_PIC is set to: {}", pic); + } + if let Ok(exceptions) = std::env::var("WASIXCC_WASM_EXCEPTIONS") { + println!("cargo:warning=WASIXCC_WASM_EXCEPTIONS is set to: {}", exceptions); + } + } + "#, + ) + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = '1.0.0' + + [package.metadata] + dl = true + "#, + ) + .build(); + + let output = p.cargo_wasix("build").assert().success(); + let stderr = String::from_utf8_lossy(&output.get_output().stderr); + + // For -dl target, WASIXCC_PIC and WASIXCC_WASM_EXCEPTIONS should be set + assert!( + stderr.contains("WASIXCC_PIC is set to: 1"), + "Expected WASIXCC_PIC=1 for -dl target, stderr:\n{}", + stderr + ); + assert!( + stderr.contains("WASIXCC_WASM_EXCEPTIONS is set to: 1"), + "Expected WASIXCC_WASM_EXCEPTIONS=1 for -dl target, stderr:\n{}", + stderr + ); + + Ok(()) +} From 9663ce7314588479f5397425e8531aff6de92a4a Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Wed, 15 Jul 2026 10:59:18 +0000 Subject: [PATCH 2/3] Use target-scoped CC/CXX/AR/RANLIB vars instead of the generic ones MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The cc crate checks CC_ before TARGET_CC and CC, so setting the target-scoped variable means a host compiler configured in the user's generic CC neither blocks wasixcc nor leaks into the WASIX build — and the user's own environment is never modified. A target-scoped variable already set by the user (dashed or underscored spelling) is respected. Also covers AR/RANLIB now, since the wasixcc suite ships wasixar and wasixranlib and cc consults those variables for static libraries. Co-Authored-By: Claude Fable 5 --- src/lib.rs | 35 ++++++++++------ tests/tests/main.rs | 97 +++++++++++++++++++++++++++++---------------- 2 files changed, 85 insertions(+), 47 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7635811..3dfb1ef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -257,18 +257,29 @@ fn rmain(config: &mut Config) -> Result<()> { } } - // Set CC to wasixcc if it's available and not already set - if std::env::var("CC").is_err() && which::which("wasixcc").is_ok() { - // SAFETY: not safe in multi-threaded environment - unsafe { env::set_var("CC", "wasixcc") }; - config.verbose(|| config.info("Set CC=wasixcc")); - } - - // Set CXX to wasixcc++ if it's available and not already set - if std::env::var("CXX").is_err() && which::which("wasixcc++").is_ok() { - // SAFETY: not safe in multi-threaded environment - unsafe { env::set_var("CXX", "wasixcc++") }; - config.verbose(|| config.info("Set CXX=wasixcc++")); + // Point the `cc` crate (and anything else honoring its conventions) at + // the wasixcc toolchain when it's installed. The variables are scoped to + // the target — `cc` checks `CC_` before `TARGET_CC` and `CC` — + // so a host compiler configured in the user's environment neither blocks + // this nor leaks into the WASIX build, and the user's generic setting is + // never touched. A target-scoped variable already set by the user (in + // either the dashed or underscored spelling) is respected. + for (tool, wasix_tool) in [ + ("CC", "wasixcc"), + ("CXX", "wasixcc++"), + ("AR", "wasixar"), + ("RANLIB", "wasixranlib"), + ] { + let dashed = format!("{tool}_{target}"); + let underscored = format!("{tool}_{}", target.replace('-', "_")); + if env::var_os(&dashed).is_some() || env::var_os(&underscored).is_some() { + continue; + } + if which::which(wasix_tool).is_ok() { + // SAFETY: not safe in multi-threaded environment + unsafe { env::set_var(&underscored, wasix_tool) }; + config.verbose(|| config.info(&format!("Set {underscored}={wasix_tool}"))); + } } // For the -dl target, set wasixcc-specific environment variables diff --git a/tests/tests/main.rs b/tests/tests/main.rs index 779a380..23860c0 100644 --- a/tests/tests/main.rs +++ b/tests/tests/main.rs @@ -1138,56 +1138,83 @@ fn registry_config_respects_existing_replacement() -> Result<()> { } #[test] fn wasixcc_env_vars_set() -> Result<()> { - // Test that CC and CXX are set to wasixcc/wasixcc++ when available + // The wasixcc tools are pointed at via target-scoped variables (the ones + // the `cc` crate checks first), so a host compiler in the generic CC/CXX + // never blocks them and is never modified. + if which::which("wasixcc").is_err() { + eprintln!("SKIPPED wasixcc_env_vars_set: wasixcc not on PATH"); + return Ok(()); + } + let p = support::project() .file("src/main.rs", "fn main() {}") .file( "build.rs", r#" fn main() { - if let Ok(cc) = std::env::var("CC") { - println!("cargo:warning=CC is set to: {}", cc); - } else { - println!("cargo:warning=CC is not set"); - } - if let Ok(cxx) = std::env::var("CXX") { - println!("cargo:warning=CXX is set to: {}", cxx); - } else { - println!("cargo:warning=CXX is not set"); + for var in [ + "CC_wasm32_wasmer_wasi", + "CXX_wasm32_wasmer_wasi", + "AR_wasm32_wasmer_wasi", + "CC", + ] { + // Changed env vars must re-run this script, or later + // builds replay the first run's cached warnings. + println!("cargo:rerun-if-env-changed={}", var); + match std::env::var(var) { + Ok(v) => println!("cargo:warning={} is set to: {}", var, v), + Err(_) => println!("cargo:warning={} is not set", var), + } } + println!("cargo:rerun-if-env-changed=CC_wasm32-wasmer-wasi"); } "#, ) - .file( - "Cargo.toml", - r#" - [package] - name = "foo" - version = '1.0.0' - "#, - ) .build(); - let output = p.cargo_wasix("build").assert().success(); + // A generic host CC in the environment must not block the target-scoped + // variables (and must be passed through untouched). + let mut cmd = p.cargo_wasix("build"); + cmd.env("CC", "host-cc-do-not-use"); + let output = cmd.assert().success(); let stderr = String::from_utf8_lossy(&output.get_output().stderr); + assert!( + stderr.contains("CC_wasm32_wasmer_wasi is set to: wasixcc"), + "{stderr}" + ); + assert!( + stderr.contains("CXX_wasm32_wasmer_wasi is set to: wasixcc++"), + "{stderr}" + ); + assert!( + stderr.contains("AR_wasm32_wasmer_wasi is set to: wasixar"), + "{stderr}" + ); + assert!( + stderr.contains("CC is set to: host-cc-do-not-use"), + "{stderr}" + ); - // If wasixcc is available, CC should be set to wasixcc - if which::which("wasixcc").is_ok() { - assert!( - stderr.contains("CC is set to: wasixcc"), - "Expected CC to be set to wasixcc when wasixcc is available, stderr:\n{}", - stderr - ); - } + // A user-set target-scoped variable wins over the wasixcc default. + let mut cmd = p.cargo_wasix("build"); + cmd.env("CC_wasm32_wasmer_wasi", "my-custom-wasix-cc"); + let output = cmd.assert().success(); + let stderr = String::from_utf8_lossy(&output.get_output().stderr); + assert!( + stderr.contains("CC_wasm32_wasmer_wasi is set to: my-custom-wasix-cc"), + "{stderr}" + ); - // If wasixcc++ is available, CXX should be set to wasixcc++ - if which::which("wasixcc++").is_ok() { - assert!( - stderr.contains("CXX is set to: wasixcc++"), - "Expected CXX to be set to wasixcc++ when wasixcc++ is available, stderr:\n{}", - stderr - ); - } + // The dashed spelling (checked first by `cc`) also counts as a user + // override: the underscored variant must not be set on top of it. + let mut cmd = p.cargo_wasix("build"); + cmd.env("CC_wasm32-wasmer-wasi", "my-dashed-wasix-cc"); + let output = cmd.assert().success(); + let stderr = String::from_utf8_lossy(&output.get_output().stderr); + assert!( + stderr.contains("CC_wasm32_wasmer_wasi is not set"), + "{stderr}" + ); Ok(()) } From 0613bd4588d99a44c37ba4fbce1a03868df2898c Mon Sep 17 00:00:00 2001 From: Arshia Ghafoori Date: Wed, 15 Jul 2026 11:09:05 +0000 Subject: [PATCH 3/3] Install wasixcc in test CI so the integration tests exercise it Uses the setup action shipped by wasix-org/wasixcc; without it on PATH the wasixcc tests skip themselves. Co-Authored-By: Claude Fable 5 --- .github/workflows/ci.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 67a9d39..2dac680 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,6 +38,14 @@ jobs: uses: wasmerio/setup-wasmer@v2 with: version: "prerelease" + - name: Set up wasixcc + # Puts wasixcc/wasixcc++/wasixar on PATH so the wasixcc integration + # tests exercise the real thing instead of skipping. + uses: wasix-org/wasixcc@v0.4.3 + with: + # Authenticated GitHub API requests/downloads; without this the + # action hits anonymous per-IP rate limits on shared runners. + github_token: ${{ secrets.GITHUB_TOKEN }} - name: test shell: bash run: cargo test --all-features -j1