Rollup merge of #137012 - Shourya742:2025-02-14-doc-and-unit-test-cc-detect, r=onur-ozkan

add docs and ut for bootstrap util cc-detect

This PR adds doc and unit test for bootstrap utils/cc-detect module
This commit is contained in:
Matthias Krüger 2025-02-16 17:14:03 +01:00 committed by GitHub
commit 34e789a8fb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 285 additions and 3 deletions

View file

@ -29,6 +29,7 @@ use crate::core::config::TargetSelection;
use crate::utils::exec::{BootstrapCommand, command};
use crate::{Build, CLang, GitRepo};
/// Finds archiver tool for the given target if possible.
/// FIXME(onur-ozkan): This logic should be replaced by calling into the `cc` crate.
fn cc2ar(cc: &Path, target: TargetSelection, default_ar: PathBuf) -> Option<PathBuf> {
if let Some(ar) = env::var_os(format!("AR_{}", target.triple.replace('-', "_"))) {
@ -58,6 +59,7 @@ fn cc2ar(cc: &Path, target: TargetSelection, default_ar: PathBuf) -> Option<Path
}
}
/// Creates and configures a new [`cc::Build`] instance for the given target.
fn new_cc_build(build: &Build, target: TargetSelection) -> cc::Build {
let mut cfg = cc::Build::new();
cfg.cargo_metadata(false)
@ -84,6 +86,12 @@ fn new_cc_build(build: &Build, target: TargetSelection) -> cc::Build {
cfg
}
/// Probes for C and C++ compilers and configures the corresponding entries in the [`Build`]
/// structure.
///
/// This function determines which targets need a C compiler (and, if needed, a C++ compiler)
/// by combining the primary build target, host targets, and any additional targets. For
/// each target, it calls [`find_target`] to configure the necessary compiler tools.
pub fn find(build: &Build) {
let targets: HashSet<_> = match build.config.cmd {
// We don't need to check cross targets for these commands.
@ -112,6 +120,11 @@ pub fn find(build: &Build) {
}
}
/// Probes and configures the C and C++ compilers for a single target.
///
/// This function uses both user-specified configuration (from `config.toml`) and auto-detection
/// logic to determine the correct C/C++ compilers for the target. It also determines the appropriate
/// archiver (`ar`) and sets up additional compilation flags (both handled and unhandled).
pub fn find_target(build: &Build, target: TargetSelection) {
let mut cfg = new_cc_build(build, target);
let config = build.config.target_config.get(&target);
@ -172,6 +185,8 @@ pub fn find_target(build: &Build, target: TargetSelection) {
}
}
/// Determines the default compiler for a given target and language when not explicitly
/// configured in `config.toml`.
fn default_compiler(
cfg: &mut cc::Build,
compiler: Language,
@ -248,6 +263,12 @@ fn default_compiler(
}
}
/// Constructs the path to the Android NDK compiler for the given target triple and language.
///
/// This helper function transform the target triple by converting certain architecture names
/// (for example, translating "arm" to "arm7a"), appends the minimum API level (hardcoded as "21"
/// for NDK r26d), and then constructs the full path based on the provided NDK directory and host
/// platform.
pub(crate) fn ndk_compiler(compiler: Language, triple: &str, ndk: &Path) -> PathBuf {
let mut triple_iter = triple.split('-');
let triple_translated = if let Some(arch) = triple_iter.next() {
@ -277,7 +298,11 @@ pub(crate) fn ndk_compiler(compiler: Language, triple: &str, ndk: &Path) -> Path
ndk.join("toolchains").join("llvm").join("prebuilt").join(host_tag).join("bin").join(compiler)
}
/// The target programming language for a native compiler.
/// Representing the target programming language for a native compiler.
///
/// This enum is used to indicate whether a particular compiler is intended for C or C++.
/// It also provides helper methods for obtaining the standard executable names for GCC and
/// clang-based compilers.
#[derive(PartialEq)]
pub(crate) enum Language {
/// The compiler is targeting C.
@ -287,7 +312,7 @@ pub(crate) enum Language {
}
impl Language {
/// Obtains the name of a compiler in the GCC collection.
/// Returns the executable name for a GCC compiler corresponding to this language.
fn gcc(self) -> &'static str {
match self {
Language::C => "gcc",
@ -295,7 +320,7 @@ impl Language {
}
}
/// Obtains the name of a compiler in the clang suite.
/// Returns the executable name for a clang-based compiler corresponding to this language.
fn clang(self) -> &'static str {
match self {
Language::C => "clang",
@ -303,3 +328,6 @@ impl Language {
}
}
}
#[cfg(test)]
mod tests;

View file

@ -0,0 +1,254 @@
use std::path::{Path, PathBuf};
use std::{env, iter};
use super::*;
use crate::core::config::{Target, TargetSelection};
use crate::{Build, Config, Flags};
#[test]
fn test_cc2ar_env_specific() {
let triple = "x86_64-unknown-linux-gnu";
let key = "AR_x86_64_unknown_linux_gnu";
env::set_var(key, "custom-ar");
let target = TargetSelection::from_user(triple);
let cc = Path::new("/usr/bin/clang");
let default_ar = PathBuf::from("default-ar");
let result = cc2ar(cc, target, default_ar);
env::remove_var(key);
assert_eq!(result, Some(PathBuf::from("custom-ar")));
}
#[test]
fn test_cc2ar_musl() {
let triple = "x86_64-unknown-linux-musl";
env::remove_var("AR_x86_64_unknown_linux_musl");
env::remove_var("AR");
let target = TargetSelection::from_user(triple);
let cc = Path::new("/usr/bin/clang");
let default_ar = PathBuf::from("default-ar");
let result = cc2ar(cc, target, default_ar);
assert_eq!(result, Some(PathBuf::from("ar")));
}
#[test]
fn test_cc2ar_openbsd() {
let triple = "x86_64-unknown-openbsd";
env::remove_var("AR_x86_64_unknown_openbsd");
env::remove_var("AR");
let target = TargetSelection::from_user(triple);
let cc = Path::new("/usr/bin/cc");
let default_ar = PathBuf::from("default-ar");
let result = cc2ar(cc, target, default_ar);
assert_eq!(result, Some(PathBuf::from("ar")));
}
#[test]
fn test_cc2ar_vxworks() {
let triple = "armv7-wrs-vxworks";
env::remove_var("AR_armv7_wrs_vxworks");
env::remove_var("AR");
let target = TargetSelection::from_user(triple);
let cc = Path::new("/usr/bin/clang");
let default_ar = PathBuf::from("default-ar");
let result = cc2ar(cc, target, default_ar);
assert_eq!(result, Some(PathBuf::from("wr-ar")));
}
#[test]
fn test_cc2ar_nto_i586() {
let triple = "i586-unknown-nto-something";
env::remove_var("AR_i586_unknown_nto_something");
env::remove_var("AR");
let target = TargetSelection::from_user(triple);
let cc = Path::new("/usr/bin/clang");
let default_ar = PathBuf::from("default-ar");
let result = cc2ar(cc, target, default_ar);
assert_eq!(result, Some(PathBuf::from("ntox86-ar")));
}
#[test]
fn test_cc2ar_nto_aarch64() {
let triple = "aarch64-unknown-nto-something";
env::remove_var("AR_aarch64_unknown_nto_something");
env::remove_var("AR");
let target = TargetSelection::from_user(triple);
let cc = Path::new("/usr/bin/clang");
let default_ar = PathBuf::from("default-ar");
let result = cc2ar(cc, target, default_ar);
assert_eq!(result, Some(PathBuf::from("ntoaarch64-ar")));
}
#[test]
fn test_cc2ar_nto_x86_64() {
let triple = "x86_64-unknown-nto-something";
env::remove_var("AR_x86_64_unknown_nto_something");
env::remove_var("AR");
let target = TargetSelection::from_user(triple);
let cc = Path::new("/usr/bin/clang");
let default_ar = PathBuf::from("default-ar");
let result = cc2ar(cc, target, default_ar);
assert_eq!(result, Some(PathBuf::from("ntox86_64-ar")));
}
#[test]
#[should_panic(expected = "Unknown architecture, cannot determine archiver for Neutrino QNX")]
fn test_cc2ar_nto_unknown() {
let triple = "powerpc-unknown-nto-something";
env::remove_var("AR_powerpc_unknown_nto_something");
env::remove_var("AR");
let target = TargetSelection::from_user(triple);
let cc = Path::new("/usr/bin/clang");
let default_ar = PathBuf::from("default-ar");
let _ = cc2ar(cc, target, default_ar);
}
#[test]
fn test_ndk_compiler_c() {
let ndk_path = PathBuf::from("/ndk");
let target_triple = "arm-unknown-linux-android";
let expected_triple_translated = "armv7a-unknown-linux-android";
let expected_compiler = format!("{}21-{}", expected_triple_translated, Language::C.clang());
let host_tag = if cfg!(target_os = "macos") {
"darwin-x86_64"
} else if cfg!(target_os = "windows") {
"windows-x86_64"
} else {
"linux-x86_64"
};
let expected_path = ndk_path
.join("toolchains")
.join("llvm")
.join("prebuilt")
.join(host_tag)
.join("bin")
.join(&expected_compiler);
let result = ndk_compiler(Language::C, target_triple, &ndk_path);
assert_eq!(result, expected_path);
}
#[test]
fn test_ndk_compiler_cpp() {
let ndk_path = PathBuf::from("/ndk");
let target_triple = "arm-unknown-linux-android";
let expected_triple_translated = "armv7a-unknown-linux-android";
let expected_compiler =
format!("{}21-{}", expected_triple_translated, Language::CPlusPlus.clang());
let host_tag = if cfg!(target_os = "macos") {
"darwin-x86_64"
} else if cfg!(target_os = "windows") {
"windows-x86_64"
} else {
"linux-x86_64"
};
let expected_path = ndk_path
.join("toolchains")
.join("llvm")
.join("prebuilt")
.join(host_tag)
.join("bin")
.join(&expected_compiler);
let result = ndk_compiler(Language::CPlusPlus, target_triple, &ndk_path);
assert_eq!(result, expected_path);
}
#[test]
fn test_language_gcc() {
assert_eq!(Language::C.gcc(), "gcc");
assert_eq!(Language::CPlusPlus.gcc(), "g++");
}
#[test]
fn test_language_clang() {
assert_eq!(Language::C.clang(), "clang");
assert_eq!(Language::CPlusPlus.clang(), "clang++");
}
#[test]
fn test_new_cc_build() {
let build = Build::new(Config { ..Config::parse(Flags::parse(&["check".to_owned()])) });
let target = TargetSelection::from_user("x86_64-unknown-linux-gnu");
let cfg = new_cc_build(&build, target.clone());
let compiler = cfg.get_compiler();
assert!(!compiler.path().to_str().unwrap().is_empty(), "Compiler path should not be empty");
}
#[test]
fn test_default_compiler_wasi() {
let build = Build::new(Config { ..Config::parse(Flags::parse(&["check".to_owned()])) });
let target = TargetSelection::from_user("wasm32-wasi");
let wasi_sdk = PathBuf::from("/wasi-sdk");
env::set_var("WASI_SDK_PATH", &wasi_sdk);
let mut cfg = cc::Build::new();
if let Some(result) = default_compiler(&mut cfg, Language::C, target.clone(), &build) {
let expected = {
let compiler = format!("{}-clang", target.triple);
wasi_sdk.join("bin").join(compiler)
};
assert_eq!(result, expected);
} else {
panic!(
"default_compiler should return a compiler path for wasi target when WASI_SDK_PATH is set"
);
}
env::remove_var("WASI_SDK_PATH");
}
#[test]
fn test_default_compiler_fallback() {
let build = Build::new(Config { ..Config::parse(Flags::parse(&["check".to_owned()])) });
let target = TargetSelection::from_user("x86_64-unknown-linux-gnu");
let mut cfg = cc::Build::new();
let result = default_compiler(&mut cfg, Language::C, target, &build);
assert!(result.is_none(), "default_compiler should return None for generic targets");
}
#[test]
fn test_find_target_with_config() {
let mut build = Build::new(Config { ..Config::parse(Flags::parse(&["check".to_owned()])) });
let target = TargetSelection::from_user("x86_64-unknown-linux-gnu");
let mut target_config = Target::default();
target_config.cc = Some(PathBuf::from("dummy-cc"));
target_config.cxx = Some(PathBuf::from("dummy-cxx"));
target_config.ar = Some(PathBuf::from("dummy-ar"));
target_config.ranlib = Some(PathBuf::from("dummy-ranlib"));
build.config.target_config.insert(target.clone(), target_config);
find_target(&build, target.clone());
let binding = build.cc.borrow();
let cc_tool = binding.get(&target).unwrap();
assert_eq!(cc_tool.path(), &PathBuf::from("dummy-cc"));
let binding = build.cxx.borrow();
let cxx_tool = binding.get(&target).unwrap();
assert_eq!(cxx_tool.path(), &PathBuf::from("dummy-cxx"));
let binding = build.ar.borrow();
let ar = binding.get(&target).unwrap();
assert_eq!(ar, &PathBuf::from("dummy-ar"));
let binding = build.ranlib.borrow();
let ranlib = binding.get(&target).unwrap();
assert_eq!(ranlib, &PathBuf::from("dummy-ranlib"));
}
#[test]
fn test_find_target_without_config() {
let mut build = Build::new(Config { ..Config::parse(Flags::parse(&["check".to_owned()])) });
let target = TargetSelection::from_user("x86_64-unknown-linux-gnu");
build.config.target_config.clear();
find_target(&build, target.clone());
assert!(build.cc.borrow().contains_key(&target));
if !target.triple.contains("vxworks") {
assert!(build.cxx.borrow().contains_key(&target));
}
assert!(build.ar.borrow().contains_key(&target));
}
#[test]
fn test_find() {
let mut build = Build::new(Config { ..Config::parse(Flags::parse(&["check".to_owned()])) });
let target1 = TargetSelection::from_user("x86_64-unknown-linux-gnu");
let target2 = TargetSelection::from_user("arm-linux-androideabi");
build.targets.push(target1.clone());
build.hosts.push(target2.clone());
find(&build);
for t in build.hosts.iter().chain(build.targets.iter()).chain(iter::once(&build.build)) {
assert!(build.cc.borrow().contains_key(t), "CC not set for target {}", t.triple);
}
}