bootstrap/utils/
cc_detect.rs

1//! C-compiler probing and detection.
2//!
3//! This module will fill out the `cc` and `cxx` maps of `Build` by looking for
4//! C and C++ compilers for each target configured. A compiler is found through
5//! a number of vectors (in order of precedence)
6//!
7//! 1. Configuration via `target.$target.cc` in `config.toml`.
8//! 2. Configuration via `target.$target.android-ndk` in `config.toml`, if
9//!    applicable
10//! 3. Special logic to probe on OpenBSD
11//! 4. The `CC_$target` environment variable.
12//! 5. The `CC` environment variable.
13//! 6. "cc"
14//!
15//! Some of this logic is implemented here, but much of it is farmed out to the
16//! `cc` crate itself, so we end up having the same fallbacks as there.
17//! Similar logic is then used to find a C++ compiler, just some s/cc/c++/ is
18//! used.
19//!
20//! It is intended that after this module has run no C/C++ compiler will
21//! ever be probed for. Instead the compilers found here will be used for
22//! everything.
23
24use std::collections::HashSet;
25use std::path::{Path, PathBuf};
26use std::{env, iter};
27
28use crate::core::config::TargetSelection;
29use crate::utils::exec::{BootstrapCommand, command};
30use crate::{Build, CLang, GitRepo};
31
32/// FIXME(onur-ozkan): This logic should be replaced by calling into the `cc` crate.
33fn cc2ar(cc: &Path, target: TargetSelection, default_ar: PathBuf) -> Option<PathBuf> {
34    if let Some(ar) = env::var_os(format!("AR_{}", target.triple.replace('-', "_"))) {
35        Some(PathBuf::from(ar))
36    } else if let Some(ar) = env::var_os("AR") {
37        Some(PathBuf::from(ar))
38    } else if target.is_msvc() {
39        None
40    } else if target.contains("musl") || target.contains("openbsd") {
41        Some(PathBuf::from("ar"))
42    } else if target.contains("vxworks") {
43        Some(PathBuf::from("wr-ar"))
44    } else if target.contains("-nto-") {
45        if target.starts_with("i586") {
46            Some(PathBuf::from("ntox86-ar"))
47        } else if target.starts_with("aarch64") {
48            Some(PathBuf::from("ntoaarch64-ar"))
49        } else if target.starts_with("x86_64") {
50            Some(PathBuf::from("ntox86_64-ar"))
51        } else {
52            panic!("Unknown architecture, cannot determine archiver for Neutrino QNX");
53        }
54    } else if target.contains("android") || target.contains("-wasi") {
55        Some(cc.parent().unwrap().join(PathBuf::from("llvm-ar")))
56    } else {
57        Some(default_ar)
58    }
59}
60
61fn new_cc_build(build: &Build, target: TargetSelection) -> cc::Build {
62    let mut cfg = cc::Build::new();
63    cfg.cargo_metadata(false)
64        .opt_level(2)
65        .warnings(false)
66        .debug(false)
67        // Compress debuginfo
68        .flag_if_supported("-gz")
69        .target(&target.triple)
70        .host(&build.build.triple);
71    match build.crt_static(target) {
72        Some(a) => {
73            cfg.static_crt(a);
74        }
75        None => {
76            if target.is_msvc() {
77                cfg.static_crt(true);
78            }
79            if target.contains("musl") {
80                cfg.static_flag(true);
81            }
82        }
83    }
84    cfg
85}
86
87pub fn find(build: &Build) {
88    let targets: HashSet<_> = match build.config.cmd {
89        // We don't need to check cross targets for these commands.
90        crate::Subcommand::Clean { .. }
91        | crate::Subcommand::Suggest { .. }
92        | crate::Subcommand::Format { .. }
93        | crate::Subcommand::Setup { .. } => {
94            build.hosts.iter().cloned().chain(iter::once(build.build)).collect()
95        }
96
97        _ => {
98            // For all targets we're going to need a C compiler for building some shims
99            // and such as well as for being a linker for Rust code.
100            build
101                .targets
102                .iter()
103                .chain(&build.hosts)
104                .cloned()
105                .chain(iter::once(build.build))
106                .collect()
107        }
108    };
109
110    for target in targets.into_iter() {
111        find_target(build, target);
112    }
113}
114
115pub fn find_target(build: &Build, target: TargetSelection) {
116    let mut cfg = new_cc_build(build, target);
117    let config = build.config.target_config.get(&target);
118    if let Some(cc) = config
119        .and_then(|c| c.cc.clone())
120        .or_else(|| default_compiler(&mut cfg, Language::C, target, build))
121    {
122        cfg.compiler(cc);
123    }
124
125    let compiler = cfg.get_compiler();
126    let ar = if let ar @ Some(..) = config.and_then(|c| c.ar.clone()) {
127        ar
128    } else {
129        cc2ar(compiler.path(), target, PathBuf::from(cfg.get_archiver().get_program()))
130    };
131
132    build.cc.borrow_mut().insert(target, compiler.clone());
133    let mut cflags = build.cc_handled_clags(target, CLang::C);
134    cflags.extend(build.cc_unhandled_cflags(target, GitRepo::Rustc, CLang::C));
135
136    // If we use llvm-libunwind, we will need a C++ compiler as well for all targets
137    // We'll need one anyways if the target triple is also a host triple
138    let mut cfg = new_cc_build(build, target);
139    cfg.cpp(true);
140    let cxx_configured = if let Some(cxx) = config
141        .and_then(|c| c.cxx.clone())
142        .or_else(|| default_compiler(&mut cfg, Language::CPlusPlus, target, build))
143    {
144        cfg.compiler(cxx);
145        true
146    } else {
147        // Use an auto-detected compiler (or one configured via `CXX_target_triple` env vars).
148        cfg.try_get_compiler().is_ok()
149    };
150
151    // for VxWorks, record CXX compiler which will be used in lib.rs:linker()
152    if cxx_configured || target.contains("vxworks") {
153        let compiler = cfg.get_compiler();
154        build.cxx.borrow_mut().insert(target, compiler);
155    }
156
157    build.verbose(|| println!("CC_{} = {:?}", target.triple, build.cc(target)));
158    build.verbose(|| println!("CFLAGS_{} = {cflags:?}", target.triple));
159    if let Ok(cxx) = build.cxx(target) {
160        let mut cxxflags = build.cc_handled_clags(target, CLang::Cxx);
161        cxxflags.extend(build.cc_unhandled_cflags(target, GitRepo::Rustc, CLang::Cxx));
162        build.verbose(|| println!("CXX_{} = {cxx:?}", target.triple));
163        build.verbose(|| println!("CXXFLAGS_{} = {cxxflags:?}", target.triple));
164    }
165    if let Some(ar) = ar {
166        build.verbose(|| println!("AR_{} = {ar:?}", target.triple));
167        build.ar.borrow_mut().insert(target, ar);
168    }
169
170    if let Some(ranlib) = config.and_then(|c| c.ranlib.clone()) {
171        build.ranlib.borrow_mut().insert(target, ranlib);
172    }
173}
174
175fn default_compiler(
176    cfg: &mut cc::Build,
177    compiler: Language,
178    target: TargetSelection,
179    build: &Build,
180) -> Option<PathBuf> {
181    match &*target.triple {
182        // When compiling for android we may have the NDK configured in the
183        // config.toml in which case we look there. Otherwise the default
184        // compiler already takes into account the triple in question.
185        t if t.contains("android") => {
186            build.config.android_ndk.as_ref().map(|ndk| ndk_compiler(compiler, &target.triple, ndk))
187        }
188
189        // The default gcc version from OpenBSD may be too old, try using egcc,
190        // which is a gcc version from ports, if this is the case.
191        t if t.contains("openbsd") => {
192            let c = cfg.get_compiler();
193            let gnu_compiler = compiler.gcc();
194            if !c.path().ends_with(gnu_compiler) {
195                return None;
196            }
197
198            let mut cmd = BootstrapCommand::from(c.to_command());
199            let output = cmd.arg("--version").run_capture_stdout(build).stdout();
200            let i = output.find(" 4.")?;
201            match output[i + 3..].chars().next().unwrap() {
202                '0'..='6' => {}
203                _ => return None,
204            }
205            let alternative = format!("e{gnu_compiler}");
206            if command(&alternative).run_capture(build).is_success() {
207                Some(PathBuf::from(alternative))
208            } else {
209                None
210            }
211        }
212
213        "mips-unknown-linux-musl" if compiler == Language::C => {
214            if cfg.get_compiler().path().to_str() == Some("gcc") {
215                Some(PathBuf::from("mips-linux-musl-gcc"))
216            } else {
217                None
218            }
219        }
220        "mipsel-unknown-linux-musl" if compiler == Language::C => {
221            if cfg.get_compiler().path().to_str() == Some("gcc") {
222                Some(PathBuf::from("mipsel-linux-musl-gcc"))
223            } else {
224                None
225            }
226        }
227
228        t if t.contains("musl") && compiler == Language::C => {
229            if let Some(root) = build.musl_root(target) {
230                let guess = root.join("bin/musl-gcc");
231                if guess.exists() { Some(guess) } else { None }
232            } else {
233                None
234            }
235        }
236
237        t if t.contains("-wasi") => {
238            let root = PathBuf::from(std::env::var_os("WASI_SDK_PATH")?);
239            let compiler = match compiler {
240                Language::C => format!("{t}-clang"),
241                Language::CPlusPlus => format!("{t}-clang++"),
242            };
243            let compiler = root.join("bin").join(compiler);
244            Some(compiler)
245        }
246
247        _ => None,
248    }
249}
250
251pub(crate) fn ndk_compiler(compiler: Language, triple: &str, ndk: &Path) -> PathBuf {
252    let mut triple_iter = triple.split('-');
253    let triple_translated = if let Some(arch) = triple_iter.next() {
254        let arch_new = match arch {
255            "arm" | "armv7" | "armv7neon" | "thumbv7" | "thumbv7neon" => "armv7a",
256            other => other,
257        };
258        std::iter::once(arch_new).chain(triple_iter).collect::<Vec<&str>>().join("-")
259    } else {
260        triple.to_string()
261    };
262
263    // The earliest API supported by NDK r26d is 21.
264    let api_level = "21";
265    let compiler = format!("{}{}-{}", triple_translated, api_level, compiler.clang());
266    let host_tag = if cfg!(target_os = "macos") {
267        // The NDK uses universal binaries, so this is correct even on ARM.
268        "darwin-x86_64"
269    } else if cfg!(target_os = "windows") {
270        "windows-x86_64"
271    } else {
272        // NDK r26d only has official releases for macOS, Windows and Linux.
273        // Try the Linux directory everywhere else, on the assumption that the OS has an
274        // emulation layer that can cope (e.g. BSDs).
275        "linux-x86_64"
276    };
277    ndk.join("toolchains").join("llvm").join("prebuilt").join(host_tag).join("bin").join(compiler)
278}
279
280/// The target programming language for a native compiler.
281#[derive(PartialEq)]
282pub(crate) enum Language {
283    /// The compiler is targeting C.
284    C,
285    /// The compiler is targeting C++.
286    CPlusPlus,
287}
288
289impl Language {
290    /// Obtains the name of a compiler in the GCC collection.
291    fn gcc(self) -> &'static str {
292        match self {
293            Language::C => "gcc",
294            Language::CPlusPlus => "g++",
295        }
296    }
297
298    /// Obtains the name of a compiler in the clang suite.
299    fn clang(self) -> &'static str {
300        match self {
301            Language::C => "clang",
302            Language::CPlusPlus => "clang++",
303        }
304    }
305}