    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264

    //! 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::{basic_manifest, main_file, project};
    use cargo_util::ProcessError;
    use std::env;
    use std::fmt::Write;
    use std::process::{Command, Output};
    use std::sync::atomic::{AtomicBool, Ordering};
    use std::sync::Once;

    /// 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 env::var("CFG_DISABLE_CROSS_TESTS") {
            Ok(ref s) if *s == "1" => 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!(all(target_os = "macos", target_arch = "aarch64")) {
            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 = "macos") {
            message.push_str(
                "
    macOS on x86_64 cross tests to target x86_64-apple-ios, which requires the iOS
    SDK to be installed. This should be included with Xcode automatically. If you
    are using the Xcode command line tools, you'll need to install the full Xcode
    app (from the Apple App Store), and switch to it with this command:

        sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer

    Some cross-tests want to *run* the executables on the host. These tests will
    be ignored if this is not possible. On macOS, this means you need an iOS
    simulator installed to run these tests. To install a simulator, open Xcode, go
    to preferences > Components, and download the latest iOS simulator.
    ",
            );
        } 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);
    }

    /// The arch triple of the test-running host.
    pub fn native() -> &'static str {
        env!("NATIVE_ARCH")
    }

    pub fn native_arch() -> &'static str {
        match native()
            .split("-")
            .next()
            .expect("Target triple has unexpected format")
        {
            "x86_64" => "x86_64",
            "aarch64" => "aarch64",
            "i686" => "x86",
            _ => panic!("This test should be gated on cross_compile::disabled."),
        }
    }

    /// The alternate target-triple to build with.
    ///
    /// Only use this function on tests that check `cross_compile::disabled`.
    pub fn alternate() -> &'static str {
        if cfg!(all(target_os = "macos", target_arch = "aarch64")) {
            "x86_64-apple-darwin"
        } else if cfg!(target_os = "macos") {
            "x86_64-apple-ios"
        } else if cfg!(target_os = "linux") {
            "i686-unknown-linux-gnu"
        } else if cfg!(all(target_os = "windows", target_env = "msvc")) {
            "i686-pc-windows-msvc"
        } else if cfg!(all(target_os = "windows", target_env = "gnu")) {
            "i686-pc-windows-gnu"
        } else {
            panic!("This test should be gated on cross_compile::disabled.");
        }
    }

    pub fn alternate_arch() -> &'static str {
        if cfg!(target_os = "macos") {
            "x86_64"
        } else {
            "x86"
        }
    }

    /// A target-triple that is neither the host nor the target.
    ///
    /// Rustc may not work with it and it's alright, apart from being a
    /// valid target triple it is supposed to be used only as a
    /// placeholder for targets that should not be considered.
    pub fn unused() -> &'static str {
        "wasm32-unknown-unknown"
    }

    /// Whether or not the host can run cross-compiled executables.
    pub fn can_run_on_host() -> bool {
        if disabled() {
            return false;
        }
        // macos is currently configured to cross compile to x86_64-apple-ios
        // which requires a simulator to run. Azure's CI image appears to have the
        // SDK installed, but are not configured to launch iOS images with a
        // simulator.
        if cfg!(target_os = "macos") {
            if CAN_RUN_ON_HOST.load(Ordering::SeqCst) {
                return true;
            } else {
                println!("Note: Cannot run on host, skipping.");
                return false;
            }
        } else {
            assert!(CAN_RUN_ON_HOST.load(Ordering::SeqCst));
            return true;
        }
    }