| //! 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; |
| } |