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 diff --git a/src/lib.rs b/src/lib.rs index 41a6306..3dfb1ef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -257,6 +257,47 @@ fn rmain(config: &mut Config) -> Result<()> { } } + // 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 + 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..23860c0 100644 --- a/tests/tests/main.rs +++ b/tests/tests/main.rs @@ -1136,3 +1136,134 @@ fn registry_config_respects_existing_replacement() -> Result<()> { assert_eq!(written, existing); Ok(()) } +#[test] +fn wasixcc_env_vars_set() -> Result<()> { + // 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() { + 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"); + } + "#, + ) + .build(); + + // 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}" + ); + + // 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}" + ); + + // 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(()) +} + +#[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(()) +}