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
use indoc::{formatdoc, indoc};
use std::env::consts;
use std::ffi::OsString;
use which::which;

/// Provides assistance for cross-compiling from the user's host platform to the desired target platform.
///
/// This function will not install required toolchains, linkers or compilers automatically. It will
/// look for the required tools and returns a human-readable help text if they can't be found or
/// any other issue has been detected.
pub fn cross_compile_assistance(target_triple: impl AsRef<str>) -> CrossCompileAssistance {
    let target_triple = target_triple.as_ref();
    let (gcc_binary_name, help_text) = match (target_triple, consts::OS, consts::ARCH) {
        (AARCH64_UNKNOWN_LINUX_MUSL, OS_LINUX, ARCH_X86_64) => (
            "aarch64-linux-gnu-gcc",
            indoc! {"
                To install an aarch64 cross-compiler on Ubuntu:
                sudo apt-get install g++-aarch64-linux-gnu libc6-dev-arm64-cross musl-tools
            "},
        ),
        (AARCH64_UNKNOWN_LINUX_MUSL, OS_MACOS, ARCH_X86_64 | ARCH_AARCH64) => (
            "aarch64-unknown-linux-musl-gcc",
            indoc! {"
                To install an aarch64 cross-compiler on macOS:
                brew install messense/macos-cross-toolchains/aarch64-unknown-linux-musl
            "},
        ),
        (AARCH64_UNKNOWN_LINUX_MUSL, OS_LINUX, ARCH_AARCH64)
        | (X86_64_UNKNOWN_LINUX_MUSL, OS_LINUX, ARCH_X86_64) => (
            "musl-gcc",
            indoc! {"
                To install musl-tools on Ubuntu:
                sudo apt-get install musl-tools
            "},
        ),
        (X86_64_UNKNOWN_LINUX_MUSL, OS_LINUX, ARCH_AARCH64) => (
            "x86_64-linux-gnu-gcc",
            indoc! {"
                To install an x86_64 cross-compiler on Ubuntu:
                sudo apt-get install g++-x86-64-linux-gnu libc6-dev-amd64-cross musl-tools
            "},
        ),
        (X86_64_UNKNOWN_LINUX_MUSL, OS_MACOS, ARCH_X86_64 | ARCH_AARCH64) => (
            "x86_64-unknown-linux-musl-gcc",
            indoc! {"
                To install an x86_64 cross-compiler on macOS:
                brew install messense/macos-cross-toolchains/x86_64-unknown-linux-musl
            "},
        ),
        _ => return CrossCompileAssistance::NoAssistance,
    };

    match which(gcc_binary_name) {
        Ok(_) => {
            // When the gcc binary name is `musl-gcc`, Cargo will automatically select the appropriate default linker,
            // and set the required environment variables.
            if gcc_binary_name == "musl-gcc" {
                CrossCompileAssistance::Configuration {
                    cargo_env: Vec::new(),
                }
            } else {
                CrossCompileAssistance::Configuration {
                    cargo_env: vec![
                        (
                            // Required until Cargo can auto-detect the musl-cross gcc/linker itself,
                            // since otherwise it checks for a binary named 'musl-gcc' (which is handled above):
                            // https://github.com/rust-lang/cargo/issues/4133
                            OsString::from(format!(
                                "CARGO_TARGET_{}_LINKER",
                                target_triple.to_uppercase().replace('-', "_")
                            )),
                            OsString::from(gcc_binary_name),
                        ),
                        (
                            // Required so that any crates that call out to gcc are also cross-compiled:
                            // https://github.com/alexcrichton/cc-rs/issues/82
                            OsString::from(format!("CC_{}", target_triple.replace('-', "_"))),
                            OsString::from(gcc_binary_name),
                        ),
                    ],
                }
            }
        }
        Err(_) => CrossCompileAssistance::HelpText(formatdoc! {"
            For cross-compilation from {0} {1} to {target_triple},
            a C compiler and linker for the target platform must be installed:

            {help_text}
            You will also need to install the Rust target:
            rustup target add {target_triple}
            ",
            consts::ARCH,
            consts::OS
        }),
    }
}

pub enum CrossCompileAssistance {
    /// No specific assistance available for the current host and target platform combination.
    NoAssistance,
    /// A human-readable help text with instructions on how to setup the
    /// host machine for cross-compilation.
    HelpText(String),
    /// Required configuration to cross-compile to the target platform.
    Configuration {
        cargo_env: Vec<(OsString, OsString)>,
    },
}

// Constants for supported target triples
const AARCH64_UNKNOWN_LINUX_MUSL: &str = "aarch64-unknown-linux-musl";
const X86_64_UNKNOWN_LINUX_MUSL: &str = "x86_64-unknown-linux-musl";

// Constants for `std::env::consts::OS` and `std::env::consts::ARCH`
const OS_LINUX: &str = "linux";
const OS_MACOS: &str = "macos";
const ARCH_X86_64: &str = "x86_64";
const ARCH_AARCH64: &str = "aarch64";