blob: 6c6df21586b63f42b78b69850265eb9ac2ed120c [file] [log] [blame]
//! Support for cross-compile tests with the `--target` flag.
//!
//! Note that cross-testing is very limited. You need to install the
//! "alternate" target to the host (32-bit for 64-bit hosts or vice-versa).
//!
//! Set `CFG_DISABLE_CROSS_TESTS=1` environment variable to disable these tests
//! if you are unable to use the alternate target. Unfortunately 32-bit
//! support on macOS is going away, so macOS users are out of luck.
//!
//! These tests are all disabled on rust-lang/rust's CI, but run in Cargo's CI.
use crate::prelude::*;
use cargo_test_support::{basic_manifest, cross_compile::alternate, main_file, project};
use cargo_util::ProcessError;
use std::fmt::Write;
use std::{
process::{Command, Output},
sync::{
Once,
atomic::{AtomicBool, Ordering},
},
};
/// Whether or not the resulting cross binaries can run on the host.
static CAN_RUN_ON_HOST: AtomicBool = AtomicBool::new(false);
pub fn disabled() -> bool {
// First, disable if requested.
match std::env::var("CFG_DISABLE_CROSS_TESTS") {
Ok(ref s) if *s == "1" => return true,
_ => {}
}
// It requires setting `target.linker` for cross-compilation to work on aarch64,
// so not going to bother now.
if cfg!(all(target_arch = "aarch64", target_os = "linux")) {
return true;
}
// Cross tests are only tested to work on macos, linux, and MSVC windows.
if !(cfg!(target_os = "macos") || cfg!(target_os = "linux") || cfg!(target_env = "msvc")) {
return true;
}
// It's not particularly common to have a cross-compilation setup, so
// try to detect that before we fail a bunch of tests through no fault
// of the user.
static CAN_BUILD_CROSS_TESTS: AtomicBool = AtomicBool::new(false);
static CHECK: Once = Once::new();
let cross_target = alternate();
let run_cross_test = || -> anyhow::Result<Output> {
let p = project()
.at("cross_test")
.file("Cargo.toml", &basic_manifest("cross_test", "1.0.0"))
.file("src/main.rs", &main_file(r#""testing!""#, &[]))
.build();
let build_result = p
.cargo("build --target")
.arg(&cross_target)
.exec_with_output();
if build_result.is_ok() {
CAN_BUILD_CROSS_TESTS.store(true, Ordering::SeqCst);
}
let result = p
.cargo("run --target")
.arg(&cross_target)
.exec_with_output();
if result.is_ok() {
CAN_RUN_ON_HOST.store(true, Ordering::SeqCst);
}
build_result
};
CHECK.call_once(|| {
drop(run_cross_test());
});
if CAN_BUILD_CROSS_TESTS.load(Ordering::SeqCst) {
// We were able to compile a simple project, so the user has the
// necessary `std::` bits installed. Therefore, tests should not
// be disabled.
return false;
}
// We can't compile a simple cross project. We want to warn the user
// by failing a single test and having the remainder of the cross tests
// pass. We don't use `std::sync::Once` here because panicking inside its
// `call_once` method would poison the `Once` instance, which is not what
// we want.
static HAVE_WARNED: AtomicBool = AtomicBool::new(false);
if HAVE_WARNED.swap(true, Ordering::SeqCst) {
// We are some other test and somebody else is handling the warning.
// Just disable the current test.
return true;
}
// We are responsible for warning the user, which we do by panicking.
let mut message = format!(
"
Cannot cross compile to {}.
This failure can be safely ignored. If you would prefer to not see this
failure, you can set the environment variable CFG_DISABLE_CROSS_TESTS to \"1\".
Alternatively, you can install the necessary libraries to enable cross
compilation tests. Cross compilation tests depend on your host platform.
",
cross_target
);
if cfg!(target_os = "linux") {
message.push_str(
"
Linux cross tests target i686-unknown-linux-gnu, which requires the ability to
build and run 32-bit targets. This requires the 32-bit libraries to be
installed. For example, on Ubuntu, run `sudo apt install gcc-multilib` to
install the necessary libraries.
",
);
} else if cfg!(target_os = "macos") {
message.push_str(
"
macOS on aarch64 cross tests to target x86_64-apple-darwin.
This should be natively supported via Xcode, nothing additional besides the
rustup target should be needed.
",
);
} else if cfg!(target_os = "windows") {
message.push_str(
"
Windows cross tests target i686-pc-windows-msvc, which requires the ability
to build and run 32-bit targets. This should work automatically if you have
properly installed Visual Studio build tools.
",
);
} else {
// The check at the top should prevent this.
panic!("platform should have been skipped");
}
let rustup_available = Command::new("rustup").output().is_ok();
if rustup_available {
write!(
message,
"
Make sure that the appropriate `rustc` target is installed with rustup:
rustup target add {}
",
cross_target
)
.unwrap();
} else {
write!(
message,
"
rustup does not appear to be installed. Make sure that the appropriate
`rustc` target is installed for the target `{}`.
",
cross_target
)
.unwrap();
}
// Show the actual error message.
match run_cross_test() {
Ok(_) => message.push_str("\nUh oh, second run succeeded?\n"),
Err(err) => match err.downcast_ref::<ProcessError>() {
Some(proc_err) => write!(message, "\nTest error: {}\n", proc_err).unwrap(),
None => write!(message, "\nUnexpected non-process error: {}\n", err).unwrap(),
},
}
panic!("{}", message);
}
/// Whether or not the host can run cross-compiled executables.
pub fn can_run_on_host() -> bool {
if disabled() {
return false;
}
assert!(CAN_RUN_ON_HOST.load(Ordering::SeqCst));
return true;
}