use std::fmt;
use std::path::PathBuf;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ToolchainKind {
Gcc,
Clang,
Msvc,
AndroidNdk,
Ios,
MacOs,
WasmWasiSdk,
WasmEmscripten,
ZigCc,
Custom,
}
impl ToolchainKind {
pub fn as_str(&self) -> &'static str {
match self {
Self::Gcc => "GCC",
Self::Clang => "Clang",
Self::Msvc => "MSVC",
Self::AndroidNdk => "Android NDK",
Self::Ios => "iOS",
Self::MacOs => "macOS",
Self::WasmWasiSdk => "WASI SDK",
Self::WasmEmscripten => "Emscripten",
Self::ZigCc => "Zig cc",
Self::Custom => "Custom",
}
}
}
#[derive(Debug, Clone)]
pub struct GccInstallation {
pub version: String,
pub prefix: PathBuf,
pub lib_dir: PathBuf,
pub include_dir: PathBuf,
pub cxx_include_dir: Option<PathBuf>,
pub crt_begin: Option<PathBuf>,
pub crt_end: Option<PathBuf>,
pub crt_i: Option<PathBuf>,
pub crt_n: Option<PathBuf>,
pub libgcc: Option<PathBuf>,
pub libgcc_eh: Option<PathBuf>,
pub triple: String,
pub multiarch_dir: Option<String>,
pub cross_compile: bool,
}
impl GccInstallation {
pub fn new(prefix: &str, triple: &str) -> Self {
let prefix_path = PathBuf::from(prefix);
let lib_dir = prefix_path.join("lib").join("gcc").join(triple);
Self {
version: "0.0.0".to_string(),
prefix: prefix_path,
lib_dir: lib_dir.clone(),
include_dir: prefix_path.join("include"),
cxx_include_dir: Some(prefix_path.join("include/c++")),
crt_begin: Some(lib_dir.join("crtbegin.o")),
crt_end: Some(lib_dir.join("crtend.o")),
crt_i: Some(lib_dir.join("crti.o")),
crt_n: Some(lib_dir.join("crtn.o")),
libgcc: Some(lib_dir.join("libgcc.a")),
libgcc_eh: Some(lib_dir.join("libgcc_eh.a")),
triple: triple.to_string(),
multiarch_dir: None,
cross_compile: false,
}
}
pub fn detect() -> Vec<Self> {
let candidates = vec![
"/usr/lib/gcc/x86_64-linux-gnu",
"/usr/lib/gcc/x86_64-pc-linux-gnu",
"/usr/lib/gcc/aarch64-linux-gnu",
"/usr/lib/gcc/arm-linux-gnueabihf",
"/usr/lib/gcc/riscv64-linux-gnu",
];
let mut installations = Vec::new();
for prefix in candidates {
let install = Self::new(prefix, "x86_64-linux-gnu");
installations.push(install);
}
installations
}
pub fn detect_version(&mut self, version_dir: &str) {
self.version = version_dir.to_string();
self.lib_dir = self.lib_dir.join(version_dir);
}
pub fn find_by_triple(triple: &str) -> Option<Self> {
let prefix = format!("/usr/lib/gcc/{}", triple);
Some(Self::new(&prefix, triple))
}
pub fn compiler_flags(&self) -> Vec<String> {
let mut flags = Vec::new();
if self.cross_compile {
flags.push(format!("--target={}", self.triple));
}
flags.push(format!("-B{}", self.lib_dir.display()));
flags.push(format!("-isystem {}", self.include_dir.display()));
if let Some(ref cxx) = self.cxx_include_dir {
flags.push(format!("-cxx-isystem {}", cxx.display()));
}
flags
}
pub fn linker_flags(&self) -> Vec<String> {
let mut flags = Vec::new();
flags.push(format!("-L{}", self.lib_dir.display()));
if let Some(ref crt_begin) = self.crt_begin {
flags.push(crt_begin.display().to_string());
}
if let Some(ref crt_i) = self.crt_i {
flags.push(crt_i.display().to_string());
}
if let Some(ref libgcc) = self.libgcc {
flags.push(libgcc.display().to_string());
}
if let Some(ref libgcc_eh) = self.libgcc_eh {
flags.push(libgcc_eh.display().to_string());
}
if let Some(ref crt_end) = self.crt_end {
flags.push(crt_end.display().to_string());
}
if let Some(ref crt_n) = self.crt_n {
flags.push(crt_n.display().to_string());
}
flags.push("-lgcc".to_string());
if self.libgcc_eh.is_some() {
flags.push("-lgcc_eh".to_string());
}
flags
}
pub fn is_native(&self) -> bool {
!self.cross_compile
}
}
impl Default for GccInstallation {
fn default() -> Self {
Self::new("/usr/lib/gcc/x86_64-linux-gnu", "x86_64-linux-gnu")
}
}
#[derive(Debug, Clone)]
pub struct MsvcInstallation {
pub vs_version: String,
pub vs_install_path: PathBuf,
pub vc_tools_path: PathBuf,
pub sdk_version: String,
pub sdk_path: PathBuf,
pub ucrt_path: PathBuf,
pub target_arch: MsvcArch,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MsvcArch {
X86,
X64,
Arm,
Arm64,
}
impl MsvcArch {
pub fn as_str(&self) -> &'static str {
match self {
Self::X86 => "x86",
Self::X64 => "x64",
Self::Arm => "arm",
Self::Arm64 => "arm64",
}
}
pub fn lib_subdir(&self) -> &'static str {
match self {
Self::X86 => "lib/x86",
Self::X64 => "lib/x64",
Self::Arm => "lib/arm",
Self::Arm64 => "lib/arm64",
}
}
}
impl MsvcInstallation {
pub fn new(vs_version: &str, vs_path: &str) -> Self {
let vs = PathBuf::from(vs_path);
Self {
vs_version: vs_version.to_string(),
vs_install_path: vs.clone(),
vc_tools_path: vs.join("VC/Tools/MSVC").join(vs_version),
sdk_version: "10.0.22621.0".to_string(),
sdk_path: PathBuf::from("C:/Program Files (x86)/Windows Kits/10"),
ucrt_path: PathBuf::from("C:/Program Files (x86)/Windows Kits/10/Include/10.0.22621.0/ucrt"),
target_arch: MsvcArch::X64,
}
}
pub fn search_paths() -> Vec<PathBuf> {
vec![
PathBuf::from("C:/Program Files/Microsoft Visual Studio/2022/Community"),
PathBuf::from("C:/Program Files/Microsoft Visual Studio/2022/Professional"),
PathBuf::from("C:/Program Files/Microsoft Visual Studio/2022/Enterprise"),
PathBuf::from("C:/Program Files (x86)/Microsoft Visual Studio/2019/Community"),
PathBuf::from("C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional"),
PathBuf::from("C:/Program Files (x86)/Microsoft Visual Studio/2019/Enterprise"),
PathBuf::from("C:/Program Files (x86)/Microsoft Visual Studio/2017/Community"),
]
}
pub fn compiler_flags(&self) -> Vec<String> {
let mut flags = Vec::new();
flags.push(format!("-target {}", self.target_triple()));
flags.push(format!("-fms-compatibility-version={}", self.vs_version));
flags.push(format!("-internal-isystem \"{}/include\"", self.vc_tools_path.display()));
flags.push(format!("-internal-isystem \"{}/atlmfc/include\"", self.vc_tools_path.display()));
if !self.sdk_path.as_os_str().is_empty() {
flags.push(format!(
"-internal-isystem \"{}/Include/{}/ucrt\"",
self.sdk_path.display(),
self.sdk_version
));
flags.push(format!(
"-internal-isystem \"{}/Include/{}/shared\"",
self.sdk_path.display(),
self.sdk_version
));
flags.push(format!(
"-internal-isystem \"{}/Include/{}/um\"",
self.sdk_path.display(),
self.sdk_version
));
flags.push(format!(
"-internal-isystem \"{}/Include/{}/winrt\"",
self.sdk_path.display(),
self.sdk_version
));
}
flags
}
pub fn linker_flags(&self) -> Vec<String> {
vec![
format!("-libpath:\"{}/{}/lib/{}\"",
self.vc_tools_path.display(),
self.target_arch.as_str(),
self.sdk_version),
format!("-libpath:\"{}/Lib/{}/{}\"",
self.sdk_path.display(),
self.sdk_version,
self.target_arch.lib_subdir()),
]
}
pub fn target_triple(&self) -> String {
match self.target_arch {
MsvcArch::X86 => "i686-pc-windows-msvc".to_string(),
MsvcArch::X64 => "x86_64-pc-windows-msvc".to_string(),
MsvcArch::Arm => "armv7-pc-windows-msvc".to_string(),
MsvcArch::Arm64 => "aarch64-pc-windows-msvc".to_string(),
}
}
pub fn with_arch(mut self, arch: MsvcArch) -> Self {
self.target_arch = arch;
self
}
}
#[derive(Debug, Clone)]
pub struct AndroidNdk {
pub ndk_path: PathBuf,
pub api_level: u32,
pub target_arch: AndroidArch,
pub stl: AndroidStl,
pub toolchain_prefix: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AndroidArch {
Arm,
Arm64,
X86,
X86_64,
}
impl AndroidArch {
pub fn as_str(&self) -> &'static str {
match self {
Self::Arm => "arm-linux-androideabi",
Self::Arm64 => "aarch64-linux-android",
Self::X86 => "i686-linux-android",
Self::X86_64 => "x86_64-linux-android",
}
}
pub fn toolchain_prefix(&self) -> &'static str {
match self {
Self::Arm => "armv7a-linux-androideabi",
Self::Arm64 => "aarch64-linux-android",
Self::X86 => "i686-linux-android",
Self::X86_64 => "x86_64-linux-android",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AndroidStl {
None,
Shared,
Static,
CxxShared,
CxxStatic,
}
impl AndroidStl {
pub fn as_flag(&self) -> Option<&'static str> {
match self {
Self::None => Some("none"),
Self::Shared => Some("c++_shared"),
Self::Static => Some("c++_static"),
Self::CxxShared => Some("c++_shared"),
Self::CxxStatic => Some("c++_static"),
}
}
}
impl AndroidNdk {
pub fn new(ndk_path: &str, api_level: u32) -> Self {
Self {
ndk_path: PathBuf::from(ndk_path),
api_level,
target_arch: AndroidArch::Arm64,
stl: AndroidStl::CxxShared,
toolchain_prefix: AndroidArch::Arm64.toolchain_prefix().to_string(),
}
}
pub fn sysroot(&self) -> PathBuf {
self.ndk_path
.join("toolchains")
.join("llvm")
.join("prebuilt")
.join("linux-x86_64")
.join("sysroot")
}
pub fn system_include(&self) -> PathBuf {
self.sysroot()
.join("usr")
.join("include")
.join(self.target_arch.toolchain_prefix())
}
pub fn stl_include(&self) -> PathBuf {
self.ndk_path
.join("sources")
.join("cxx-stl")
.join("llvm-libc++")
.join("include")
}
pub fn stl_lib_path(&self) -> PathBuf {
self.ndk_path
.join("sources")
.join("cxx-stl")
.join("llvm-libc++")
.join("libs")
.join(self.target_arch.toolchain_prefix())
}
pub fn lib_path(&self) -> PathBuf {
self.sysroot()
.join("usr")
.join("lib")
.join(self.target_arch.toolchain_prefix())
.join(self.api_level.to_string())
}
pub fn compiler_flags(&self) -> Vec<String> {
vec![
format!("--target={}", self.target_arch.as_str()),
format!("--sysroot={}", self.sysroot().display()),
format!("-isystem {}", self.system_include().display()),
format!("-isystem {}", self.stl_include().display()),
format!("-DANDROID"), format!("-D__ANDROID__"), format!("-DANDROID"),
format!("-DANDROID_API_LEVEL={}", self.api_level),
format!("-D__ANDROID_API__={}", self.api_level),
format!("-fPIC"),
format!("-ffunction-sections"),
format!("-fdata-sections"),
]
}
pub fn linker_flags(&self) -> Vec<String> {
let mut flags = vec![
format!("--sysroot={}", self.sysroot().display()),
format!("-L{}", self.lib_path().display()),
];
match self.stl {
AndroidStl::CxxShared | AndroidStl::Shared => {
flags.push(format!("-L{}", self.stl_lib_path().display()));
flags.push("-lc++_shared".to_string());
}
AndroidStl::CxxStatic | AndroidStl::Static => {
flags.push(format!("-L{}", self.stl_lib_path().display()));
flags.push("-lc++_static".to_string());
}
_ => {}
}
flags.push("-landroid".to_string());
flags.push("-llog".to_string());
flags.push("-ldl".to_string());
flags.push("-lm".to_string());
flags
}
pub fn with_arch(mut self, arch: AndroidArch) -> Self {
self.target_arch = arch;
self.toolchain_prefix = arch.toolchain_prefix().to_string();
self
}
pub fn with_stl(mut self, stl: AndroidStl) -> Self {
self.stl = stl;
self
}
pub fn with_api_level(mut self, level: u32) -> Self {
self.api_level = level;
self
}
}
impl Default for AndroidNdk {
fn default() -> Self {
Self::new("/opt/android-ndk", 30)
}
}
#[derive(Debug, Clone)]
pub struct IosToolchain {
pub sdk_path: PathBuf,
pub sdk_version: String,
pub min_version: String,
pub target_arch: IosArch,
pub is_simulator: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IosArch {
Arm64,
Arm64e,
X86_64,
}
impl IosArch {
pub fn target_triple(&self, simulator: bool) -> &'static str {
match (self, simulator) {
(IosArch::Arm64, false) => "arm64-apple-ios",
(IosArch::Arm64e, false) => "arm64e-apple-ios",
(IosArch::X86_64, true) => "x86_64-apple-ios-simulator",
(IosArch::Arm64, true) => "arm64-apple-ios-simulator",
_ => "arm64-apple-ios",
}
}
}
impl IosToolchain {
pub fn from_xcrun(sdk_name: &str) -> Option<Self> {
let sdk_path = match sdk_name {
"iphoneos" => "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk",
"iphonesimulator" => "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk",
_ => return None,
};
Some(Self {
sdk_path: PathBuf::from(sdk_path),
sdk_version: "17.0".to_string(),
min_version: "15.0".to_string(),
target_arch: IosArch::Arm64,
is_simulator: sdk_name == "iphonesimulator",
})
}
pub fn compiler_flags(&self) -> Vec<String> {
vec![
format!("--target={}", self.target_arch.target_triple(self.is_simulator)),
format!("-isysroot {}", self.sdk_path.display()),
format!("-miphoneos-version-min={}", self.min_version),
"-fobjc-arc".to_string(),
"-fobjc-abi-version=2".to_string(),
]
}
pub fn linker_flags(&self) -> Vec<String> {
vec![
format!("-isysroot {}", self.sdk_path.display()),
format!("-miphoneos-version-min={}", self.min_version),
"-framework Foundation".to_string(),
"-framework UIKit".to_string(),
]
}
pub fn with_min_version(mut self, version: &str) -> Self {
self.min_version = version.to_string();
self
}
pub fn with_arch(mut self, arch: IosArch) -> Self {
self.target_arch = arch;
self
}
}
#[derive(Debug, Clone)]
pub struct MacOsToolchain {
pub sdk_path: PathBuf,
pub sdk_version: String,
pub min_version: String,
pub target_arch: MacOsArch,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MacOsArch {
X86_64,
Arm64,
}
impl MacOsArch {
pub fn target_triple(&self) -> &'static str {
match self {
MacOsArch::X86_64 => "x86_64-apple-macosx",
MacOsArch::Arm64 => "arm64-apple-macosx",
}
}
}
impl MacOsToolchain {
pub fn from_xcrun() -> Option<Self> {
Some(Self {
sdk_path: PathBuf::from(
"/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk"
),
sdk_version: "14.0".to_string(),
min_version: "12.0".to_string(),
target_arch: MacOsArch::Arm64,
})
}
pub fn compiler_flags(&self) -> Vec<String> {
vec![
format!("--target={}", self.target_arch.target_triple()),
format!("-isysroot {}", self.sdk_path.display()),
format!("-mmacosx-version-min={}", self.min_version),
]
}
pub fn linker_flags(&self) -> Vec<String> {
vec![
format!("-isysroot {}", self.sdk_path.display()),
format!("-mmacosx-version-min={}", self.min_version),
"-framework Foundation".to_string(),
"-framework AppKit".to_string(),
]
}
pub fn framework_search_paths(&self) -> Vec<String> {
let frameworks = self.sdk_path.join("System/Library/Frameworks");
vec![
frameworks.display().to_string(),
self.sdk_path.join("Library/Frameworks").display().to_string(),
]
}
pub fn with_min_version(mut self, version: &str) -> Self {
self.min_version = version.to_string();
self
}
pub fn with_arch(mut self, arch: MacOsArch) -> Self {
self.target_arch = arch;
self
}
}
#[derive(Debug, Clone)]
pub struct WasiSdk {
pub sdk_path: PathBuf,
pub version: String,
pub sysroot: PathBuf,
}
impl WasiSdk {
pub fn new(sdk_path: &str, version: &str) -> Self {
let path = PathBuf::from(sdk_path);
Self {
sysroot: path.join("share/wasi-sysroot"),
sdk_path: path.clone(),
version: version.to_string(),
}
}
pub fn compiler_flags(&self) -> Vec<String> {
vec![
"--target=wasm32-wasi".to_string(),
format!("--sysroot={}", self.sysroot.display()),
"-D_WASI_EMULATED_MMAN".to_string(),
"-D_WASI_EMULATED_SIGNAL".to_string(),
"-D_WASI_EMULATED_PROCESS_CLOCKS".to_string(),
]
}
pub fn linker_flags(&self) -> Vec<String> {
vec![
format!("--sysroot={}", self.sysroot.display()),
"-lwasi-emulated-mman".to_string(),
"-lwasi-emulated-signal".to_string(),
"-lwasi-emulated-process-clocks".to_string(),
]
}
}
impl Default for WasiSdk {
fn default() -> Self {
Self::new("/opt/wasi-sdk", "20")
}
}
#[derive(Debug, Clone)]
pub struct EmscriptenToolchain {
pub emsdk_path: PathBuf,
pub version: String,
pub node_path: Option<PathBuf>,
pub sysroot_includes: PathBuf,
pub sysroot_libs: PathBuf,
}
impl EmscriptenToolchain {
pub fn new(emsdk_path: &str, version: &str) -> Self {
let path = PathBuf::from(emsdk_path);
let upstream = path.join("upstream").join("emscripten");
Self {
emsdk_path: path.clone(),
version: version.to_string(),
node_path: Some(PathBuf::from("node")),
sysroot_includes: upstream.join("system").join("include"),
sysroot_libs: upstream.join("system").join("lib"),
}
}
pub fn compiler_flags(&self) -> Vec<String> {
vec![
"--target=wasm32-unknown-emscripten".to_string(),
format!("-isystem {}", self.sysroot_includes.display()),
"-D__EMSCRIPTEN__".to_string(),
format!("-D__EMSCRIPTEN_major__=3"),
format!("-D__EMSCRIPTEN_minor__=1"),
]
}
pub fn linker_flags(&self) -> Vec<String> {
vec![
format!("-L{}", self.sysroot_libs.display()),
"-sALLOW_MEMORY_GROWTH=1".to_string(),
"-sEXIT_RUNTIME=1".to_string(),
"-sEXPORTED_FUNCTIONS=['_main']".to_string(),
]
}
}
impl Default for EmscriptenToolchain {
fn default() -> Self {
Self::new("/opt/emsdk", "3.1.37")
}
}
#[derive(Debug, Clone)]
pub struct ZigCcToolchain {
pub zig_path: PathBuf,
pub target: String,
pub optimization: ZigOptLevel,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ZigOptLevel {
Debug,
ReleaseSafe,
ReleaseFast,
ReleaseSmall,
}
impl ZigOptLevel {
pub fn as_flag(&self) -> &'static str {
match self {
Self::Debug => "-O0",
Self::ReleaseSafe => "-O2",
Self::ReleaseFast => "-O3",
Self::ReleaseSmall => "-Os",
}
}
}
impl ZigCcToolchain {
pub fn new(zig_path: &str, target: &str) -> Self {
Self {
zig_path: PathBuf::from(zig_path),
target: target.to_string(),
optimization: ZigOptLevel::ReleaseFast,
}
}
pub fn build_command(&self, source: &str, output: &str) -> Vec<String> {
vec![
self.zig_path.display().to_string(),
"cc".to_string(),
format!("-target {}", self.target),
self.optimization.as_flag().to_string(),
source.to_string(),
"-o".to_string(),
output.to_string(),
]
}
pub fn compiler_flags(&self) -> Vec<String> {
vec![
format!("--target={}", self.target),
self.optimization.as_flag().to_string(),
]
}
pub fn with_opt_level(mut self, level: ZigOptLevel) -> Self {
self.optimization = level;
self
}
pub fn cross_compile_flags(target: &str) -> Option<Vec<String>> {
match target {
"x86_64-linux-gnu" => Some(vec![
"-target x86_64-linux-gnu".to_string(),
]),
"aarch64-linux-gnu" => Some(vec![
"-target aarch64-linux-gnu".to_string(),
]),
"x86_64-windows-gnu" => Some(vec![
"-target x86_64-windows-gnu".to_string(),
]),
"wasm32-wasi" => Some(vec![
"-target wasm32-wasi".to_string(),
]),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum CxxStandard {
Cxx98,
Cxx03,
Cxx11,
Cxx14,
Cxx17,
Cxx20,
Cxx23,
Cxx26,
C89,
C99,
C11,
C17,
C23,
}
impl CxxStandard {
pub fn flag(&self) -> &'static str {
match self {
Self::Cxx98 => "-std=c++98",
Self::Cxx03 => "-std=c++03",
Self::Cxx11 => "-std=c++11",
Self::Cxx14 => "-std=c++14",
Self::Cxx17 => "-std=c++17",
Self::Cxx20 => "-std=c++20",
Self::Cxx23 => "-std=c++23",
Self::Cxx26 => "-std=c++26",
Self::C89 => "-std=c89",
Self::C99 => "-std=c99",
Self::C11 => "-std=c11",
Self::C17 => "-std=c17",
Self::C23 => "-std=c23",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Sanitizer {
Address,
Memory,
MemoryWithOrigins,
Thread,
Undefined,
Leak,
DataFlow,
SafeStack,
ShadowCallStack,
Hwaddress,
KAddress,
KMemory,
}
impl Sanitizer {
pub fn flag(&self) -> &'static str {
match self {
Self::Address => "-fsanitize=address",
Self::Memory => "-fsanitize=memory",
Self::MemoryWithOrigins => "-fsanitize=memory -fsanitize-memory-track-origins",
Self::Thread => "-fsanitize=thread",
Self::Undefined => "-fsanitize=undefined",
Self::Leak => "-fsanitize=leak",
Self::DataFlow => "-fsanitize=dataflow",
Self::SafeStack => "-fsanitize=safe-stack",
Self::ShadowCallStack => "-fsanitize=shadow-call-stack",
Self::Hwaddress => "-fsanitize=hwaddress",
Self::KAddress => "-fsanitize=kernel-address",
Self::KMemory => "-fsanitize=kernel-memory",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LtoMode {
None,
Thin,
Full,
}
impl LtoMode {
pub fn flag(&self) -> Option<&'static str> {
match self {
Self::None => None,
Self::Thin => Some("-flto=thin"),
Self::Full => Some("-flto"),
}
}
}
#[derive(Debug, Clone)]
pub struct FeatureDetector {
pub supported_standards: Vec<CxxStandard>,
pub supported_sanitizers: Vec<Sanitizer>,
pub lto_supported: bool,
pub thin_lto_supported: bool,
pub split_dwarf_supported: bool,
pub pgo_supported: bool,
pub coverage_supported: bool,
pub xray_supported: bool,
}
impl FeatureDetector {
pub fn new() -> Self {
Self {
supported_standards: vec![
CxxStandard::Cxx11, CxxStandard::Cxx14, CxxStandard::Cxx17,
CxxStandard::Cxx20, CxxStandard::Cxx23,
CxxStandard::C89, CxxStandard::C99, CxxStandard::C11, CxxStandard::C17,
],
supported_sanitizers: vec![
Sanitizer::Address, Sanitizer::Undefined, Sanitizer::Thread,
Sanitizer::Leak, Sanitizer::Memory,
],
lto_supported: true,
thin_lto_supported: true,
split_dwarf_supported: true,
pgo_supported: true,
coverage_supported: true,
xray_supported: true,
}
}
pub fn supports_standard(&self, standard: CxxStandard) -> bool {
self.supported_standards.contains(&standard)
}
pub fn supports_sanitizer(&self, sanitizer: Sanitizer) -> bool {
self.supported_sanitizers.contains(&sanitizer)
}
pub fn max_cxx_standard(&self) -> Option<CxxStandard> {
self.supported_standards
.iter()
.filter(|s| matches!(s, CxxStandard::Cxx98 | CxxStandard::Cxx03 |
CxxStandard::Cxx11 | CxxStandard::Cxx14 | CxxStandard::Cxx17 |
CxxStandard::Cxx20 | CxxStandard::Cxx23 | CxxStandard::Cxx26))
.max()
.copied()
}
pub fn max_c_standard(&self) -> Option<CxxStandard> {
self.supported_standards
.iter()
.filter(|s| matches!(s, CxxStandard::C89 | CxxStandard::C99 |
CxxStandard::C11 | CxxStandard::C17 | CxxStandard::C23))
.max()
.copied()
}
pub fn all_standard_flags(&self) -> Vec<String> {
self.supported_standards.iter().map(|s| s.flag().to_string()).collect()
}
pub fn all_sanitizer_flags(&self) -> Vec<String> {
self.supported_sanitizers.iter().map(|s| s.flag().to_string()).collect()
}
pub fn with_lto(mut self, supported: bool) -> Self {
self.lto_supported = supported;
self
}
pub fn with_thin_lto(mut self, supported: bool) -> Self {
self.thin_lto_supported = supported;
if supported {
self.lto_supported = true;
}
self
}
}
impl Default for FeatureDetector {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct CrossSysroot {
pub sysroot_path: PathBuf,
pub target_triple: String,
pub include_dir: PathBuf,
pub lib_dir: PathBuf,
pub usr_include: PathBuf,
pub usr_lib: PathBuf,
}
impl CrossSysroot {
pub fn new(sysroot: &str, target: &str) -> Self {
let root = PathBuf::from(sysroot);
Self {
sysroot_path: root.clone(),
target_triple: target.to_string(),
include_dir: root.join("usr").join("include"),
lib_dir: root.join("usr").join("lib"),
usr_include: root.join("usr").join("include"),
usr_lib: root.join("usr").join("lib"),
}
}
pub fn common_locations() -> Vec<PathBuf> {
vec![
PathBuf::from("/usr/aarch64-linux-gnu"),
PathBuf::from("/usr/arm-linux-gnueabihf"),
PathBuf::from("/usr/riscv64-linux-gnu"),
PathBuf::from("/usr/i686-linux-gnu"),
PathBuf::from("/usr/powerpc64le-linux-gnu"),
PathBuf::from("/usr/mips64el-linux-gnuabi64"),
]
}
pub fn compiler_flags(&self) -> Vec<String> {
vec![
format!("--target={}", self.target_triple),
format!("--sysroot={}", self.sysroot_path.display()),
format!("-isystem {}", self.include_dir.display()),
format!("-I{}", self.usr_include.display()),
]
}
pub fn linker_flags(&self) -> Vec<String> {
vec![
format!("--sysroot={}", self.sysroot_path.display()),
format!("-L{}", self.lib_dir.display()),
format!("-L{}", self.usr_lib.display()),
]
}
}
impl fmt::Display for CrossSysroot {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "CrossSysroot({})", self.target_triple)
}
}
#[derive(Debug, Clone)]
pub struct MultiarchPaths {
pub base_path: PathBuf,
pub multiarch_tuples: Vec<String>,
}
impl MultiarchPaths {
pub fn new() -> Self {
Self {
base_path: PathBuf::from("/usr/lib"),
multiarch_tuples: Vec::new(),
}
}
pub fn common_tuples() -> Vec<String> {
vec![
"x86_64-linux-gnu".to_string(),
"i386-linux-gnu".to_string(),
"aarch64-linux-gnu".to_string(),
"arm-linux-gnueabihf".to_string(),
"arm-linux-gnueabi".to_string(),
"riscv64-linux-gnu".to_string(),
"powerpc64le-linux-gnu".to_string(),
"s390x-linux-gnu".to_string(),
"mips64el-linux-gnuabi64".to_string(),
]
}
pub fn find_library_paths(&self, tuple: &str) -> Vec<PathBuf> {
vec![
self.base_path.join(tuple),
PathBuf::from("/usr/lib").join(tuple),
PathBuf::from(format!("/lib/{}", tuple)),
]
}
pub fn find_include_paths(&self, tuple: &str) -> Vec<PathBuf> {
vec![
PathBuf::from(format!("/usr/include/{}", tuple)),
PathBuf::from("/usr/include"),
]
}
pub fn add_tuple(&mut self, tuple: &str) {
if !self.multiarch_tuples.contains(&tuple.to_string()) {
self.multiarch_tuples.push(tuple.to_string());
}
}
pub fn ld_library_paths(&self) -> Vec<String> {
self.multiarch_tuples
.iter()
.map(|t| format!("/usr/lib/{}", t))
.collect()
}
pub fn is_valid_tuple(tuple: &str) -> bool {
Self::common_tuples().contains(&tuple.to_string())
}
}
impl Default for MultiarchPaths {
fn default() -> Self {
let mut mp = Self::new();
mp.multiarch_tuples = Self::common_tuples();
mp
}
}
#[derive(Debug, Clone)]
pub struct ToolchainConfig {
pub kind: ToolchainKind,
pub gcc: Option<GccInstallation>,
pub msvc: Option<MsvcInstallation>,
pub android: Option<AndroidNdk>,
pub ios: Option<IosToolchain>,
pub macos: Option<MacOsToolchain>,
pub wasi: Option<WasiSdk>,
pub emscripten: Option<EmscriptenToolchain>,
pub zig: Option<ZigCcToolchain>,
pub sysroot: Option<CrossSysroot>,
pub multiarch: MultiarchPaths,
pub features: FeatureDetector,
pub extra_flags: Vec<String>,
}
impl ToolchainConfig {
pub fn new(kind: ToolchainKind) -> Self {
Self {
kind,
gcc: None,
msvc: None,
android: None,
ios: None,
macos: None,
wasi: None,
emscripten: None,
zig: None,
sysroot: None,
multiarch: MultiarchPaths::default(),
features: FeatureDetector::new(),
extra_flags: Vec::new(),
}
}
pub fn gcc_native() -> Self {
Self {
kind: ToolchainKind::Gcc,
gcc: Some(GccInstallation::default()),
..Self::new(ToolchainKind::Gcc)
}
}
pub fn android_ndk(ndk_path: &str, api: u32) -> Self {
Self {
kind: ToolchainKind::AndroidNdk,
android: Some(AndroidNdk::new(ndk_path, api)),
..Self::new(ToolchainKind::AndroidNdk)
}
}
pub fn ios_sdk() -> Option<Self> {
IosToolchain::from_xcrun("iphoneos").map(|ios| Self {
kind: ToolchainKind::Ios,
ios: Some(ios),
..Self::new(ToolchainKind::Ios)
})
}
pub fn macos_sdk() -> Option<Self> {
MacOsToolchain::from_xcrun().map(|mac| Self {
kind: ToolchainKind::MacOs,
macos: Some(mac),
..Self::new(ToolchainKind::MacOs)
})
}
pub fn compiler_flags(&self) -> Vec<String> {
let mut flags = Vec::new();
match self.kind {
ToolchainKind::Gcc => {
if let Some(ref gcc) = self.gcc {
flags.extend(gcc.compiler_flags());
}
}
ToolchainKind::Msvc => {
if let Some(ref msvc) = self.msvc {
flags.extend(msvc.compiler_flags());
}
}
ToolchainKind::AndroidNdk => {
if let Some(ref ndk) = self.android {
flags.extend(ndk.compiler_flags());
}
}
ToolchainKind::Ios => {
if let Some(ref ios) = self.ios {
flags.extend(ios.compiler_flags());
}
}
ToolchainKind::MacOs => {
if let Some(ref mac) = self.macos {
flags.extend(mac.compiler_flags());
}
}
ToolchainKind::WasmWasiSdk => {
if let Some(ref wasi) = self.wasi {
flags.extend(wasi.compiler_flags());
}
}
ToolchainKind::WasmEmscripten => {
if let Some(ref em) = self.emscripten {
flags.extend(em.compiler_flags());
}
}
ToolchainKind::ZigCc => {
if let Some(ref zig) = self.zig {
flags.extend(zig.compiler_flags());
}
}
_ => {}
}
if let Some(ref sysroot) = self.sysroot {
flags.extend(sysroot.compiler_flags());
}
flags.extend(self.extra_flags.clone());
flags
}
pub fn linker_flags(&self) -> Vec<String> {
let mut flags = Vec::new();
match self.kind {
ToolchainKind::Gcc => {
if let Some(ref gcc) = self.gcc {
flags.extend(gcc.linker_flags());
}
}
ToolchainKind::Msvc => {
if let Some(ref msvc) = self.msvc {
flags.extend(msvc.linker_flags());
}
}
ToolchainKind::AndroidNdk => {
if let Some(ref ndk) = self.android {
flags.extend(ndk.linker_flags());
}
}
ToolchainKind::Ios => {
if let Some(ref ios) = self.ios {
flags.extend(ios.linker_flags());
}
}
ToolchainKind::MacOs => {
if let Some(ref mac) = self.macos {
flags.extend(mac.linker_flags());
}
}
ToolchainKind::WasmWasiSdk => {
if let Some(ref wasi) = self.wasi {
flags.extend(wasi.linker_flags());
}
}
ToolchainKind::WasmEmscripten => {
if let Some(ref em) = self.emscripten {
flags.extend(em.linker_flags());
}
}
_ => {}
}
if let Some(ref sysroot) = self.sysroot {
flags.extend(sysroot.linker_flags());
}
flags
}
pub fn add_flag(&mut self, flag: &str) {
self.extra_flags.push(flag.to_string());
}
pub fn with_sysroot(mut self, sysroot: &str, target: &str) -> Self {
self.sysroot = Some(CrossSysroot::new(sysroot, target));
self
}
}
impl Default for ToolchainConfig {
fn default() -> Self {
Self::new(ToolchainKind::Gcc)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_gcc_installation_new() {
let gcc = GccInstallation::new("/usr/lib/gcc/x86_64-linux-gnu", "x86_64-linux-gnu");
assert_eq!(gcc.triple, "x86_64-linux-gnu");
assert!(!gcc.cross_compile);
}
#[test]
fn test_gcc_compiler_flags() {
let gcc = GccInstallation::new("/usr/lib/gcc/x86_64-linux-gnu", "x86_64-linux-gnu");
let flags = gcc.compiler_flags();
assert!(flags.iter().any(|f| f.contains("-isystem")));
}
#[test]
fn test_gcc_linker_flags() {
let gcc = GccInstallation::new("/usr/lib/gcc/x86_64-linux-gnu", "x86_64-linux-gnu");
let flags = gcc.linker_flags();
assert!(flags.iter().any(|f| f.contains("-lgcc")));
}
#[test]
fn test_gcc_detect_installations() {
let installs = GccInstallation::detect();
assert!(!installs.is_empty());
}
#[test]
fn test_gcc_find_by_triple() {
let gcc = GccInstallation::find_by_triple("aarch64-linux-gnu");
assert!(gcc.is_some());
assert_eq!(gcc.unwrap().triple, "aarch64-linux-gnu");
}
#[test]
fn test_msvc_installation_new() {
let msvc = MsvcInstallation::new("14.3", "C:/VS/2022/Community");
assert_eq!(msvc.vs_version, "14.3");
assert_eq!(msvc.target_arch, MsvcArch::X64);
}
#[test]
fn test_msvc_target_triple() {
let msvc = MsvcInstallation::new("14.3", "C:/VS/2022/Community");
assert_eq!(msvc.target_triple(), "x86_64-pc-windows-msvc");
let msvc32 = MsvcInstallation::new("14.3", "C:/VS/2022/Community")
.with_arch(MsvcArch::X86);
assert_eq!(msvc32.target_triple(), "i686-pc-windows-msvc");
}
#[test]
fn test_msvc_compiler_flags() {
let msvc = MsvcInstallation::new("14.3", "C:/VS/2022/Community");
let flags = msvc.compiler_flags();
assert!(flags.iter().any(|f| f.contains("ms-compatibility")));
}
#[test]
fn test_android_ndk_new() {
let ndk = AndroidNdk::new("/opt/android-ndk", 30);
assert_eq!(ndk.api_level, 30);
assert_eq!(ndk.target_arch, AndroidArch::Arm64);
}
#[test]
fn test_android_ndk_compiler_flags() {
let ndk = AndroidNdk::new("/opt/android-ndk", 30);
let flags = ndk.compiler_flags();
assert!(flags.iter().any(|f| f.contains("ANDROID")));
assert!(flags.iter().any(|f| f.contains("--sysroot")));
}
#[test]
fn test_android_ndk_stl() {
let ndk = AndroidNdk::new("/opt/android-ndk", 30)
.with_stl(AndroidStl::CxxShared);
assert_eq!(ndk.stl.as_flag(), Some("c++_shared"));
}
#[test]
fn test_android_ndk_arch_toolchain_prefix() {
assert_eq!(AndroidArch::Arm.toolchain_prefix(), "armv7a-linux-androideabi");
assert_eq!(AndroidArch::Arm64.toolchain_prefix(), "aarch64-linux-android");
assert_eq!(AndroidArch::X86.toolchain_prefix(), "i686-linux-android");
}
#[test]
fn test_ios_toolchain_from_xcrun() {
let ios = IosToolchain::from_xcrun("iphoneos");
assert!(ios.is_some());
let ios = ios.unwrap();
assert!(!ios.is_simulator);
}
#[test]
fn test_ios_simulator() {
let ios = IosToolchain::from_xcrun("iphonesimulator");
assert!(ios.is_some());
assert!(ios.unwrap().is_simulator);
}
#[test]
fn test_ios_compiler_flags() {
let ios = IosToolchain::from_xcrun("iphoneos").unwrap();
let flags = ios.compiler_flags();
assert!(flags.iter().any(|f| f.contains("-isysroot")));
}
#[test]
fn test_macos_toolchain_from_xcrun() {
let mac = MacOsToolchain::from_xcrun();
assert!(mac.is_some());
}
#[test]
fn test_macos_framework_paths() {
let mac = MacOsToolchain::from_xcrun().unwrap();
let paths = mac.framework_search_paths();
assert!(!paths.is_empty());
assert!(paths[0].contains("Frameworks"));
}
#[test]
fn test_macos_compiler_flags() {
let mac = MacOsToolchain::from_xcrun().unwrap();
let flags = mac.compiler_flags();
assert!(flags.iter().any(|f| f.contains("macosx-version-min")));
}
#[test]
fn test_wasi_sdk_compiler_flags() {
let wasi = WasiSdk::new("/opt/wasi-sdk", "20");
let flags = wasi.compiler_flags();
assert!(flags.iter().any(|f| f.contains("wasm32-wasi")));
}
#[test]
fn test_emscripten_compiler_flags() {
let em = EmscriptenToolchain::new("/opt/emsdk", "3.1.37");
let flags = em.compiler_flags();
assert!(flags.iter().any(|f| f.contains("__EMSCRIPTEN__")));
}
#[test]
fn test_emscripten_linker_flags() {
let em = EmscriptenToolchain::new("/opt/emsdk", "3.1.37");
let flags = em.linker_flags();
assert!(flags.iter().any(|f| f.contains("ALLOW_MEMORY_GROWTH")));
}
#[test]
fn test_zig_cc_new() {
let zig = ZigCcToolchain::new("/usr/local/bin/zig", "x86_64-linux-gnu");
assert_eq!(zig.optimization, ZigOptLevel::ReleaseFast);
}
#[test]
fn test_zig_cc_build_command() {
let zig = ZigCcToolchain::new("/usr/bin/zig", "wasm32-wasi");
let cmd = zig.build_command("main.c", "main.wasm");
assert!(cmd.contains(&"main.c".to_string()));
assert!(cmd.contains(&"main.wasm".to_string()));
assert!(cmd.contains(&"-target".to_string()));
}
#[test]
fn test_zig_cross_compile_flags() {
let flags = ZigCcToolchain::cross_compile_flags("aarch64-linux-gnu");
assert!(flags.is_some());
assert!(flags.unwrap().iter().any(|f| f.contains("aarch64")));
}
#[test]
fn test_feature_detector_standards() {
let fd = FeatureDetector::new();
assert!(fd.supports_standard(CxxStandard::Cxx17));
assert!(fd.supports_standard(CxxStandard::Cxx20));
assert!(fd.supports_standard(CxxStandard::C11));
}
#[test]
fn test_feature_detector_sanitizers() {
let fd = FeatureDetector::new();
assert!(fd.supports_sanitizer(Sanitizer::Address));
assert!(fd.supports_sanitizer(Sanitizer::Undefined));
}
#[test]
fn test_feature_detector_max_standards() {
let fd = FeatureDetector::new();
assert_eq!(fd.max_cxx_standard(), Some(CxxStandard::Cxx23));
assert_eq!(fd.max_c_standard(), Some(CxxStandard::C23));
}
#[test]
fn test_feature_detector_lto() {
let fd = FeatureDetector::new().with_thin_lto(true);
assert!(fd.lto_supported);
assert!(fd.thin_lto_supported);
}
#[test]
fn test_sanitizer_flags() {
assert_eq!(Sanitizer::Address.flag(), "-fsanitize=address");
assert_eq!(Sanitizer::Thread.flag(), "-fsanitize=thread");
assert_eq!(Sanitizer::Undefined.flag(), "-fsanitize=undefined");
}
#[test]
fn test_lto_mode_flags() {
assert_eq!(LtoMode::Full.flag(), Some("-flto"));
assert_eq!(LtoMode::Thin.flag(), Some("-flto=thin"));
assert_eq!(LtoMode::None.flag(), None);
}
#[test]
fn test_cross_sysroot_compiler_flags() {
let sysroot = CrossSysroot::new("/sysroot/aarch64", "aarch64-linux-gnu");
let flags = sysroot.compiler_flags();
assert!(flags.iter().any(|f| f.contains("--sysroot")));
assert!(flags.iter().any(|f| f.contains("aarch64")));
}
#[test]
fn test_cross_sysroot_common_locations() {
let locs = CrossSysroot::common_locations();
assert!(locs.len() >= 5);
}
#[test]
fn test_multiarch_common_tuples() {
let tuples = MultiarchPaths::common_tuples();
assert!(tuples.contains(&"x86_64-linux-gnu".to_string()));
assert!(tuples.contains(&"aarch64-linux-gnu".to_string()));
assert!(tuples.len() >= 8);
}
#[test]
fn test_multiarch_paths_find_library() {
let mp = MultiarchPaths::new();
let paths = mp.find_library_paths("x86_64-linux-gnu");
assert!(!paths.is_empty());
}
#[test]
fn test_multiarch_valid_tuples() {
assert!(MultiarchPaths::is_valid_tuple("x86_64-linux-gnu"));
assert!(!MultiarchPaths::is_valid_tuple("invalid-triple"));
}
#[test]
fn test_toolchain_config_gcc() {
let config = ToolchainConfig::gcc_native();
let flags = config.compiler_flags();
assert!(!flags.is_empty());
}
#[test]
fn test_toolchain_config_android() {
let config = ToolchainConfig::android_ndk("/opt/android-ndk", 33);
let flags = config.compiler_flags();
assert!(flags.iter().any(|f| f.contains("ANDROID")));
}
#[test]
fn test_toolchain_config_with_sysroot() {
let config = ToolchainConfig::new(ToolchainKind::Gcc)
.with_sysroot("/sysroot/arm", "arm-linux-gnueabi");
assert!(config.sysroot.is_some());
let flags = config.compiler_flags();
assert!(flags.iter().any(|f| f.contains("arm")));
}
#[test]
fn test_toolchain_kind_as_str() {
assert_eq!(ToolchainKind::Gcc.as_str(), "GCC");
assert_eq!(ToolchainKind::Msvc.as_str(), "MSVC");
assert_eq!(ToolchainKind::ZigCc.as_str(), "Zig cc");
}
#[test]
fn test_cxx_standard_flags() {
assert_eq!(CxxStandard::Cxx17.flag(), "-std=c++17");
assert_eq!(CxxStandard::Cxx20.flag(), "-std=c++20");
assert_eq!(CxxStandard::C11.flag(), "-std=c11");
}
}