1//! Support for cross-compile tests with the `--target` flag.
2//!
3//! Note that cross-testing is very limited. You need to install the
4//! "alternate" target to the host (32-bit for 64-bit hosts or vice-versa).
5//!
6//! Set `CFG_DISABLE_CROSS_TESTS=1` environment variable to disable these tests
7//! if you are unable to use the alternate target. Unfortunately 32-bit
8//! support on macOS is going away, so macOS users are out of luck.
9//!
10//! These tests are all disabled on rust-lang/rust's CI, but run in Cargo's CI.
1112use crate::{basic_manifest, main_file, project};
13use cargo_util::ProcessError;
14use std::env;
15use std::fmt::Write;
16use std::process::{Command, Output};
17use std::sync::atomic::{AtomicBool, Ordering};
18use std::sync::Once;
1920/// Whether or not the resulting cross binaries can run on the host.
21static CAN_RUN_ON_HOST: AtomicBool = AtomicBool::new(false);
2223pub fn disabled() -> bool {
24// First, disable if requested.
25match env::var("CFG_DISABLE_CROSS_TESTS") {
26Ok(ref s) if *s == "1" => return true,
27_ => {}
28 }
2930// Cross tests are only tested to work on macos, linux, and MSVC windows.
31if !(cfg!(target_os = "macos") || cfg!(target_os = "linux") || cfg!(target_env = "msvc")) {
32return true;
33 }
3435// It's not particularly common to have a cross-compilation setup, so
36 // try to detect that before we fail a bunch of tests through no fault
37 // of the user.
38static CAN_BUILD_CROSS_TESTS: AtomicBool = AtomicBool::new(false);
39static CHECK: Once = Once::new();
4041let cross_target = alternate();
4243let run_cross_test = || -> anyhow::Result<Output> {
44let p = project()
45 .at("cross_test")
46 .file("Cargo.toml", &basic_manifest("cross_test", "1.0.0"))
47 .file("src/main.rs", &main_file(r#""testing!""#, &[]))
48 .build();
4950let build_result = p
51 .cargo("build --target")
52 .arg(&cross_target)
53 .exec_with_output();
5455if build_result.is_ok() {
56 CAN_BUILD_CROSS_TESTS.store(true, Ordering::SeqCst);
57 }
5859let result = p
60 .cargo("run --target")
61 .arg(&cross_target)
62 .exec_with_output();
6364if result.is_ok() {
65 CAN_RUN_ON_HOST.store(true, Ordering::SeqCst);
66 }
67 build_result
68 };
6970 CHECK.call_once(|| {
71 drop(run_cross_test());
72 });
7374if CAN_BUILD_CROSS_TESTS.load(Ordering::SeqCst) {
75// We were able to compile a simple project, so the user has the
76 // necessary `std::` bits installed. Therefore, tests should not
77 // be disabled.
78return false;
79 }
8081// We can't compile a simple cross project. We want to warn the user
82 // by failing a single test and having the remainder of the cross tests
83 // pass. We don't use `std::sync::Once` here because panicking inside its
84 // `call_once` method would poison the `Once` instance, which is not what
85 // we want.
86static HAVE_WARNED: AtomicBool = AtomicBool::new(false);
8788if HAVE_WARNED.swap(true, Ordering::SeqCst) {
89// We are some other test and somebody else is handling the warning.
90 // Just disable the current test.
91return true;
92 }
9394// We are responsible for warning the user, which we do by panicking.
95let mut message = format!(
96"
97Cannot cross compile to {}.
9899This failure can be safely ignored. If you would prefer to not see this
100failure, you can set the environment variable CFG_DISABLE_CROSS_TESTS to \"1\".
101102Alternatively, you can install the necessary libraries to enable cross
103compilation tests. Cross compilation tests depend on your host platform.
104",
105 cross_target
106 );
107108if cfg!(target_os = "linux") {
109 message.push_str(
110"
111Linux cross tests target i686-unknown-linux-gnu, which requires the ability to
112build and run 32-bit targets. This requires the 32-bit libraries to be
113installed. For example, on Ubuntu, run `sudo apt install gcc-multilib` to
114install the necessary libraries.
115",
116 );
117 } else if cfg!(all(target_os = "macos", target_arch = "aarch64")) {
118 message.push_str(
119"
120macOS on aarch64 cross tests to target x86_64-apple-darwin.
121This should be natively supported via Xcode, nothing additional besides the
122rustup target should be needed.
123",
124 );
125 } else if cfg!(target_os = "macos") {
126 message.push_str(
127"
128macOS on x86_64 cross tests to target x86_64-apple-ios, which requires the iOS
129SDK to be installed. This should be included with Xcode automatically. If you
130are using the Xcode command line tools, you'll need to install the full Xcode
131app (from the Apple App Store), and switch to it with this command:
132133 sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
134135Some cross-tests want to *run* the executables on the host. These tests will
136be ignored if this is not possible. On macOS, this means you need an iOS
137simulator installed to run these tests. To install a simulator, open Xcode, go
138to preferences > Components, and download the latest iOS simulator.
139",
140 );
141 } else if cfg!(target_os = "windows") {
142 message.push_str(
143"
144Windows cross tests target i686-pc-windows-msvc, which requires the ability
145to build and run 32-bit targets. This should work automatically if you have
146properly installed Visual Studio build tools.
147",
148 );
149 } else {
150// The check at the top should prevent this.
151panic!("platform should have been skipped");
152 }
153154let rustup_available = Command::new("rustup").output().is_ok();
155if rustup_available {
156write!(
157 message,
158"
159Make sure that the appropriate `rustc` target is installed with rustup:
160161 rustup target add {}
162",
163 cross_target
164 )
165 .unwrap();
166 } else {
167write!(
168 message,
169"
170rustup does not appear to be installed. Make sure that the appropriate
171`rustc` target is installed for the target `{}`.
172",
173 cross_target
174 )
175 .unwrap();
176 }
177178// Show the actual error message.
179match run_cross_test() {
180Ok(_) => message.push_str("\nUh oh, second run succeeded?\n"),
181Err(err) => match err.downcast_ref::<ProcessError>() {
182Some(proc_err) => write!(message, "\nTest error: {}\n", proc_err).unwrap(),
183None => write!(message, "\nUnexpected non-process error: {}\n", err).unwrap(),
184 },
185 }
186187panic!("{}", message);
188}
189190/// The arch triple of the test-running host.
191pub fn native() -> &'static str {
192env!("NATIVE_ARCH")
193}
194195pub fn native_arch() -> &'static str {
196match native()
197 .split("-")
198 .next()
199 .expect("Target triple has unexpected format")
200 {
201"x86_64" => "x86_64",
202"aarch64" => "aarch64",
203"i686" => "x86",
204_ => panic!("This test should be gated on cross_compile::disabled."),
205 }
206}
207208/// The alternate target-triple to build with.
209///
210/// Only use this function on tests that check `cross_compile::disabled`.
211pub fn alternate() -> &'static str {
212 try_alternate().expect("This test should be gated on cross_compile::disabled.")
213}
214215/// A possible alternate target-triple to build with.
216pub(crate) fn try_alternate() -> Option<&'static str> {
217if cfg!(all(target_os = "macos", target_arch = "aarch64")) {
218Some("x86_64-apple-darwin")
219 } else if cfg!(target_os = "macos") {
220Some("x86_64-apple-ios")
221 } else if cfg!(target_os = "linux") {
222Some("i686-unknown-linux-gnu")
223 } else if cfg!(all(target_os = "windows", target_env = "msvc")) {
224Some("i686-pc-windows-msvc")
225 } else if cfg!(all(target_os = "windows", target_env = "gnu")) {
226Some("i686-pc-windows-gnu")
227 } else {
228None
229}
230}
231232pub fn alternate_arch() -> &'static str {
233if cfg!(target_os = "macos") {
234"x86_64"
235} else {
236"x86"
237}
238}
239240/// A target-triple that is neither the host nor the target.
241///
242/// Rustc may not work with it and it's alright, apart from being a
243/// valid target triple it is supposed to be used only as a
244/// placeholder for targets that should not be considered.
245pub fn unused() -> &'static str {
246"wasm32-unknown-unknown"
247}
248249/// Whether or not the host can run cross-compiled executables.
250pub fn can_run_on_host() -> bool {
251if disabled() {
252return false;
253 }
254// macos is currently configured to cross compile to x86_64-apple-ios
255 // which requires a simulator to run. Azure's CI image appears to have the
256 // SDK installed, but are not configured to launch iOS images with a
257 // simulator.
258if cfg!(target_os = "macos") {
259if CAN_RUN_ON_HOST.load(Ordering::SeqCst) {
260return true;
261 } else {
262println!("Note: Cannot run on host, skipping.");
263return false;
264 }
265 } else {
266assert!(CAN_RUN_ON_HOST.load(Ordering::SeqCst));
267return true;
268 }
269}
270271/// Check if the given target has been installed.
272///
273/// Generally [`disabled`] should be used to check if cross-compilation is allowed.
274/// And [`alternate`] to get the cross target.
275///
276/// You should only use this as a last resort to skip tests,
277/// because it doesn't report skipped tests as ignored.
278pub fn requires_target_installed(target: &str) -> bool {
279let has_target = std::process::Command::new("rustup")
280 .args(["target", "list", "--installed"])
281 .output()
282 .ok()
283 .map(|output| {
284 String::from_utf8(output.stdout)
285 .map(|stdout| stdout.contains(target))
286 .unwrap_or_default()
287 })
288 .unwrap_or_default();
289if !has_target {
290let msg =
291format!("to run this test, run `rustup target add {target} --toolchain <toolchain>`",);
292if cargo_util::is_ci() {
293panic!("{msg}");
294 } else {
295eprintln!("{msg}");
296 }
297 }
298 has_target
299}