use std::collections::HashMap;
use std::env;
use std::fmt;
use std::fs;
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use std::process::Command as ProcessCommand;
use std::time::{Duration, SystemTime};
use crate::triple::{Triple, OS};
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum X86ToolchainKind {
Gcc,
Clang,
Msvc,
IccClassic,
IccOneApi,
MinGW,
ClangMinGW,
BareMetalGcc,
LlvmFull,
Custom(String),
}
impl X86ToolchainKind {
pub fn as_str(&self) -> &str {
match self {
Self::Gcc => "GCC",
Self::Clang => "LLVM Clang",
Self::Msvc => "MSVC",
Self::IccClassic => "Intel C++ Compiler Classic",
Self::IccOneApi => "Intel oneAPI DPC++",
Self::MinGW => "MinGW-w64",
Self::ClangMinGW => "Clang/MinGW",
Self::BareMetalGcc => "Bare-metal GCC",
Self::LlvmFull => "LLVM Full",
Self::Custom(s) => s,
}
}
pub fn is_gcc_family(&self) -> bool {
matches!(self, Self::Gcc | Self::MinGW | Self::BareMetalGcc)
}
pub fn is_clang_family(&self) -> bool {
matches!(self, Self::Clang | Self::ClangMinGW | Self::LlvmFull)
}
pub fn is_msvc_family(&self) -> bool {
matches!(self, Self::Msvc)
}
pub fn is_intel_family(&self) -> bool {
matches!(self, Self::IccClassic | Self::IccOneApi)
}
pub fn canonical_name(&self) -> &str {
match self {
Self::Gcc => "gcc",
Self::Clang => "clang",
Self::Msvc => "msvc",
Self::IccClassic => "icc",
Self::IccOneApi => "icx",
Self::MinGW => "mingw-gcc",
Self::ClangMinGW => "mingw-clang",
Self::BareMetalGcc => "baremetal-gcc",
Self::LlvmFull => "llvm",
Self::Custom(s) => s.as_str(),
}
}
}
impl fmt::Display for X86ToolchainKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86Abi {
SysV,
MsAbi,
Intel,
GnuIlp32,
BareMetal,
}
impl X86Abi {
pub fn as_str(&self) -> &'static str {
match self {
Self::SysV => "sysv",
Self::MsAbi => "ms",
Self::Intel => "intel",
Self::GnuIlp32 => "gnu-ilp32",
Self::BareMetal => "baremetal",
}
}
pub fn is_windows(&self) -> bool {
matches!(self, Self::MsAbi)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum X86WordSize {
Bits16,
Bits32,
Bits64,
X32,
}
impl X86WordSize {
pub fn as_str(&self) -> &'static str {
match self {
Self::Bits16 => "16",
Self::Bits32 => "32",
Self::Bits64 => "64",
Self::X32 => "x32",
}
}
pub fn pointer_width(&self) -> u32 {
match self {
Self::Bits16 => 2,
Self::Bits32 => 4,
Self::Bits64 => 8,
Self::X32 => 4,
}
}
pub fn m_flag(&self) -> &'static str {
match self {
Self::Bits16 => "-m16",
Self::Bits32 => "-m32",
Self::Bits64 => "-m64",
Self::X32 => "-mx32",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86ToolKind {
Cc,
Cxx,
Assembler,
Linker,
Archiver,
ObjCopy,
ObjDump,
Nm,
Strip,
Size,
ReadElf,
Debugger,
Profiler,
Ranlib,
DllTool,
ResourceCompiler,
LtoPlugin,
}
impl X86ToolKind {
pub fn display_name(&self) -> &'static str {
match self {
Self::Cc => "C Compiler",
Self::Cxx => "C++ Compiler",
Self::Assembler => "Assembler",
Self::Linker => "Linker",
Self::Archiver => "Archiver",
Self::ObjCopy => "objcopy",
Self::ObjDump => "objdump",
Self::Nm => "nm",
Self::Strip => "strip",
Self::Size => "size",
Self::ReadElf => "readelf",
Self::Debugger => "Debugger",
Self::Profiler => "Profiler",
Self::Ranlib => "ranlib",
Self::DllTool => "dlltool",
Self::ResourceCompiler => "Resource Compiler",
Self::LtoPlugin => "LTO Plugin",
}
}
}
#[derive(Debug, Clone)]
pub struct X86Toolchain {
pub kind: X86ToolchainKind,
pub description: String,
pub target_triple: String,
pub abi: X86Abi,
pub word_size: X86WordSize,
pub install_prefix: PathBuf,
pub bin_dir: PathBuf,
pub lib_dir: PathBuf,
pub include_dir: PathBuf,
pub cxx_include_dir: Option<PathBuf>,
pub sysroot: Option<PathBuf>,
pub cc_path: Option<PathBuf>,
pub cxx_path: Option<PathBuf>,
pub asm_path: Option<PathBuf>,
pub ld_path: Option<PathBuf>,
pub ld_gold_path: Option<PathBuf>,
pub ld_lld_path: Option<PathBuf>,
pub link_exe_path: Option<PathBuf>,
pub lld_link_path: Option<PathBuf>,
pub ar_path: Option<PathBuf>,
pub llvm_ar_path: Option<PathBuf>,
pub lib_exe_path: Option<PathBuf>,
pub ranlib_path: Option<PathBuf>,
pub objcopy_path: Option<PathBuf>,
pub objdump_path: Option<PathBuf>,
pub nm_path: Option<PathBuf>,
pub strip_path: Option<PathBuf>,
pub size_path: Option<PathBuf>,
pub readelf_path: Option<PathBuf>,
pub debugger_path: Option<PathBuf>,
pub profiler_path: Option<PathBuf>,
pub dlltool_path: Option<PathBuf>,
pub windres_path: Option<PathBuf>,
pub lto_plugin_path: Option<PathBuf>,
pub compiler_version: String,
pub compiler_version_major: u32,
pub compiler_version_minor: u32,
pub compiler_version_patch: u32,
pub asm_version: Option<String>,
pub ld_version: Option<String>,
pub crt_begin: Option<PathBuf>,
pub crt_end: Option<PathBuf>,
pub crt_i: Option<PathBuf>,
pub crt_n: Option<PathBuf>,
pub crt_begin_s: Option<PathBuf>,
pub crt_end_s: Option<PathBuf>,
pub libgcc_path: Option<PathBuf>,
pub libgcc_eh_path: Option<PathBuf>,
pub libgcc_s_path: Option<PathBuf>,
pub compiler_rt_path: Option<PathBuf>,
pub libunwind_path: Option<PathBuf>,
pub multilib_os_dir: Option<String>,
pub multilib_gcc_dir: Option<String>,
pub multilib_variants: Vec<X86MultilibVariant>,
pub multiarch_tuple: Option<String>,
pub default_c_standard: String,
pub supported_c_standards: Vec<String>,
pub default_cxx_standard: String,
pub supported_cxx_standards: Vec<String>,
pub supported_sanitizers: Vec<String>,
pub lto_supported: bool,
pub thin_lto_supported: bool,
pub pgo_supported: bool,
pub coverage_supported: bool,
pub split_dwarf_supported: bool,
pub color_diagnostics_supported: bool,
pub response_files_supported: bool,
pub default_cpu: String,
pub supported_cpus: Vec<String>,
pub default_fp_math: String,
pub supported_fp_maths: Vec<String>,
pub env_vars: HashMap<String, String>,
pub is_cross: bool,
pub is_installed: bool,
pub detection_time: SystemTime,
}
impl X86Toolchain {
pub fn new(kind: X86ToolchainKind) -> Self {
Self {
kind,
description: String::new(),
target_triple: "x86_64-unknown-linux-gnu".to_string(),
abi: X86Abi::SysV,
word_size: X86WordSize::Bits64,
install_prefix: PathBuf::new(),
bin_dir: PathBuf::new(),
lib_dir: PathBuf::new(),
include_dir: PathBuf::new(),
cxx_include_dir: None,
sysroot: None,
cc_path: None,
cxx_path: None,
asm_path: None,
ld_path: None,
ld_gold_path: None,
ld_lld_path: None,
link_exe_path: None,
lld_link_path: None,
ar_path: None,
llvm_ar_path: None,
lib_exe_path: None,
ranlib_path: None,
objcopy_path: None,
objdump_path: None,
nm_path: None,
strip_path: None,
size_path: None,
readelf_path: None,
debugger_path: None,
profiler_path: None,
dlltool_path: None,
windres_path: None,
lto_plugin_path: None,
compiler_version: "0.0.0".to_string(),
compiler_version_major: 0,
compiler_version_minor: 0,
compiler_version_patch: 0,
asm_version: None,
ld_version: None,
crt_begin: None,
crt_end: None,
crt_i: None,
crt_n: None,
crt_begin_s: None,
crt_end_s: None,
libgcc_path: None,
libgcc_eh_path: None,
libgcc_s_path: None,
compiler_rt_path: None,
libunwind_path: None,
multilib_os_dir: None,
multilib_gcc_dir: None,
multilib_variants: Vec::new(),
multiarch_tuple: None,
default_c_standard: "gnu17".to_string(),
supported_c_standards: vec![
"c89".into(),
"c90".into(),
"c99".into(),
"c11".into(),
"c17".into(),
"c18".into(),
"c23".into(),
"c2x".into(),
"gnu89".into(),
"gnu90".into(),
"gnu99".into(),
"gnu11".into(),
"gnu17".into(),
"gnu18".into(),
"gnu23".into(),
],
default_cxx_standard: "gnu++17".to_string(),
supported_cxx_standards: vec![
"c++98".into(),
"c++03".into(),
"c++11".into(),
"c++14".into(),
"c++17".into(),
"c++20".into(),
"c++23".into(),
"c++26".into(),
"gnu++98".into(),
"gnu++03".into(),
"gnu++11".into(),
"gnu++14".into(),
"gnu++17".into(),
"gnu++20".into(),
"gnu++23".into(),
"gnu++26".into(),
],
supported_sanitizers: vec![
"address".into(),
"undefined".into(),
"thread".into(),
"leak".into(),
"memory".into(),
],
lto_supported: true,
thin_lto_supported: true,
pgo_supported: true,
coverage_supported: true,
split_dwarf_supported: true,
color_diagnostics_supported: true,
response_files_supported: true,
default_cpu: "x86-64".to_string(),
supported_cpus: Self::known_x86_cpus(),
default_fp_math: "sse".to_string(),
supported_fp_maths: vec!["387".into(), "sse".into(), "sse+387".into()],
env_vars: HashMap::new(),
is_cross: false,
is_installed: false,
detection_time: SystemTime::now(),
}
}
fn known_x86_cpus() -> Vec<String> {
vec![
"i386".into(),
"i486".into(),
"i586".into(),
"i686".into(),
"pentium".into(),
"pentium-mmx".into(),
"pentiumpro".into(),
"pentium2".into(),
"pentium3".into(),
"pentium3m".into(),
"pentium4".into(),
"pentium4m".into(),
"prescott".into(),
"nocona".into(),
"core2".into(),
"nehalem".into(),
"westmere".into(),
"sandybridge".into(),
"ivybridge".into(),
"haswell".into(),
"broadwell".into(),
"skylake".into(),
"skylake-avx512".into(),
"cannonlake".into(),
"icelake-client".into(),
"icelake-server".into(),
"cascadelake".into(),
"cooperlake".into(),
"tigerlake".into(),
"sapphirerapids".into(),
"alderlake".into(),
"raptorlake".into(),
"meteorlake".into(),
"graniterapids".into(),
"sierraforest".into(),
"grandridge".into(),
"arrowlake".into(),
"lunarlake".into(),
"pantherlake".into(),
"k6".into(),
"k6-2".into(),
"k6-3".into(),
"athlon".into(),
"athlon-tbird".into(),
"athlon-xp".into(),
"athlon-mp".into(),
"athlon64".into(),
"athlon-fx".into(),
"opteron".into(),
"k8".into(),
"k8-sse3".into(),
"barcelona".into(),
"bdver1".into(),
"bdver2".into(),
"bdver3".into(),
"bdver4".into(),
"btver1".into(),
"btver2".into(),
"znver1".into(),
"znver2".into(),
"znver3".into(),
"znver4".into(),
"znver5".into(),
"x86-64".into(),
"x86-64-v2".into(),
"x86-64-v3".into(),
"x86-64-v4".into(),
"generic".into(),
"native".into(),
]
}
pub fn with_target_triple(mut self, triple: &str) -> Self {
self.target_triple = triple.to_string();
self
}
pub fn with_word_size(mut self, ws: X86WordSize) -> Self {
self.word_size = ws;
self
}
pub fn with_abi(mut self, abi: X86Abi) -> Self {
self.abi = abi;
self
}
pub fn with_cross(mut self, cross: bool) -> Self {
self.is_cross = cross;
self
}
pub fn with_sysroot(mut self, sysroot: Option<PathBuf>) -> Self {
self.sysroot = sysroot;
self
}
pub fn supports_shared_libraries(&self) -> bool {
self.crt_begin_s.is_some()
&& self.crt_end_s.is_some()
&& (self.libgcc_s_path.is_some()
|| self.compiler_rt_path.is_some()
|| self.kind.is_msvc_family())
}
pub fn supports_static_linking(&self) -> bool {
self.libgcc_path.is_some() || self.compiler_rt_path.is_some() || self.kind.is_msvc_family()
}
pub fn resolve_cc_path(&self) -> Option<PathBuf> {
if let Some(ref p) = self.cc_path {
if p.as_os_str().is_empty() {
return None;
}
return Some(p.clone());
}
match self.kind {
X86ToolchainKind::Gcc | X86ToolchainKind::MinGW | X86ToolchainKind::BareMetalGcc => {
self.resolve_tool("gcc")
}
X86ToolchainKind::Clang | X86ToolchainKind::LlvmFull => self.resolve_tool("clang"),
X86ToolchainKind::ClangMinGW => self
.resolve_tool("x86_64-w64-mingw32-clang")
.or_else(|| self.resolve_tool("clang")),
X86ToolchainKind::Msvc => self.resolve_tool("cl.exe"),
X86ToolchainKind::IccClassic => self.resolve_tool("icc"),
X86ToolchainKind::IccOneApi => self.resolve_tool("icx"),
X86ToolchainKind::Custom(_) => None,
}
}
pub fn resolve_cxx_path(&self) -> Option<PathBuf> {
if let Some(ref p) = self.cxx_path {
if p.as_os_str().is_empty() {
return None;
}
return Some(p.clone());
}
match self.kind {
X86ToolchainKind::Gcc | X86ToolchainKind::MinGW | X86ToolchainKind::BareMetalGcc => {
self.resolve_tool("g++")
}
X86ToolchainKind::Clang | X86ToolchainKind::LlvmFull => self.resolve_tool("clang++"),
X86ToolchainKind::ClangMinGW => self
.resolve_tool("x86_64-w64-mingw32-clang++")
.or_else(|| self.resolve_tool("clang++")),
X86ToolchainKind::Msvc => self.resolve_tool("cl.exe"),
X86ToolchainKind::IccClassic => self.resolve_tool("icpc"),
X86ToolchainKind::IccOneApi => self.resolve_tool("icpx"),
X86ToolchainKind::Custom(_) => None,
}
}
fn resolve_tool(&self, name: &str) -> Option<PathBuf> {
if !self.bin_dir.as_os_str().is_empty() {
let candidate = self.bin_dir.join(name);
if candidate.exists() {
return Some(candidate);
}
let candidate_exe = self.bin_dir.join(format!("{}.exe", name));
if candidate_exe.exists() {
return Some(candidate_exe);
}
}
which_in_path(name)
}
pub fn default_compiler_flags(&self) -> Vec<String> {
let mut flags: Vec<String> = Vec::new();
flags.push(self.word_size.m_flag().to_string());
if self.is_cross {
flags.push(format!("--target={}", self.target_triple));
}
match self.kind {
X86ToolchainKind::Gcc | X86ToolchainKind::BareMetalGcc | X86ToolchainKind::MinGW => {
if let Some(ref sysroot) = self.sysroot {
flags.push(format!("--sysroot={}", sysroot.display()));
}
if let Some(ref ml) = self.multilib_os_dir {
flags.push(format!("-B{}", self.lib_dir.join(ml).display()));
}
flags.push(format!("-B{}", self.lib_dir.display()));
}
X86ToolchainKind::Clang | X86ToolchainKind::LlvmFull | X86ToolchainKind::ClangMinGW => {
if let Some(ref gcc_install) = self.find_gcc_installation() {
flags.push(format!("--gcc-toolchain={}", gcc_install.display()));
}
if let Some(ref sysroot) = self.sysroot {
flags.push(format!("--sysroot={}", sysroot.display()));
}
if self.kind == X86ToolchainKind::ClangMinGW {
flags.push("--target=x86_64-w64-mingw32".to_string());
}
}
X86ToolchainKind::Msvc => {
flags.push(format!("--target={}", self.target_triple));
flags.push("-fms-compatibility".to_string());
flags.push("-fms-extensions".to_string());
flags.push("-fdelayed-template-parsing".to_string());
}
X86ToolchainKind::IccClassic | X86ToolchainKind::IccOneApi => {
flags.push("-xHost".to_string());
if let Some(ref sysroot) = self.sysroot {
flags.push(format!("--sysroot={}", sysroot.display()));
}
}
X86ToolchainKind::Custom(_) => {}
}
flags
}
pub fn default_linker_flags(&self) -> Vec<String> {
let mut flags: Vec<String> = Vec::new();
flags.push(self.word_size.m_flag().to_string());
match self.kind {
X86ToolchainKind::Gcc | X86ToolchainKind::BareMetalGcc | X86ToolchainKind::MinGW => {
if let Some(ref ml) = self.multilib_os_dir {
flags.push(format!("-L{}", self.lib_dir.join(ml).display()));
}
flags.push(format!("-L{}", self.lib_dir.display()));
}
X86ToolchainKind::Clang | X86ToolchainKind::LlvmFull => {
if let Some(ref sysroot) = self.sysroot {
flags.push(format!("--sysroot={}", sysroot.display()));
}
}
X86ToolchainKind::Msvc => {
if let Some(ref l) = self.lib_dir.as_os_str().to_str() {
flags.push(format!("/LIBPATH:{}", l));
}
}
_ => {}
}
flags
}
fn find_gcc_installation(&self) -> Option<PathBuf> {
let candidates = vec![
"/usr/lib/gcc/x86_64-linux-gnu",
"/usr/lib/gcc/x86_64-pc-linux-gnu",
"/usr/lib/gcc/x86_64-redhat-linux",
"/usr/lib/gcc/x86_64-suse-linux",
];
for c in &candidates {
let p = PathBuf::from(c);
if p.exists() {
if let Ok(entries) = fs::read_dir(&p) {
let mut versions: Vec<PathBuf> = entries
.filter_map(|e| e.ok())
.map(|e| e.path())
.filter(|p| p.is_dir())
.collect();
if !versions.is_empty() {
versions.sort();
return Some(versions.last().unwrap().clone());
}
}
return Some(p);
}
}
None
}
pub fn is_usable(&self) -> bool {
let has_cc = self.resolve_cc_path().is_some();
let has_asm = self.resolve_tool("as").is_some() || self.resolve_tool("llvm-mc").is_some();
let has_ld = self.resolve_tool("ld").is_some()
|| self.resolve_tool("ld.lld").is_some()
|| self.resolve_tool("link.exe").is_some();
has_cc && has_asm && has_ld
}
pub fn supports_c_standard(&self, std: &str) -> bool {
self.supported_c_standards.iter().any(|s| s == std)
}
pub fn supports_cxx_standard(&self, std: &str) -> bool {
self.supported_cxx_standards.iter().any(|s| s == std)
}
pub fn supports_sanitizer(&self, sanitizer: &str) -> bool {
self.supported_sanitizers.iter().any(|s| s == sanitizer)
}
pub fn add_multilib_variant(&mut self, variant: X86MultilibVariant) {
if !self.multilib_variants.contains(&variant) {
self.multilib_variants.push(variant);
}
}
}
impl Default for X86Toolchain {
fn default() -> Self {
Self::new(X86ToolchainKind::Gcc)
}
}
impl fmt::Display for X86Toolchain {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} ({}) — {}",
self.description,
self.target_triple,
self.kind.as_str()
)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct X86MultilibVariant {
pub dir_name: String,
pub flags: Vec<String>,
pub os_dir: String,
pub description: String,
}
impl X86MultilibVariant {
pub fn new(dir: &str, flags: Vec<String>, os_dir: &str, desc: &str) -> Self {
Self {
dir_name: dir.to_string(),
flags,
os_dir: os_dir.to_string(),
description: desc.to_string(),
}
}
pub fn standard_x86_64_variants() -> Vec<Self> {
vec![
Self::new(".", vec!["-m64".to_string()], "../lib64", "64-bit default"),
Self::new("32", vec!["-m32".to_string()], "../lib32", "32-bit (i386)"),
Self::new("x32", vec!["-mx32".to_string()], "../libx32", "x32 ABI"),
]
}
}
pub struct X86ToolchainDetector {
pub extra_search_paths: Vec<PathBuf>,
pub deep_scan: bool,
pub trust_env: bool,
pub host_triple: String,
pub cache: HashMap<String, Vec<X86Toolchain>>,
pub word_size_filter: Option<X86WordSize>,
}
impl X86ToolchainDetector {
pub fn new() -> Self {
Self {
extra_search_paths: Vec::new(),
deep_scan: false,
trust_env: true,
host_triple: detect_host_triple(),
cache: HashMap::new(),
word_size_filter: None,
}
}
pub fn detect_all(&mut self) -> Vec<X86Toolchain> {
let cache_key = "all".to_string();
if let Some(cached) = self.cache.get(&cache_key) {
return cached.clone();
}
let mut toolchains: Vec<X86Toolchain> = Vec::new();
toolchains.extend(self.detect_gcc_installations());
toolchains.extend(self.detect_clang_installations());
if cfg!(target_os = "windows") || self.deep_scan {
toolchains.extend(self.detect_msvc_installations());
}
toolchains.extend(self.detect_icc_installations());
toolchains.extend(self.detect_mingw_installations());
if self.deep_scan {
toolchains.extend(self.detect_cross_toolchains());
}
if let Some(ref ws) = self.word_size_filter {
toolchains.retain(|tc| tc.word_size == *ws);
}
if !self.deep_scan {
toolchains.retain(|tc| tc.is_usable());
}
toolchains.sort_by(|a, b| {
let pa = toolchain_priority_score(a);
let pb = toolchain_priority_score(b);
pb.cmp(&pa)
});
self.cache.insert(cache_key, toolchains.clone());
toolchains
}
pub fn detect_gcc_installations(&self) -> Vec<X86Toolchain> {
let mut results = Vec::new();
let search_roots = self.gcc_search_roots();
for root in &search_roots {
if !root.exists() {
continue;
}
let gcc_base = root.join("lib").join("gcc");
if let Ok(entries) = fs::read_dir(&gcc_base) {
for triple_entry in entries.filter_map(|e| e.ok()) {
let triple_dir = triple_entry.path();
if !triple_dir.is_dir() {
continue;
}
let triple_name = triple_dir
.file_name()
.map(|n| n.to_string_lossy().to_string())
.unwrap_or_default();
if !triple_name.contains("linux") && !triple_name.contains("gnu") {
continue;
}
if let Ok(version_entries) = fs::read_dir(&triple_dir) {
for ver_entry in version_entries.filter_map(|e| e.ok()) {
let ver_dir = ver_entry.path();
if !ver_dir.is_dir() {
continue;
}
let version_str = ver_dir
.file_name()
.map(|n| n.to_string_lossy().to_string())
.unwrap_or_default();
if !version_str
.chars()
.next()
.map(|c| c.is_ascii_digit())
.unwrap_or(false)
{
continue;
}
let mut tc = X86Toolchain::new(X86ToolchainKind::Gcc);
tc.description = format!("GCC {} ({})", version_str, triple_name);
tc.target_triple = triple_name.clone();
tc.install_prefix = root.clone();
tc.bin_dir = root.join("bin");
tc.lib_dir = ver_dir.clone();
tc.include_dir = root.join("include");
tc.cxx_include_dir = root.join("include/c++").join(&version_str).into();
tc.multiarch_tuple = Some(triple_name.clone());
let parts: Vec<&str> = version_str.split('.').collect();
if parts.len() >= 1 {
tc.compiler_version_major = parts[0].parse().unwrap_or(0);
}
if parts.len() >= 2 {
tc.compiler_version_minor = parts[1].parse().unwrap_or(0);
}
if parts.len() >= 3 {
tc.compiler_version_patch = parts[2].parse().unwrap_or(0);
}
tc.compiler_version = version_str.clone();
tc.crt_begin = find_file(&ver_dir, "crtbegin.o");
tc.crt_end = find_file(&ver_dir, "crtend.o");
tc.crt_i = find_file(&ver_dir, "crti.o");
tc.crt_n = find_file(&ver_dir, "crtn.o");
tc.crt_begin_s = find_file(&ver_dir, "crtbeginS.o");
tc.crt_end_s = find_file(&ver_dir, "crtendS.o");
tc.libgcc_path = find_file(&ver_dir, "libgcc.a");
tc.libgcc_eh_path = find_file(&ver_dir, "libgcc_eh.a");
tc.libgcc_s_path = find_file(&ver_dir, "libgcc_s.so");
tc.multilib_variants = X86MultilibVariant::standard_x86_64_variants();
tc.multiarch_tuple = Some(triple_name.clone());
tc.lto_supported = version_str
.split('.')
.next()
.map(|m| m.parse::<u32>().unwrap_or(0) >= 4)
.unwrap_or(false);
tc.pgo_supported = true;
tc.coverage_supported = version_str
.split('.')
.next()
.map(|m| m.parse::<u32>().unwrap_or(0) >= 8)
.unwrap_or(false);
tc.is_installed = tc.is_usable();
tc.detection_time = SystemTime::now();
if self.trust_env {
tc.env_vars = self.collect_gcc_env(&tc);
}
results.push(tc);
}
}
}
}
}
if self.deep_scan {
results.extend(self.deep_detect_gcc());
}
results
}
fn gcc_search_roots(&self) -> Vec<PathBuf> {
let mut roots = vec![
PathBuf::from("/usr"),
PathBuf::from("/usr/local"),
PathBuf::from("/opt/gcc"),
PathBuf::from("/usr/lib/gcc"),
];
for extra in &self.extra_search_paths {
roots.push(extra.clone());
}
if self.trust_env {
if let Ok(gcc_root) = env::var("GCC_ROOT") {
roots.push(PathBuf::from(gcc_root));
}
if let Ok(cc_path) = env::var("CC") {
if let Some(parent) = Path::new(&cc_path).parent() {
if let Some(grandparent) = parent.parent() {
roots.push(grandparent.to_path_buf());
}
}
}
}
roots
}
fn deep_detect_gcc(&self) -> Vec<X86Toolchain> {
let mut results = Vec::new();
let extra_paths = vec![
"/opt/rh/gcc-toolset-13/root/usr",
"/opt/rh/gcc-toolset-12/root/usr",
"/opt/rh/gcc-toolset-11/root/usr",
"/opt/rh/gcc-toolset-10/root/usr",
"/usr/lib/gcc-snapshot",
"/snap/gcc/current",
"/usr/lib64/gcc",
"/usr/lib32/gcc",
"/usr/libx32/gcc",
];
for path_str in &extra_paths {
let root = PathBuf::from(path_str);
if root.exists() {
let gcc_base = root.join("lib").join("gcc");
if let Ok(entries) = fs::read_dir(&gcc_base) {
for triple_entry in entries.filter_map(|e| e.ok()) {
let triple_dir = triple_entry.path();
if triple_dir.is_dir() {
if let Ok(ver_entries) = fs::read_dir(&triple_dir) {
for ver_entry in ver_entries.filter_map(|e| e.ok()) {
let ver_dir = ver_entry.path();
if ver_dir.is_dir() {
let version_str = ver_dir
.file_name()
.map(|n| n.to_string_lossy().to_string())
.unwrap_or_default();
if version_str
.chars()
.next()
.map(|c| c.is_ascii_digit())
.unwrap_or(false)
{
let triple_name = triple_dir
.file_name()
.map(|n| n.to_string_lossy().to_string())
.unwrap_or_default();
let mut tc = X86Toolchain::new(X86ToolchainKind::Gcc);
tc.description = format!(
"GCC {} ({}) [deep]",
version_str, triple_name
);
tc.target_triple = triple_name;
tc.install_prefix = root.clone();
tc.bin_dir = root.join("bin");
tc.lib_dir = ver_dir.clone();
tc.compiler_version = version_str;
tc.is_installed = tc.is_usable();
tc.detection_time = SystemTime::now();
results.push(tc);
}
}
}
}
}
}
}
}
}
results
}
pub fn detect_clang_installations(&self) -> Vec<X86Toolchain> {
let mut results = Vec::new();
let search_paths = self.clang_search_paths();
for prefix in &search_paths {
let bin_dir = prefix.join("bin");
let clang_path = bin_dir.join("clang");
let clangxx_path = bin_dir.join("clang++");
if clang_path.exists() || clangxx_path.exists() {
let version = self.detect_clang_version(&bin_dir);
let mut tc = X86Toolchain::new(X86ToolchainKind::Clang);
tc.install_prefix = prefix.clone();
tc.bin_dir = bin_dir.clone();
tc.lib_dir = prefix.join("lib");
tc.include_dir = prefix
.join("lib")
.join("clang")
.join(&version)
.join("include");
tc.compiler_version = version.clone();
let parts: Vec<&str> = version.split('.').collect();
if parts.len() >= 1 {
tc.compiler_version_major = parts[0].parse().unwrap_or(0);
}
if parts.len() >= 2 {
tc.compiler_version_minor = parts[1].parse().unwrap_or(0);
}
if parts.len() >= 3 {
tc.compiler_version_patch = parts[2].parse().unwrap_or(0);
}
tc.description = format!("LLVM Clang {} ({})", version, self.host_triple);
tc.target_triple = self.host_triple.clone();
if let Some(ref gcc_install) = self.find_gcc_for_clang() {
tc.crt_begin = gcc_install.crt_begin.clone();
tc.crt_end = gcc_install.crt_end.clone();
tc.crt_i = gcc_install.crt_i.clone();
tc.crt_n = gcc_install.crt_n.clone();
tc.libgcc_path = gcc_install.libgcc_path.clone();
tc.libgcc_eh_path = gcc_install.libgcc_eh_path.clone();
tc.libgcc_s_path = gcc_install.libgcc_s_path.clone();
}
tc.compiler_rt_path = find_file(
&prefix
.join("lib")
.join("clang")
.join(&version)
.join("lib")
.join("linux"),
"libclang_rt.builtins-x86_64.a",
);
tc.lto_supported = true;
tc.thin_lto_supported = true;
tc.split_dwarf_supported = version
.split('.')
.next()
.map(|m| m.parse::<u32>().unwrap_or(0) >= 7)
.unwrap_or(false);
tc.is_installed = tc.is_usable();
tc.detection_time = SystemTime::now();
self.enrich_with_llvm_tools(&mut tc);
results.push(tc);
}
for ver in 10..=20 {
let ver_clang = bin_dir.join(format!("clang-{}", ver));
if ver_clang.exists() {
let mut tc = X86Toolchain::new(X86ToolchainKind::Clang);
tc.install_prefix = prefix.clone();
tc.bin_dir = bin_dir.clone();
tc.lib_dir = prefix.join("lib");
tc.compiler_version = ver.to_string();
tc.compiler_version_major = ver;
tc.description = format!("LLVM Clang {} ({})", ver, self.host_triple);
tc.target_triple = self.host_triple.clone();
tc.include_dir = prefix
.join("lib")
.join("clang")
.join(&ver.to_string())
.join("include");
tc.is_installed = tc.is_usable();
tc.detection_time = SystemTime::now();
self.enrich_with_llvm_tools(&mut tc);
results.push(tc);
break; }
}
}
results
}
fn clang_search_paths(&self) -> Vec<PathBuf> {
let mut paths = vec![
PathBuf::from("/usr"),
PathBuf::from("/usr/local"),
PathBuf::from("/opt/llvm"),
];
for ver in (10..=20).rev() {
paths.push(PathBuf::from(format!("/usr/lib/llvm-{}", ver)));
paths.push(PathBuf::from(format!("/opt/llvm-{}", ver)));
}
if self.trust_env {
if let Ok(llvm_dir) = env::var("LLVM_DIR") {
paths.push(PathBuf::from(llvm_dir));
}
if let Ok(clang_path) = env::var("CLANG_PATH") {
if let Some(parent) = Path::new(&clang_path).parent() {
if let Some(grandparent) = parent.parent() {
paths.push(grandparent.to_path_buf());
}
}
}
}
paths.extend(self.extra_search_paths.clone());
paths
}
fn detect_clang_version(&self, bin_dir: &Path) -> String {
let clang_bin = bin_dir.join("clang");
if let Ok(output) = ProcessCommand::new(&clang_bin).arg("--version").output() {
let stdout = String::from_utf8_lossy(&output.stdout);
for line in stdout.lines() {
if line.contains("clang version") {
let parts: Vec<&str> = line.split_whitespace().collect();
for (i, part) in parts.iter().enumerate() {
if *part == "version" && i + 1 < parts.len() {
return parts[i + 1].to_string();
}
}
}
}
}
for ver in (10..=20).rev() {
let ver_bin = bin_dir.join(format!("clang-{}", ver));
if ver_bin.exists() {
return ver.to_string();
}
}
"unknown".to_string()
}
fn find_gcc_for_clang(&self) -> Option<X86Toolchain> {
let gcc_installs = self.detect_gcc_installations();
gcc_installs.into_iter().next()
}
fn enrich_with_llvm_tools(&self, tc: &mut X86Toolchain) {
let bin = &tc.bin_dir;
for name in &["ld.lld", "lld", "ld64.lld"] {
let p = bin.join(name);
if p.exists() {
tc.ld_lld_path = Some(p);
break;
}
}
for name in &["llvm-ar", "llvm-ar-18", "llvm-ar-17", "llvm-ar-16"] {
let p = bin.join(name);
if p.exists() {
tc.llvm_ar_path = Some(p);
break;
}
}
let llvm_objcopy = bin.join("llvm-objcopy");
if llvm_objcopy.exists() {
tc.objcopy_path = Some(llvm_objcopy);
}
let llvm_objdump = bin.join("llvm-objdump");
if llvm_objdump.exists() {
tc.objdump_path = Some(llvm_objdump);
}
let llvm_nm = bin.join("llvm-nm");
if llvm_nm.exists() {
tc.nm_path = Some(llvm_nm);
}
let llvm_strip = bin.join("llvm-strip");
if llvm_strip.exists() {
tc.strip_path = Some(llvm_strip);
}
let llvm_size = bin.join("llvm-size");
if llvm_size.exists() {
tc.size_path = Some(llvm_size);
}
let llvm_readelf = bin.join("llvm-readelf");
if llvm_readelf.exists() {
tc.readelf_path = Some(llvm_readelf);
}
let llvm_dlltool = bin.join("llvm-dlltool");
if llvm_dlltool.exists() {
tc.dlltool_path = Some(llvm_dlltool);
}
let lld_link = bin.join("lld-link");
if lld_link.exists() {
tc.lld_link_path = Some(lld_link);
}
}
pub fn detect_msvc_installations(&self) -> Vec<X86Toolchain> {
let mut results = Vec::new();
let vs_paths = self.msvc_search_paths();
for vs_root in &vs_paths {
if !vs_root.exists() {
continue;
}
let vc_dir = vs_root.join("VC").join("Tools").join("MSVC");
if let Ok(entries) = fs::read_dir(&vc_dir) {
for entry in entries.filter_map(|e| e.ok()) {
let ver_dir = entry.path();
if !ver_dir.is_dir() {
continue;
}
let version_str = entry.file_name().to_string_lossy().to_string();
if !version_str.contains('.') {
continue;
}
for host in &["x64", "x86"] {
let bin_dir = ver_dir
.join("bin")
.join(format!("Host{}", host))
.join("x64");
if bin_dir.exists() {
let mut tc = X86Toolchain::new(X86ToolchainKind::Msvc);
tc.description = format!("MSVC {} ({})", version_str, host);
tc.target_triple = "x86_64-pc-windows-msvc".to_string();
tc.install_prefix = vs_root.clone();
tc.bin_dir = bin_dir.clone();
tc.lib_dir = ver_dir.join("lib").join("x64");
tc.include_dir = ver_dir.join("include");
tc.compiler_version = version_str.clone();
let parts: Vec<&str> = version_str.split('.').collect();
if parts.len() >= 1 {
tc.compiler_version_major = parts[0].parse().unwrap_or(0);
}
if parts.len() >= 2 {
tc.compiler_version_minor = parts[1].parse().unwrap_or(0);
}
tc.abi = X86Abi::MsAbi;
tc.is_installed = true;
tc.detection_time = SystemTime::now();
let link_exe = bin_dir.join("link.exe");
if link_exe.exists() {
tc.link_exe_path = Some(link_exe);
}
let lib_exe = bin_dir.join("lib.exe");
if lib_exe.exists() {
tc.lib_exe_path = Some(lib_exe);
}
if let Some(sdk) = self.detect_windows_sdk() {
tc.sysroot = Some(sdk.sdk_path.clone());
if let Some(ref sdk_include) = sdk.ucrt_include {
tc.include_dir = sdk_include.clone();
}
}
tc.supported_c_standards =
vec!["c89".into(), "c99".into(), "c11".into(), "c17".into()];
tc.supported_cxx_standards = vec![
"c++14".into(),
"c++17".into(),
"c++20".into(),
"c++23".into(),
];
tc.default_c_standard = "c11".to_string();
tc.default_cxx_standard = "c++17".to_string();
tc.lto_supported = true;
tc.pgo_supported = true;
tc.coverage_supported = true;
results.push(tc);
}
}
}
}
}
results
}
fn msvc_search_paths(&self) -> Vec<PathBuf> {
let mut paths = 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"),
PathBuf::from("C:/Program Files (x86)/Microsoft Visual Studio/2017/Professional"),
PathBuf::from("C:/Program Files (x86)/Microsoft Visual Studio/2017/Enterprise"),
PathBuf::from("C:/Program Files (x86)/Microsoft Visual Studio/2015/Community"),
];
if self.trust_env {
if let Ok(vs_path) = env::var("VSINSTALLDIR") {
paths.insert(0, PathBuf::from(vs_path));
}
if let Ok(_vs_version) = env::var("VisualStudioVersion") {
for year in &["2022", "2019", "2017"] {
let p = PathBuf::from(format!(
"C:/Program Files/Microsoft Visual Studio/{}/Community",
year
));
if p.exists() {
paths.insert(0, p);
}
}
}
}
paths.extend(self.extra_search_paths.clone());
paths
}
fn detect_windows_sdk(&self) -> Option<WindowsSdkInfo> {
let sdk_roots = vec![
"C:/Program Files (x86)/Windows Kits/10",
"C:/Program Files/Windows Kits/10",
];
for root in &sdk_roots {
let base = PathBuf::from(root);
if !base.exists() {
continue;
}
let include_dir = base.join("Include");
if let Ok(entries) = fs::read_dir(&include_dir) {
let mut versions: Vec<String> = entries
.filter_map(|e| e.ok())
.map(|e| e.file_name().to_string_lossy().to_string())
.filter(|n| n.starts_with("10."))
.collect();
if !versions.is_empty() {
versions.sort();
let latest = versions.last().unwrap().clone();
let lib_dir = base.join("Lib").join(&latest);
return Some(WindowsSdkInfo {
sdk_path: base.clone(),
sdk_version: latest.clone(),
ucrt_include: Some(base.join("Include").join(&latest).join("ucrt")),
um_include: Some(base.join("Include").join(&latest).join("um")),
shared_include: Some(base.join("Include").join(&latest).join("shared")),
lib_path: Some(lib_dir),
});
}
}
}
None
}
pub fn detect_icc_installations(&self) -> Vec<X86Toolchain> {
let mut results = Vec::new();
let search_paths = self.icc_search_paths();
for prefix in &search_paths {
let bin_dir = prefix.join("bin");
let icc_path = bin_dir.join("icc");
let icx_path = bin_dir.join("icx");
if icx_path.exists() {
if let Some(tc) =
self.build_icc_toolchain(prefix, X86ToolchainKind::IccOneApi, "icx")
{
results.push(tc);
}
} else if icc_path.exists() {
if let Some(tc) =
self.build_icc_toolchain(prefix, X86ToolchainKind::IccClassic, "icc")
{
results.push(tc);
}
}
}
if self.trust_env {
if let Ok(icc_dir) = env::var("ICC_PATH") {
let p = PathBuf::from(icc_dir);
if p.exists() {
if let Some(tc) =
self.build_icc_toolchain(&p, X86ToolchainKind::IccClassic, "icc")
{
results.push(tc);
}
}
}
if let Ok(oneapi_dir) = env::var("ONEAPI_ROOT") {
let p = PathBuf::from(oneapi_dir).join("compiler").join("latest");
if p.exists() {
if let Some(tc) =
self.build_icc_toolchain(&p, X86ToolchainKind::IccOneApi, "icx")
{
results.push(tc);
}
}
}
}
results
}
fn icc_search_paths(&self) -> Vec<PathBuf> {
let mut paths = vec![
PathBuf::from("/opt/intel/oneapi/compiler/latest"),
PathBuf::from("/opt/intel/compilers_and_libraries/linux"),
PathBuf::from("/opt/intel/compiler"),
];
for year in (2015..=2024).rev() {
paths.push(PathBuf::from(format!(
"/opt/intel/compilers_and_libraries_{}/linux",
year
)));
}
for ver in ["2024.1", "2024.0", "2023.2", "2023.1", "2023.0"] {
paths.push(PathBuf::from(format!("/opt/intel/oneapi/compiler/{}", ver)));
}
if self.trust_env {
if let Ok(p) = env::var("INTEL_COMPILER_DIR") {
paths.push(PathBuf::from(p));
}
}
paths.extend(self.extra_search_paths.clone());
paths
}
fn build_icc_toolchain(
&self,
prefix: &Path,
kind: X86ToolchainKind,
cc_name: &str,
) -> Option<X86Toolchain> {
let bin_dir = prefix.join("bin");
if !bin_dir.exists() {
return None;
}
let mut tc = X86Toolchain::new(kind.clone());
tc.install_prefix = prefix.to_path_buf();
tc.bin_dir = bin_dir.clone();
tc.lib_dir = prefix.join("lib");
tc.include_dir = prefix.join("include");
tc.description = format!("{} ({})", kind.as_str(), self.host_triple);
tc.target_triple = self.host_triple.clone();
let cc_path = bin_dir.join(cc_name);
if let Ok(output) = ProcessCommand::new(&cc_path).arg("--version").output() {
let stdout = String::from_utf8_lossy(&output.stdout);
for line in stdout.lines() {
if line.contains("Version") || line.contains("version") {
let parts: Vec<&str> = line.split_whitespace().collect();
for (i, part) in parts.iter().enumerate() {
if part.contains('.') && i + 1 <= parts.len() {
tc.compiler_version = part.to_string();
let vparts: Vec<&str> = part.split('.').collect();
if vparts.len() >= 1 {
tc.compiler_version_major = vparts[0].parse().unwrap_or(0);
}
if vparts.len() >= 2 {
tc.compiler_version_minor = vparts[1].parse().unwrap_or(0);
}
break;
}
}
break;
}
}
}
tc.is_installed = tc.is_usable();
tc.detection_time = SystemTime::now();
tc.supported_sanitizers = vec!["address".into(), "thread".into(), "undefined".into()];
tc.lto_supported = true;
tc.pgo_supported = true;
Some(tc)
}
pub fn detect_mingw_installations(&self) -> Vec<X86Toolchain> {
let mut results = Vec::new();
let search_roots = vec![
"/usr/x86_64-w64-mingw32",
"/usr/i686-w64-mingw32",
"/opt/mingw-w64",
"/mingw64",
"/mingw32",
];
for root_str in &search_roots {
let root = PathBuf::from(root_str);
if !root.exists() {
continue;
}
let bin_dir = root.join("bin");
let gcc_exe = bin_dir.join("x86_64-w64-mingw32-gcc");
let _gxx_exe = bin_dir.join("x86_64-w64-mingw32-g++");
if gcc_exe.exists() {
let mut tc = X86Toolchain::new(X86ToolchainKind::MinGW);
tc.install_prefix = root.clone();
tc.bin_dir = bin_dir.clone();
tc.lib_dir = root.join("lib");
tc.include_dir = root.join("include");
tc.description = "MinGW-w64 (GCC for Windows x86-64)".to_string();
tc.target_triple = "x86_64-w64-mingw32".to_string();
tc.abi = X86Abi::MsAbi;
tc.is_cross = true;
tc.is_installed = true;
tc.detection_time = SystemTime::now();
if let Ok(output) = ProcessCommand::new(&gcc_exe).arg("--version").output() {
let stdout = String::from_utf8_lossy(&output.stdout);
if let Some(line) = stdout.lines().next() {
tc.compiler_version = line.to_string();
}
}
tc.compiler_version_major = 10;
tc.compiler_version_minor = 0;
tc.compiler_version_patch = 0;
tc.dlltool_path = find_in_path(&bin_dir, "x86_64-w64-mingw32-dlltool");
tc.windres_path = find_in_path(&bin_dir, "x86_64-w64-mingw32-windres");
let clang_exe = bin_dir.join("x86_64-w64-mingw32-clang");
if clang_exe.exists() {
tc.lto_supported = true;
tc.thin_lto_supported = true;
}
results.push(tc);
}
}
if let Ok(output) = ProcessCommand::new("clang")
.arg("--target=x86_64-w64-mingw32")
.arg("-###")
.arg("-xc")
.arg("/dev/null")
.arg("-o")
.arg("/dev/null")
.output()
{
if output.status.success() {
let mut tc = X86Toolchain::new(X86ToolchainKind::ClangMinGW);
tc.description = "Clang/MinGW-w64 cross-compiler".to_string();
tc.target_triple = "x86_64-w64-mingw32".to_string();
tc.abi = X86Abi::MsAbi;
tc.is_cross = true;
tc.bin_dir = PathBuf::from("/usr/bin");
tc.is_installed = true;
tc.lto_supported = true;
tc.thin_lto_supported = true;
tc.detection_time = SystemTime::now();
results.push(tc);
}
}
results
}
pub fn detect_cross_toolchains(&self) -> Vec<X86Toolchain> {
let mut results = Vec::new();
let cross_triples = vec![
"x86_64-linux-gnu",
"i686-linux-gnu",
"x86_64-linux-musl",
"i686-linux-musl",
"x86_64-linux-android",
"i686-linux-android",
"x86_64-w64-mingw32",
"i686-w64-mingw32",
"x86_64-pc-windows-msvc",
"x86_64-unknown-freebsd",
"x86_64-unknown-netbsd",
"x86_64-unknown-openbsd",
];
for triple in &cross_triples {
let cc_name = format!("{}-gcc", triple);
if let Some(gcc_path) = which_in_path(&cc_name) {
if let Some(parent) = gcc_path.parent() {
let mut tc = X86Toolchain::new(X86ToolchainKind::Gcc);
tc.target_triple = triple.to_string();
tc.bin_dir = parent.to_path_buf();
tc.install_prefix = parent.parent().unwrap_or(parent).to_path_buf();
tc.description = format!("Cross GCC for {}", triple);
tc.is_cross = true;
tc.is_installed = true;
tc.detection_time = SystemTime::now();
results.push(tc);
}
}
}
results
}
fn collect_gcc_env(&self, _tc: &X86Toolchain) -> HashMap<String, String> {
let mut env = HashMap::new();
let vars_to_check = [
"CC",
"CXX",
"CFLAGS",
"CXXFLAGS",
"CPPFLAGS",
"LDFLAGS",
"LD_LIBRARY_PATH",
"LIBRARY_PATH",
"GCC_ROOT",
"GCC_EXEC_PREFIX",
"COMPILER_PATH",
"C_INCLUDE_PATH",
"CPLUS_INCLUDE_PATH",
"OBJC_INCLUDE_PATH",
];
for var in &vars_to_check {
if let Ok(val) = env::var(var) {
env.insert(var.to_string(), val);
}
}
env
}
pub fn detect_host_triple(&self) -> String {
self.host_triple.clone()
}
}
impl Default for X86ToolchainDetector {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct WindowsSdkInfo {
pub sdk_path: PathBuf,
pub sdk_version: String,
pub ucrt_include: Option<PathBuf>,
pub um_include: Option<PathBuf>,
pub shared_include: Option<PathBuf>,
pub lib_path: Option<PathBuf>,
}
pub struct X86ToolDriver {
pub toolchain: X86Toolchain,
pub extra_flags: Vec<String>,
pub working_dir: Option<PathBuf>,
pub verbose: bool,
pub use_ccache: bool,
pub timeout_secs: Option<u64>,
pub env_overrides: HashMap<String, String>,
}
impl X86ToolDriver {
pub fn new(toolchain: X86Toolchain) -> Self {
Self {
toolchain,
extra_flags: Vec::new(),
working_dir: None,
verbose: false,
use_ccache: false,
timeout_secs: None,
env_overrides: HashMap::new(),
}
}
pub fn build_cc_command(&self, source: &str, output: &str) -> ProcessCommand {
let cc = self
.toolchain
.resolve_cc_path()
.unwrap_or_else(|| PathBuf::from("cc"));
let mut cmd = ProcessCommand::new(&cc);
for flag in self.toolchain.default_compiler_flags() {
cmd.arg(&flag);
}
for flag in &self.extra_flags {
cmd.arg(&flag);
}
cmd.arg("-c");
cmd.arg(source);
cmd.arg("-o");
cmd.arg(output);
if let Some(ref wd) = self.working_dir {
cmd.current_dir(wd);
}
self.apply_env(&mut cmd);
self.apply_timeout(&mut cmd);
cmd
}
pub fn build_cxx_command(&self, source: &str, output: &str) -> ProcessCommand {
let cxx = self
.toolchain
.resolve_cxx_path()
.unwrap_or_else(|| PathBuf::from("c++"));
let mut cmd = ProcessCommand::new(&cxx);
for flag in self.toolchain.default_compiler_flags() {
cmd.arg(&flag);
}
for flag in &self.extra_flags {
cmd.arg(&flag);
}
cmd.arg("-c");
cmd.arg(source);
cmd.arg("-o");
cmd.arg(output);
if let Some(ref wd) = self.working_dir {
cmd.current_dir(wd);
}
self.apply_env(&mut cmd);
self.apply_timeout(&mut cmd);
cmd
}
pub fn build_compile_link_command(&self, sources: &[String], output: &str) -> ProcessCommand {
let cc = self
.toolchain
.resolve_cc_path()
.unwrap_or_else(|| PathBuf::from("cc"));
let mut cmd = ProcessCommand::new(&cc);
for flag in self.toolchain.default_compiler_flags() {
cmd.arg(&flag);
}
for flag in self.toolchain.default_linker_flags() {
cmd.arg(&flag);
}
for flag in &self.extra_flags {
cmd.arg(&flag);
}
for source in sources {
cmd.arg(source);
}
cmd.arg("-o");
cmd.arg(output);
if let Some(ref wd) = self.working_dir {
cmd.current_dir(wd);
}
self.apply_env(&mut cmd);
self.apply_timeout(&mut cmd);
cmd
}
pub fn build_cross_compile_command(
&self,
source: &str,
output: &str,
target_triple: &str,
) -> ProcessCommand {
let mut cmd = self.build_cc_command(source, output);
cmd.arg(format!("--target={}", target_triple));
cmd
}
pub fn build_asm_command(&self, source: &str, output: &str) -> ProcessCommand {
let asm = self.find_asm_tool();
let mut cmd = ProcessCommand::new(&asm);
match self.toolchain.word_size {
X86WordSize::Bits32 => {
cmd.arg("--32");
}
X86WordSize::Bits64 => {
cmd.arg("--64");
}
X86WordSize::X32 => {
cmd.arg("--x32");
}
_ => {}
}
cmd.arg(source);
cmd.arg("-o");
cmd.arg(output);
if let Some(ref wd) = self.working_dir {
cmd.current_dir(wd);
}
self.apply_env(&mut cmd);
self.apply_timeout(&mut cmd);
cmd
}
pub fn build_llvm_mc_command(&self, source: &str, output: &str) -> ProcessCommand {
let llvm_mc = self.find_tool(&["llvm-mc", "llvm-mc-18", "llvm-mc-17"]);
let mut cmd = ProcessCommand::new(&llvm_mc);
cmd.arg(format!(
"--arch={}",
if self.toolchain.word_size == X86WordSize::Bits64 {
"x86-64"
} else {
"x86"
}
));
match self.toolchain.word_size {
X86WordSize::Bits32 => {
cmd.arg("--filetype=obj").arg("-triple=i686-linux-gnu");
}
X86WordSize::Bits64 => {
cmd.arg("--filetype=obj").arg("-triple=x86_64-linux-gnu");
}
_ => {}
}
cmd.arg(source);
cmd.arg("-o");
cmd.arg(output);
if let Some(ref wd) = self.working_dir {
cmd.current_dir(wd);
}
self.apply_env(&mut cmd);
self.apply_timeout(&mut cmd);
cmd
}
pub fn build_ml64_command(&self, source: &str, output: &str) -> ProcessCommand {
let ml64 = self.find_tool(&["ml64.exe", "ml64"]);
let mut cmd = ProcessCommand::new(&ml64);
cmd.arg("/c");
cmd.arg(source);
cmd.arg(format!("/Fo{}", output));
self.apply_env(&mut cmd);
self.apply_timeout(&mut cmd);
cmd
}
fn find_asm_tool(&self) -> PathBuf {
if let Some(ref p) = self.toolchain.asm_path {
return p.clone();
}
self.find_tool(&["as", "gas", "llvm-mc"])
}
pub fn build_ld_command(&self, objects: &[String], output: &str) -> ProcessCommand {
let ld = self.find_linker("ld");
let mut cmd = ProcessCommand::new(&ld);
match self.toolchain.word_size {
X86WordSize::Bits32 => {
cmd.arg("-m").arg("elf_i386");
}
X86WordSize::Bits64 => {
cmd.arg("-m").arg("elf_x86_64");
}
X86WordSize::X32 => {
cmd.arg("-m").arg("elf32_x86_64");
}
_ => {}
}
for flag in self.toolchain.default_linker_flags() {
cmd.arg(&flag);
}
for flag in &self.extra_flags {
cmd.arg(&flag);
}
if let Some(ref crt1) = self.find_crt("crt1.o") {
cmd.arg(crt1);
}
if let Some(ref crti) = self.toolchain.crt_i {
cmd.arg(crti);
}
if let Some(ref crtbegin) = self.toolchain.crt_begin {
cmd.arg(crtbegin);
}
for obj in objects {
cmd.arg(obj);
}
if let Some(ref crtend) = self.toolchain.crt_end {
cmd.arg(crtend);
}
if let Some(ref crtn) = self.toolchain.crt_n {
cmd.arg(crtn);
}
cmd.arg("-o");
cmd.arg(output);
cmd.arg("-lc");
cmd.arg("-lgcc");
if self.toolchain.libgcc_eh_path.is_some() {
cmd.arg("-lgcc_eh");
}
if let Some(ref wd) = self.working_dir {
cmd.current_dir(wd);
}
self.apply_env(&mut cmd);
self.apply_timeout(&mut cmd);
cmd
}
pub fn build_gold_command(&self, objects: &[String], output: &str) -> ProcessCommand {
let gold = self.find_tool(&["ld.gold", "gold"]);
let mut cmd = ProcessCommand::new(&gold);
match self.toolchain.word_size {
X86WordSize::Bits32 => {
cmd.arg("-m").arg("elf_i386");
}
X86WordSize::Bits64 => {
cmd.arg("-m").arg("elf_x86_64");
}
_ => {}
}
for flag in &self.extra_flags {
cmd.arg(&flag);
}
if let Some(ref plugin) = self.toolchain.lto_plugin_path {
cmd.arg(format!("--plugin={}", plugin.display()));
}
for obj in objects {
cmd.arg(obj);
}
cmd.arg("-o");
cmd.arg(output);
if let Some(ref wd) = self.working_dir {
cmd.current_dir(wd);
}
self.apply_env(&mut cmd);
self.apply_timeout(&mut cmd);
cmd
}
pub fn build_lld_command(&self, objects: &[String], output: &str) -> ProcessCommand {
let lld = self.find_tool(&["ld.lld", "lld"]);
let mut cmd = ProcessCommand::new(&lld);
match self.toolchain.word_size {
X86WordSize::Bits32 => {
cmd.arg("-m").arg("elf_i386");
}
X86WordSize::Bits64 => {
cmd.arg("-m").arg("elf_x86_64");
}
_ => {}
}
for flag in self.toolchain.default_linker_flags() {
cmd.arg(&flag);
}
for flag in &self.extra_flags {
cmd.arg(&flag);
}
for obj in objects {
cmd.arg(obj);
}
cmd.arg("-o");
cmd.arg(output);
if let Some(ref wd) = self.working_dir {
cmd.current_dir(wd);
}
self.apply_env(&mut cmd);
self.apply_timeout(&mut cmd);
cmd
}
pub fn build_msvc_link_command(&self, objects: &[String], output: &str) -> ProcessCommand {
let link = self.find_tool(&["link.exe", "lld-link"]);
let mut cmd = ProcessCommand::new(&link);
cmd.arg(format!("/OUT:{}", output));
if let Some(ref lib) = self.toolchain.lib_dir.to_str() {
cmd.arg(format!("/LIBPATH:{}", lib));
}
for flag in &self.extra_flags {
cmd.arg(&flag);
}
for obj in objects {
cmd.arg(obj);
}
cmd.arg("kernel32.lib");
cmd.arg("user32.lib");
cmd.arg("gdi32.lib");
cmd.arg("winspool.lib");
cmd.arg("comdlg32.lib");
cmd.arg("advapi32.lib");
cmd.arg("shell32.lib");
cmd.arg("ole32.lib");
cmd.arg("oleaut32.lib");
cmd.arg("uuid.lib");
cmd.arg("odbc32.lib");
cmd.arg("odbccp32.lib");
self.apply_env(&mut cmd);
self.apply_timeout(&mut cmd);
cmd
}
pub fn build_lld_link_command(&self, objects: &[String], output: &str) -> ProcessCommand {
let lld_link = self.find_tool(&["lld-link", "lld-link.exe"]);
let mut cmd = ProcessCommand::new(&lld_link);
cmd.arg(format!("/OUT:{}", output));
cmd.arg("/MACHINE:X64");
cmd.arg("/NOLOGO");
if let Some(ref lib) = self.toolchain.lib_dir.to_str() {
cmd.arg(format!("/LIBPATH:{}", lib));
}
for flag in &self.extra_flags {
cmd.arg(&flag);
}
for obj in objects {
cmd.arg(obj);
}
cmd.arg("kernel32.lib");
cmd.arg("user32.lib");
self.apply_env(&mut cmd);
self.apply_timeout(&mut cmd);
cmd
}
fn find_linker(&self, default: &str) -> PathBuf {
if let Some(ref p) = self.toolchain.ld_lld_path {
return p.clone();
}
if let Some(ref p) = self.toolchain.ld_gold_path {
return p.clone();
}
if let Some(ref p) = self.toolchain.ld_path {
return p.clone();
}
PathBuf::from(default)
}
fn find_crt(&self, name: &str) -> Option<PathBuf> {
if let Some(ref sysroot) = self.toolchain.sysroot {
let p = sysroot.join("usr/lib").join(name);
if p.exists() {
return Some(p);
}
}
let p = self.toolchain.lib_dir.join(name);
if p.exists() {
return Some(p);
}
for dir in &["/usr/lib/x86_64-linux-gnu", "/usr/lib64", "/usr/lib"] {
let candidate = PathBuf::from(dir).join(name);
if candidate.exists() {
return Some(candidate);
}
}
None
}
pub fn build_ar_command(&self, objects: &[String], archive: &str) -> ProcessCommand {
let ar = self.find_archiver();
let mut cmd = ProcessCommand::new(&ar);
cmd.arg("rcs"); cmd.arg(archive);
for obj in objects {
cmd.arg(obj);
}
if let Some(ref wd) = self.working_dir {
cmd.current_dir(wd);
}
self.apply_env(&mut cmd);
self.apply_timeout(&mut cmd);
cmd
}
pub fn build_llvm_ar_command(&self, objects: &[String], archive: &str) -> ProcessCommand {
let llvm_ar = self.find_tool(&["llvm-ar", "llvm-ar-18", "llvm-ar-17"]);
let mut cmd = ProcessCommand::new(&llvm_ar);
cmd.arg("rcs");
cmd.arg(archive);
for obj in objects {
cmd.arg(obj);
}
if let Some(ref wd) = self.working_dir {
cmd.current_dir(wd);
}
self.apply_env(&mut cmd);
self.apply_timeout(&mut cmd);
cmd
}
pub fn build_lib_command(&self, objects: &[String], archive: &str) -> ProcessCommand {
let lib_exe = self.find_tool(&["lib.exe", "lib"]);
let mut cmd = ProcessCommand::new(&lib_exe);
cmd.arg(format!("/OUT:{}", archive));
for obj in objects {
cmd.arg(obj);
}
self.apply_env(&mut cmd);
self.apply_timeout(&mut cmd);
cmd
}
fn find_archiver(&self) -> PathBuf {
if let Some(ref p) = self.toolchain.llvm_ar_path {
return p.clone();
}
if let Some(ref p) = self.toolchain.ar_path {
return p.clone();
}
PathBuf::from("ar")
}
pub fn build_objcopy_command(&self, input: &str, output: &str) -> ProcessCommand {
let objcopy = self.find_tool(&["llvm-objcopy", "objcopy"]);
let mut cmd = ProcessCommand::new(&objcopy);
cmd.arg(input);
cmd.arg(output);
if let Some(ref wd) = self.working_dir {
cmd.current_dir(wd);
}
self.apply_env(&mut cmd);
self.apply_timeout(&mut cmd);
cmd
}
pub fn build_objdump_command(&self, input: &str) -> ProcessCommand {
let objdump = self.find_tool(&["llvm-objdump", "objdump"]);
let mut cmd = ProcessCommand::new(&objdump);
cmd.arg("-d"); cmd.arg(input);
self.apply_env(&mut cmd);
self.apply_timeout(&mut cmd);
cmd
}
pub fn build_nm_command(&self, input: &str) -> ProcessCommand {
let nm = self.find_tool(&["llvm-nm", "nm"]);
let mut cmd = ProcessCommand::new(&nm);
cmd.arg(input);
self.apply_env(&mut cmd);
self.apply_timeout(&mut cmd);
cmd
}
pub fn build_strip_command(&self, input: &str) -> ProcessCommand {
let strip = self.find_tool(&["llvm-strip", "strip"]);
let mut cmd = ProcessCommand::new(&strip);
cmd.arg(input);
self.apply_env(&mut cmd);
self.apply_timeout(&mut cmd);
cmd
}
pub fn build_size_command(&self, input: &str) -> ProcessCommand {
let size = self.find_tool(&["llvm-size", "size"]);
let mut cmd = ProcessCommand::new(&size);
cmd.arg(input);
self.apply_env(&mut cmd);
self.apply_timeout(&mut cmd);
cmd
}
pub fn build_readelf_command(&self, input: &str) -> ProcessCommand {
let readelf = self.find_tool(&["llvm-readelf", "readelf"]);
let mut cmd = ProcessCommand::new(&readelf);
cmd.arg("-a"); cmd.arg(input);
self.apply_env(&mut cmd);
self.apply_timeout(&mut cmd);
cmd
}
pub fn build_gdb_command(&self, executable: &str) -> ProcessCommand {
let gdb = self.find_tool(&["gdb"]);
let mut cmd = ProcessCommand::new(&gdb);
cmd.arg(executable);
self.apply_env(&mut cmd);
self.apply_timeout(&mut cmd);
cmd
}
pub fn build_lldb_command(&self, executable: &str) -> ProcessCommand {
let lldb = self.find_tool(&["lldb", "lldb-18", "lldb-17", "lldb-16"]);
let mut cmd = ProcessCommand::new(&lldb);
cmd.arg(executable);
self.apply_env(&mut cmd);
self.apply_timeout(&mut cmd);
cmd
}
pub fn build_debugger_command(&self, executable: &str) -> ProcessCommand {
if self.find_tool(&["lldb"]).exists() {
self.build_lldb_command(executable)
} else {
self.build_gdb_command(executable)
}
}
pub fn build_profiler_command(&self, executable: &str, gmon_file: &str) -> ProcessCommand {
let gprof = self.find_tool(&["gprof"]);
let mut cmd = ProcessCommand::new(&gprof);
cmd.arg(executable);
cmd.arg(gmon_file);
self.apply_env(&mut cmd);
self.apply_timeout(&mut cmd);
cmd
}
fn find_tool(&self, candidates: &[&str]) -> PathBuf {
for name in candidates {
if !self.toolchain.bin_dir.as_os_str().is_empty() {
let p = self.toolchain.bin_dir.join(name);
if p.exists() {
return p;
}
let pe = self.toolchain.bin_dir.join(format!("{}.exe", name));
if pe.exists() {
return pe;
}
}
}
for name in candidates {
if let Some(p) = which_in_path(name) {
return p;
}
}
PathBuf::from(candidates.first().copied().unwrap_or("unknown"))
}
fn apply_env(&self, cmd: &mut ProcessCommand) {
for (key, val) in &self.env_overrides {
cmd.env(key, val);
}
}
fn apply_timeout(&self, _cmd: &mut ProcessCommand) {
if self.timeout_secs.is_some() {
}
}
pub fn run_command(cmd: &mut ProcessCommand) -> io::Result<X86ToolOutput> {
let start = SystemTime::now();
let output = cmd.output()?;
let elapsed = start.elapsed().unwrap_or_default();
Ok(X86ToolOutput {
exit_code: output.status.code().unwrap_or(-1),
stdout: String::from_utf8_lossy(&output.stdout).to_string(),
stderr: String::from_utf8_lossy(&output.stderr).to_string(),
success: output.status.success(),
elapsed,
})
}
pub fn compile(&self, source: &str, output: &str) -> io::Result<X86ToolOutput> {
let mut cmd = self.build_cc_command(source, output);
if self.verbose {
eprintln!("[X86ToolDriver] Running: {:?}", cmd);
}
Self::run_command(&mut cmd)
}
pub fn compile_cxx(&self, source: &str, output: &str) -> io::Result<X86ToolOutput> {
let mut cmd = self.build_cxx_command(source, output);
if self.verbose {
eprintln!("[X86ToolDriver] Running: {:?}", cmd);
}
Self::run_command(&mut cmd)
}
pub fn compile_and_link(&self, sources: &[String], output: &str) -> io::Result<X86ToolOutput> {
let mut cmd = self.build_compile_link_command(sources, output);
if self.verbose {
eprintln!("[X86ToolDriver] Running: {:?}", cmd);
}
Self::run_command(&mut cmd)
}
pub fn assemble(&self, source: &str, output: &str) -> io::Result<X86ToolOutput> {
let mut cmd = self.build_asm_command(source, output);
if self.verbose {
eprintln!("[X86ToolDriver] Running: {:?}", cmd);
}
Self::run_command(&mut cmd)
}
pub fn link(&self, objects: &[String], output: &str) -> io::Result<X86ToolOutput> {
let mut cmd = if self.toolchain.kind.is_msvc_family() {
self.build_msvc_link_command(objects, output)
} else {
self.build_lld_command(objects, output)
};
if self.verbose {
eprintln!("[X86ToolDriver] Running: {:?}", cmd);
}
Self::run_command(&mut cmd)
}
pub fn create_archive(&self, objects: &[String], archive: &str) -> io::Result<X86ToolOutput> {
let mut cmd = if self.toolchain.kind.is_msvc_family() {
self.build_lib_command(objects, archive)
} else if self.toolchain.llvm_ar_path.is_some() {
self.build_llvm_ar_command(objects, archive)
} else {
self.build_ar_command(objects, archive)
};
if self.verbose {
eprintln!("[X86ToolDriver] Running: {:?}", cmd);
}
Self::run_command(&mut cmd)
}
}
#[derive(Debug, Clone)]
pub struct X86ToolOutput {
pub exit_code: i32,
pub stdout: String,
pub stderr: String,
pub success: bool,
pub elapsed: Duration,
}
impl X86ToolOutput {
pub fn is_success(&self) -> bool {
self.success
}
pub fn has_errors(&self) -> bool {
!self.success || self.stderr.contains("error:")
}
pub fn has_warnings(&self) -> bool {
self.stderr.contains("warning:")
}
}
#[derive(Debug, Clone)]
pub struct X86ToolConfig {
pub tool_kind: X86ToolKind,
pub target_triple: String,
pub cpu_model: String,
pub default_flags: Vec<String>,
pub target_flags: Vec<String>,
pub cpu_flags: Vec<String>,
pub optimization_flags: Vec<String>,
pub warning_flags: Vec<String>,
pub debug_flags: Vec<String>,
pub sanitizer_flags: Vec<String>,
pub workarounds: Vec<X86ToolchainWorkaround>,
pub verbose: bool,
pub custom_flags: HashMap<String, String>,
}
impl X86ToolConfig {
pub fn new(tool_kind: X86ToolKind) -> Self {
Self {
tool_kind,
target_triple: "x86_64-unknown-linux-gnu".to_string(),
cpu_model: "x86-64".to_string(),
default_flags: Vec::new(),
target_flags: Vec::new(),
cpu_flags: Vec::new(),
optimization_flags: vec!["-O2".to_string()],
warning_flags: vec!["-Wall".to_string(), "-Wextra".to_string()],
debug_flags: vec!["-g".to_string()],
sanitizer_flags: Vec::new(),
workarounds: Vec::new(),
verbose: false,
custom_flags: HashMap::new(),
}
}
pub fn for_c_compiler() -> Self {
let mut cfg = Self::new(X86ToolKind::Cc);
cfg.default_flags = vec![
"-c".to_string(),
"-pipe".to_string(),
"-fno-common".to_string(),
"-fdiagnostics-color=always".to_string(),
];
cfg
}
pub fn for_cxx_compiler() -> Self {
let mut cfg = Self::new(X86ToolKind::Cxx);
cfg.default_flags = vec![
"-c".to_string(),
"-pipe".to_string(),
"-fno-common".to_string(),
"-fdiagnostics-color=always".to_string(),
"-fno-rtti".to_string(),
"-fno-exceptions".to_string(),
];
cfg
}
pub fn for_assembler() -> Self {
let mut cfg = Self::new(X86ToolKind::Assembler);
cfg.default_flags = vec!["--warn".to_string(), "--fatal-warnings".to_string()];
cfg
}
pub fn for_linker() -> Self {
let mut cfg = Self::new(X86ToolKind::Linker);
cfg.default_flags = vec![
"--eh-frame-hdr".to_string(),
"--hash-style=gnu".to_string(),
"--build-id".to_string(),
"-z".to_string(),
"relro".to_string(),
"-z".to_string(),
"now".to_string(),
];
cfg
}
pub fn for_archiver() -> Self {
let mut cfg = Self::new(X86ToolKind::Archiver);
cfg.default_flags = vec!["rcsD".to_string()];
cfg
}
pub fn with_target_flags(mut self, triple: &str) -> Self {
self.target_triple = triple.to_string();
self.target_flags = match triple {
t if t.contains("linux") => vec![
"-D__linux__".to_string(),
"-D__unix__".to_string(),
"-D_POSIX_SOURCE".to_string(),
],
t if t.contains("windows") => vec!["-D_WIN32".to_string(), "-D_WINDOWS".to_string()],
t if t.contains("darwin") || t.contains("macos") => {
vec!["-D__APPLE__".to_string(), "-D__MACH__".to_string()]
}
t if t.contains("freebsd") => vec!["-D__FreeBSD__".to_string()],
_ => vec![],
}
.into_iter()
.map(|s| s.to_string())
.collect();
self
}
pub fn with_cpu(mut self, cpu: &str) -> Self {
self.cpu_model = cpu.to_string();
self.cpu_flags = X86ToolConfig::cpu_optimization_flags(cpu);
self
}
pub fn cpu_march_flag(cpu: &str) -> String {
match cpu {
"native" => "-march=native".to_string(),
"x86-64" => "-march=x86-64".to_string(),
"x86-64-v2" => "-march=x86-64-v2".to_string(),
"x86-64-v3" => "-march=x86-64-v3".to_string(),
"x86-64-v4" => "-march=x86-64-v4".to_string(),
"znver4" => "-march=znver4".to_string(),
"znver3" => "-march=znver3".to_string(),
"znver2" => "-march=znver2".to_string(),
"znver1" => "-march=znver1".to_string(),
"sapphirerapids" => "-march=sapphirerapids".to_string(),
"alderlake" => "-march=alderlake".to_string(),
"icelake-server" => "-march=icelake-server".to_string(),
"icelake-client" => "-march=icelake-client".to_string(),
"cascadelake" => "-march=cascadelake".to_string(),
"skylake-avx512" => "-march=skylake-avx512".to_string(),
"skylake" => "-march=skylake".to_string(),
"broadwell" => "-march=broadwell".to_string(),
"haswell" => "-march=haswell".to_string(),
"ivybridge" => "-march=core-avx-i".to_string(),
"sandybridge" => "-march=corei7-avx".to_string(),
"nehalem" => "-march=nehalem".to_string(),
"core2" => "-march=core2".to_string(),
"pentium4" => "-march=pentium4".to_string(),
"pentium3" => "-march=pentium3".to_string(),
"i686" => "-march=i686".to_string(),
"i586" => "-march=pentium".to_string(),
_ => format!("-march={}", cpu),
}
}
pub fn cpu_mtune_flag(cpu: &str) -> String {
match cpu {
"native" => "-mtune=native".to_string(),
"generic" => "-mtune=generic".to_string(),
"x86-64" => "-mtune=generic".to_string(),
_ => format!("-mtune={}", cpu),
}
}
pub fn cpu_optimization_flags(cpu: &str) -> Vec<String> {
let mut flags = vec![Self::cpu_march_flag(cpu), Self::cpu_mtune_flag(cpu)];
match cpu {
"native" | "znver4" | "znver3" | "znver2" => {
flags.push("-mno-tbm".to_string());
flags.push("-mno-lwp".to_string());
if matches!(cpu, "znver3" | "znver4") {
flags.push("-mprefer-vector-width=256".to_string());
}
}
"sapphirerapids" | "icelake-server" | "skylake-avx512" => {
flags.push("-mprefer-vector-width=512".to_string());
}
"generic" | "x86-64" => {
flags.push("-mtune=generic".to_string());
}
_ => {}
}
flags
}
pub fn with_optimization(mut self, level: &str) -> Self {
self.optimization_flags = match level {
"0" | "none" => vec!["-O0".to_string()],
"1" => vec!["-O1".to_string()],
"2" | "default" => vec!["-O2".to_string()],
"3" | "aggressive" => vec!["-O3".to_string()],
"s" | "size" => vec!["-Os".to_string()],
"z" | "minsize" => vec!["-Oz".to_string()],
"fast" => vec!["-Ofast".to_string()],
_ => vec![format!("-O{}", level)],
};
self
}
pub fn with_lto(mut self, mode: &str) -> Self {
match mode {
"thin" => {
self.optimization_flags.push("-flto=thin".to_string());
}
"full" => {
self.optimization_flags.push("-flto".to_string());
}
_ => {}
}
self
}
pub fn with_debug(mut self, level: &str) -> Self {
self.debug_flags = match level {
"0" | "none" => vec![],
"1" | "line-tables-only" => vec!["-g1".to_string()],
"2" | "default" => vec!["-g".to_string()],
"3" | "full" => vec!["-g3".to_string()],
"gdb" => vec!["-ggdb".to_string()],
"split" => {
vec!["-g".to_string(), "-gsplit-dwarf".to_string()]
}
_ => vec![format!("-g{}", level)],
};
self
}
pub fn with_sanitizer(mut self, sanitizer: &str) -> Self {
self.sanitizer_flags = match sanitizer {
"address" => vec![
"-fsanitize=address".to_string(),
"-fno-omit-frame-pointer".to_string(),
],
"thread" => vec!["-fsanitize=thread".to_string()],
"memory" => vec![
"-fsanitize=memory".to_string(),
"-fsanitize-memory-track-origins".to_string(),
],
"undefined" => vec![
"-fsanitize=undefined".to_string(),
"-fsanitize=integer".to_string(),
"-fsanitize=nullability".to_string(),
],
"leak" => vec!["-fsanitize=leak".to_string()],
"hwaddress" => vec!["-fsanitize=hwaddress".to_string()],
"cfi" => vec!["-fsanitize=cfi".to_string()],
"safe-stack" => vec!["-fsanitize=safe-stack".to_string()],
_ => vec![],
};
self
}
pub fn add_workaround(&mut self, workaround: X86ToolchainWorkaround) {
self.workarounds.push(workaround);
}
pub fn all_flags(&self) -> Vec<String> {
let mut flags: Vec<String> = Vec::new();
flags.extend(self.default_flags.clone());
flags.extend(self.target_flags.clone());
flags.extend(self.cpu_flags.clone());
flags.extend(self.optimization_flags.clone());
flags.extend(self.warning_flags.clone());
flags.extend(self.debug_flags.clone());
flags.extend(self.sanitizer_flags.clone());
if self.verbose {
flags.push("-v".to_string());
}
for w in &self.workarounds {
flags.extend(w.flags.clone());
}
flags
}
pub fn gcc_defaults(word_size: X86WordSize) -> Vec<String> {
let mut flags = vec![
"-c".to_string(),
"-pipe".to_string(),
"-fno-common".to_string(),
"-fdiagnostics-color=always".to_string(),
word_size.m_flag().to_string(),
];
if word_size == X86WordSize::Bits64 {
flags.push("-fPIC".to_string());
}
flags
}
pub fn clang_defaults(word_size: X86WordSize) -> Vec<String> {
let mut flags = vec![
"-c".to_string(),
"-pipe".to_string(),
"-fno-common".to_string(),
"-fcolor-diagnostics".to_string(),
word_size.m_flag().to_string(),
"-fno-strict-aliasing".to_string(),
"-Wno-unused-command-line-argument".to_string(),
];
if word_size == X86WordSize::Bits64 {
flags.push("-fPIC".to_string());
}
flags
}
pub fn msvc_defaults(word_size: X86WordSize) -> Vec<String> {
match word_size {
X86WordSize::Bits64 => vec![
"/c".to_string(),
"/nologo".to_string(),
"/W3".to_string(),
"/O2".to_string(),
"/D".to_string(),
"WIN32".to_string(),
"/D".to_string(),
"_WINDOWS".to_string(),
"/D".to_string(),
"NDEBUG".to_string(),
"/EHsc".to_string(),
],
X86WordSize::Bits32 => vec![
"/c".to_string(),
"/nologo".to_string(),
"/W3".to_string(),
"/O2".to_string(),
"/D".to_string(),
"WIN32".to_string(),
"/D".to_string(),
"_WINDOWS".to_string(),
"/EHsc".to_string(),
],
_ => vec![],
}
}
pub fn icc_defaults(word_size: X86WordSize) -> Vec<String> {
let mut flags = vec![
"-c".to_string(),
"-fPIC".to_string(),
word_size.m_flag().to_string(),
];
if word_size == X86WordSize::Bits64 {
flags.push("-xHost".to_string());
}
flags
}
}
impl Default for X86ToolConfig {
fn default() -> Self {
Self::for_c_compiler()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct X86ToolchainWorkaround {
pub description: String,
pub toolchain_kind: X86ToolchainKind,
pub version_range: Option<(String, String)>,
pub flags: Vec<String>,
pub enabled: bool,
}
impl X86ToolchainWorkaround {
pub fn new(desc: &str, kind: X86ToolchainKind, flags: Vec<String>) -> Self {
Self {
description: desc.to_string(),
toolchain_kind: kind,
version_range: None,
flags,
enabled: true,
}
}
pub fn with_version_range(mut self, min: &str, max: &str) -> Self {
self.version_range = Some((min.to_string(), max.to_string()));
self
}
pub fn disabled(mut self) -> Self {
self.enabled = false;
self
}
pub fn known_gcc_workarounds() -> Vec<Self> {
vec![
Self::new(
"GCC < 5: wrong-code bug with -fipa-sra at -O2+",
X86ToolchainKind::Gcc,
vec!["-fno-ipa-sra".to_string()],
)
.with_version_range("0.0", "5.0"),
Self::new(
"GCC 4.8: incorrect codegen for AVX2 gather",
X86ToolchainKind::Gcc,
vec!["-mno-avx2".to_string()],
)
.with_version_range("4.8.0", "4.8.5"),
Self::new(
"GCC < 9: broken -flifetime-dse with placement new",
X86ToolchainKind::Gcc,
vec!["-fno-lifetime-dse".to_string()],
)
.with_version_range("0.0", "9.0"),
Self::new(
"GCC < 7: spurious -Wimplicit-fallthrough in system headers",
X86ToolchainKind::Gcc,
vec!["-Wno-implicit-fallthrough".to_string()],
)
.with_version_range("0.0", "7.0"),
]
}
pub fn known_clang_workarounds() -> Vec<Self> {
vec![
Self::new(
"Clang < 12: missing support for -march=alderlake",
X86ToolchainKind::Clang,
vec!["-march=tremont".to_string()],
)
.with_version_range("0.0", "12.0"),
Self::new(
"Clang < 10: broken coroutines in some C++20 modes",
X86ToolchainKind::Clang,
vec!["-fno-coroutines".to_string()],
)
.with_version_range("0.0", "10.0"),
Self::new(
"Clang: work around integrated-as limitation on older CPUs",
X86ToolchainKind::Clang,
vec!["-fno-integrated-as".to_string()],
)
.with_version_range("0.0", "7.0"),
]
}
pub fn known_msvc_workarounds() -> Vec<Self> {
vec![
Self::new(
"MSVC pre-2019: broken /Zc:preprocessor with variadic macros",
X86ToolchainKind::Msvc,
vec!["/Zc:preprocessor-".to_string()],
),
Self::new(
"MSVC: work around C4668 warning from windows.h",
X86ToolchainKind::Msvc,
vec!["/wd4668".to_string()],
),
]
}
pub fn known_icc_workarounds() -> Vec<Self> {
vec![
Self::new(
"ICC: work around aggressive inlining that causes code bloat",
X86ToolchainKind::IccClassic,
vec!["-no-ipo".to_string()],
),
Self::new(
"ICX: work around missing _mm512_ intrinsics in early versions",
X86ToolchainKind::IccOneApi,
vec!["-mno-avx512f".to_string()],
),
]
}
}
pub struct X86ToolchainManager {
pub toolchains: HashMap<X86ToolchainKind, Vec<X86Toolchain>>,
pub custom_toolchains: Vec<X86Toolchain>,
pub detector: X86ToolchainDetector,
pub priority_order: Vec<X86ToolchainKind>,
pub fallback_chains: HashMap<X86ToolchainKind, Vec<X86ToolchainKind>>,
pub active_toolchain: Option<X86Toolchain>,
pub prefer_clang: bool,
pub allow_cross_for_native: bool,
}
impl X86ToolchainManager {
pub fn new() -> Self {
Self {
toolchains: HashMap::new(),
custom_toolchains: Vec::new(),
detector: X86ToolchainDetector::new(),
priority_order: Self::default_priority_order(),
fallback_chains: Self::default_fallback_chains(),
active_toolchain: None,
prefer_clang: false,
allow_cross_for_native: false,
}
}
fn default_priority_order() -> Vec<X86ToolchainKind> {
vec![
X86ToolchainKind::Gcc,
X86ToolchainKind::Clang,
X86ToolchainKind::LlvmFull,
X86ToolchainKind::IccOneApi,
X86ToolchainKind::IccClassic,
X86ToolchainKind::ClangMinGW,
X86ToolchainKind::MinGW,
X86ToolchainKind::Msvc,
X86ToolchainKind::BareMetalGcc,
]
}
fn default_fallback_chains() -> HashMap<X86ToolchainKind, Vec<X86ToolchainKind>> {
let mut chains = HashMap::new();
chains.insert(
X86ToolchainKind::Gcc,
vec![X86ToolchainKind::Clang, X86ToolchainKind::LlvmFull],
);
chains.insert(
X86ToolchainKind::Clang,
vec![X86ToolchainKind::Gcc, X86ToolchainKind::LlvmFull],
);
chains.insert(
X86ToolchainKind::Msvc,
vec![X86ToolchainKind::ClangMinGW, X86ToolchainKind::MinGW],
);
chains.insert(
X86ToolchainKind::IccClassic,
vec![X86ToolchainKind::Gcc, X86ToolchainKind::Clang],
);
chains.insert(
X86ToolchainKind::IccOneApi,
vec![X86ToolchainKind::Gcc, X86ToolchainKind::Clang],
);
chains
}
pub fn detect_all(&mut self) {
let detected = self.detector.detect_all();
for tc in detected {
self.add_toolchain(tc);
}
}
pub fn add_toolchain(&mut self, tc: X86Toolchain) {
let kind = tc.kind.clone();
let entry = self.toolchains.entry(kind).or_insert_with(Vec::new);
if !entry
.iter()
.any(|existing| existing.description == tc.description)
{
entry.push(tc);
}
}
pub fn register_custom(&mut self, tc: X86Toolchain) {
self.custom_toolchains.push(tc);
}
pub fn select_for_target(&self, target_triple: &str) -> Option<&X86Toolchain> {
for tc in &self.custom_toolchains {
if tc.target_triple == target_triple {
return Some(tc);
}
}
let parsed = Triple::parse(target_triple);
let host_triple = &self.detector.host_triple;
let is_native = target_triple == host_triple;
let is_windows = matches!(parsed.os, OS::Win32);
let is_linux = matches!(parsed.os, OS::Linux);
let relevant_kinds: Vec<X86ToolchainKind> = if is_windows {
vec![
X86ToolchainKind::Msvc,
X86ToolchainKind::ClangMinGW,
X86ToolchainKind::MinGW,
X86ToolchainKind::Clang,
X86ToolchainKind::Gcc,
]
} else if is_linux && is_native {
if self.prefer_clang {
vec![
X86ToolchainKind::Clang,
X86ToolchainKind::LlvmFull,
X86ToolchainKind::Gcc,
X86ToolchainKind::IccOneApi,
]
} else {
vec![
X86ToolchainKind::Gcc,
X86ToolchainKind::Clang,
X86ToolchainKind::LlvmFull,
X86ToolchainKind::IccOneApi,
]
}
} else if is_native {
vec![
X86ToolchainKind::Gcc,
X86ToolchainKind::Clang,
X86ToolchainKind::LlvmFull,
]
} else {
vec![
X86ToolchainKind::Gcc,
X86ToolchainKind::Clang,
X86ToolchainKind::MinGW,
X86ToolchainKind::ClangMinGW,
]
};
for kind in &relevant_kinds {
if let Some(toolchains) = self.toolchains.get(kind) {
for tc in toolchains {
if tc.target_triple == target_triple && tc.is_installed {
return Some(tc);
}
}
for tc in toolchains {
if target_triple.starts_with(&tc.target_triple) && tc.is_installed {
return Some(tc);
}
}
}
}
for kind in &self.priority_order {
if let Some(toolchains) = self.toolchains.get(kind) {
if let Some(tc) = toolchains.first() {
if tc.is_installed {
return Some(tc);
}
}
}
}
None
}
pub fn select_by_kind(&self, kind: X86ToolchainKind) -> Option<&X86Toolchain> {
if let Some(toolchains) = self.toolchains.get(&kind) {
toolchains.last()
} else {
self.custom_toolchains.iter().find(|tc| tc.kind == kind)
}
}
pub fn get_fallback(&self, desired: X86ToolchainKind) -> Option<&X86Toolchain> {
if let Some(fallbacks) = self.fallback_chains.get(&desired) {
for fallback_kind in fallbacks {
if let Some(tc) = self.select_by_kind(fallback_kind.clone()) {
return Some(tc);
}
}
}
None
}
pub fn register_from_paths(
&mut self,
kind: X86ToolchainKind,
cc_path: &str,
cxx_path: Option<&str>,
ld_path: Option<&str>,
ar_path: Option<&str>,
target_triple: &str,
) {
let mut tc = X86Toolchain::new(kind.clone());
tc.target_triple = target_triple.to_string();
tc.cc_path = Some(PathBuf::from(cc_path));
tc.cxx_path = cxx_path.map(PathBuf::from);
tc.ld_path = ld_path.map(PathBuf::from);
tc.ar_path = ar_path.map(PathBuf::from);
tc.description = format!("Custom {} for {}", kind.as_str(), target_triple);
tc.is_installed = Path::new(cc_path).exists();
if let Some(parent) = Path::new(cc_path).parent() {
tc.bin_dir = parent.to_path_buf();
if let Some(grandparent) = parent.parent() {
tc.install_prefix = grandparent.to_path_buf();
tc.lib_dir = grandparent.join("lib");
tc.include_dir = grandparent.join("include");
}
}
self.register_custom(tc);
}
pub fn list_installed(&self) -> Vec<&X86Toolchain> {
let mut result: Vec<&X86Toolchain> = Vec::new();
for tcs in self.toolchains.values() {
for tc in tcs {
if tc.is_installed {
result.push(tc);
}
}
}
for tc in &self.custom_toolchains {
if tc.is_installed {
result.push(tc);
}
}
result
}
pub fn list_by_kind(&self, kind: X86ToolchainKind) -> Vec<&X86Toolchain> {
let mut result: Vec<&X86Toolchain> = Vec::new();
if let Some(tcs) = self.toolchains.get(&kind) {
for tc in tcs {
result.push(tc);
}
}
for tc in &self.custom_toolchains {
if tc.kind == kind {
result.push(tc);
}
}
result
}
pub fn count_installed(&self) -> usize {
self.list_installed().len()
}
pub fn has_gcc_family(&self) -> bool {
self.list_by_kind(X86ToolchainKind::Gcc).len()
+ self.list_by_kind(X86ToolchainKind::MinGW).len()
+ self.list_by_kind(X86ToolchainKind::BareMetalGcc).len()
> 0
}
pub fn has_clang_family(&self) -> bool {
self.list_by_kind(X86ToolchainKind::Clang).len()
+ self.list_by_kind(X86ToolchainKind::LlvmFull).len()
+ self.list_by_kind(X86ToolchainKind::ClangMinGW).len()
> 0
}
pub fn best_overall(&self) -> Option<&X86Toolchain> {
for kind in &self.priority_order {
if let Some(tcs) = self.toolchains.get(kind) {
if let Some(tc) = tcs.last() {
if tc.is_installed && tc.is_usable() {
return Some(tc);
}
}
}
}
self.custom_toolchains
.iter()
.find(|tc| tc.is_installed && tc.is_usable())
}
}
impl Default for X86ToolchainManager {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct X86CompileCommand {
pub directory: String,
pub file: String,
pub arguments: Vec<String>,
pub output: Option<String>,
pub toolchain_kind: Option<X86ToolchainKind>,
}
impl X86CompileCommand {
pub fn new(directory: &str, file: &str, arguments: Vec<String>) -> Self {
Self {
directory: directory.to_string(),
file: file.to_string(),
arguments,
output: None,
toolchain_kind: None,
}
}
pub fn derive_output(&mut self) {
let path = Path::new(&self.file);
let stem = path
.file_stem()
.map(|s| s.to_string_lossy().to_string())
.unwrap_or_else(|| "a".to_string());
let obj_name = format!("{}.o", stem);
let out = Path::new(&self.directory).join(&obj_name);
self.output = Some(out.display().to_string());
}
}
#[derive(Debug, Clone)]
pub struct X86CompilationDatabase {
pub commands: Vec<X86CompileCommand>,
pub db_path: Option<PathBuf>,
pub build_system: Option<String>,
pub verified: bool,
}
impl X86CompilationDatabase {
pub fn new() -> Self {
Self {
commands: Vec::new(),
db_path: None,
build_system: None,
verified: false,
}
}
pub fn load(path: &str) -> io::Result<Self> {
let content = fs::read_to_string(path)?;
Self::parse(&content, Some(PathBuf::from(path)))
}
pub fn parse(json_content: &str, db_path: Option<PathBuf>) -> io::Result<Self> {
let mut db = Self::new();
db.db_path = db_path;
let trimmed = json_content.trim();
if !trimmed.starts_with('[') {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"compile_commands.json must start with '['",
));
}
let objects = Self::parse_json_array(trimmed)?;
for obj_str in &objects {
if let Some(cmd) = Self::parse_command_object(obj_str) {
db.commands.push(cmd);
}
}
db.verified = true;
Ok(db)
}
fn parse_json_array(input: &str) -> io::Result<Vec<String>> {
let mut objects = Vec::new();
let chars: Vec<char> = input.chars().collect();
let mut i = 0;
let len = chars.len();
while i < len && chars[i].is_whitespace() {
i += 1;
}
if i < len && chars[i] == '[' {
i += 1;
}
while i < len {
while i < len && chars[i].is_whitespace() {
i += 1;
}
if i >= len {
break;
}
if chars[i] == ']' {
break;
}
if chars[i] == '{' {
let mut depth = 0;
let start = i;
let mut in_string = false;
let mut escaped = false;
while i < len {
let c = chars[i];
if escaped {
escaped = false;
} else if c == '\\' && in_string {
escaped = true;
} else if c == '"' {
in_string = !in_string;
} else if !in_string {
if c == '{' {
depth += 1;
}
if c == '}' {
depth -= 1;
}
}
i += 1;
if depth == 0 && !in_string {
break;
}
}
let obj: String = chars[start..i].iter().collect();
objects.push(obj);
while i < len && chars[i].is_whitespace() {
i += 1;
}
if i < len && chars[i] == ',' {
i += 1;
}
} else {
i += 1;
}
}
Ok(objects)
}
fn parse_command_object(obj: &str) -> Option<X86CompileCommand> {
let directory = Self::extract_json_string(obj, "directory")?;
let file = Self::extract_json_string(obj, "file")
.or_else(|| Self::extract_json_string(obj, "file"))?;
let arguments = if obj.contains("\"arguments\"") {
Self::extract_json_array(obj, "arguments")
} else if obj.contains("\"command\"") {
let cmd = Self::extract_json_string(obj, "command")?;
shell_split(&cmd)
} else {
return None;
};
let output = Self::extract_json_string(obj, "output");
Some(X86CompileCommand {
directory,
file,
arguments,
output,
toolchain_kind: None,
})
}
fn extract_json_string(obj: &str, key: &str) -> Option<String> {
let pattern = format!("\"{}\"", key);
if let Some(pos) = obj.find(&pattern) {
let rest = &obj[pos + pattern.len()..];
let after_key = rest.trim_start();
if let Some(after_colon) = after_key.strip_prefix(':') {
let trimmed = after_colon.trim_start();
if trimmed.starts_with('"') {
let mut result = String::new();
let chars: Vec<char> = trimmed[1..].chars().collect();
let mut i = 0;
let mut escaped = false;
while i < chars.len() {
let c = chars[i];
if escaped {
result.push(match c {
'n' => '\n',
't' => '\t',
'r' => '\r',
'\\' => '\\',
'"' => '"',
_ => c,
});
escaped = false;
} else if c == '\\' {
escaped = true;
} else if c == '"' {
break;
} else {
result.push(c);
}
i += 1;
}
return Some(result);
}
}
}
None
}
fn extract_json_array(obj: &str, key: &str) -> Vec<String> {
let pattern = format!("\"{}\"", key);
if let Some(pos) = obj.find(&pattern) {
let rest = &obj[pos + pattern.len()..];
let after_key = rest.trim_start();
if let Some(after_colon) = after_key.strip_prefix(':') {
let trimmed = after_colon.trim_start();
if trimmed.starts_with('[') {
let mut args = Vec::new();
let inner = &trimmed[1..];
let mut depth = 0;
let mut current = String::new();
let mut in_string = false;
let mut escaped = false;
for c in inner.chars() {
if escaped {
current.push(c);
escaped = false;
continue;
}
if c == '\\' && in_string {
escaped = true;
current.push(c);
continue;
}
if c == '"' {
in_string = !in_string;
current.push(c);
continue;
}
if !in_string {
if c == '[' {
depth += 1;
current.push(c);
} else if c == ']' {
if depth == 0 {
let arg = current.trim().to_string();
if !arg.is_empty() {
let cleaned = arg.trim_matches('"').to_string();
args.push(cleaned);
}
break;
}
depth -= 1;
current.push(c);
} else if c == ',' && depth == 0 {
let arg = current.trim().to_string();
let cleaned = arg.trim_matches('"').to_string();
if !cleaned.is_empty() {
args.push(cleaned);
}
current = String::new();
} else {
current.push(c);
}
} else {
current.push(c);
}
}
return args;
}
}
}
Vec::new()
}
pub fn generate_json(&self) -> String {
let mut json = String::from("[\n");
for (i, cmd) in self.commands.iter().enumerate() {
json.push_str(" {\n");
json.push_str(&format!(
" \"directory\": \"{}\"",
Self::escape_json(&cmd.directory)
));
if let Some(ref output) = cmd.output {
json.push_str(",\n");
json.push_str(&format!(
" \"output\": \"{}\"",
Self::escape_json(output)
));
}
json.push_str(",\n");
json.push_str(&format!(
" \"file\": \"{}\"",
Self::escape_json(&cmd.file)
));
json.push_str(",\n");
json.push_str(" \"arguments\": [\n");
for (j, arg) in cmd.arguments.iter().enumerate() {
let suffix = if j + 1 < cmd.arguments.len() { "," } else { "" };
json.push_str(&format!(" \"{}\"{}", Self::escape_json(arg), suffix));
json.push('\n');
}
json.push_str(" ]\n");
json.push_str(" }");
if i + 1 < self.commands.len() {
json.push(',');
}
json.push('\n');
}
json.push_str("]\n");
json
}
pub fn write_to_file(&self, path: &str) -> io::Result<()> {
let json = self.generate_json();
let mut file = fs::File::create(path)?;
file.write_all(json.as_bytes())
}
fn escape_json(s: &str) -> String {
let mut result = String::new();
for c in s.chars() {
match c {
'"' => result.push_str("\\\""),
'\\' => result.push_str("\\\\"),
'\n' => result.push_str("\\n"),
'\r' => result.push_str("\\r"),
'\t' => result.push_str("\\t"),
_ => result.push(c),
}
}
result
}
pub fn add_command(&mut self, cmd: X86CompileCommand) {
self.commands.push(cmd);
}
pub fn find_by_file(&self, file: &str) -> Vec<&X86CompileCommand> {
self.commands
.iter()
.filter(|cmd| {
cmd.file == file
|| cmd.file.ends_with(&format!("/{}", file))
|| cmd.file.ends_with(&format!("\\{}", file))
})
.collect()
}
pub fn all_files(&self) -> Vec<&str> {
let mut files: Vec<&str> = self.commands.iter().map(|c| c.file.as_str()).collect();
files.sort();
files.dedup();
files
}
pub fn all_directories(&self) -> Vec<&str> {
let mut dirs: Vec<&str> = self.commands.iter().map(|c| c.directory.as_str()).collect();
dirs.sort();
dirs.dedup();
dirs
}
pub fn len(&self) -> usize {
self.commands.len()
}
pub fn is_empty(&self) -> bool {
self.commands.is_empty()
}
pub fn from_cmake_build_dir(build_dir: &str) -> io::Result<Self> {
let json_path = Path::new(build_dir).join("compile_commands.json");
Self::load(json_path.to_str().unwrap_or("compile_commands.json"))
}
pub fn from_make_build(build_dir: &str, make_target: &str) -> io::Result<Self> {
let output = ProcessCommand::new("bear")
.arg("--")
.arg("make")
.arg("-C")
.arg(build_dir)
.arg(make_target)
.output()?;
if output.status.success() {
let json_path = Path::new(build_dir).join("compile_commands.json");
if json_path.exists() {
return Self::load(json_path.to_str().unwrap());
}
}
Err(io::Error::new(
io::ErrorKind::Other,
"Failed to generate compile_commands.json via bear",
))
}
pub fn from_ninja_build(build_dir: &str) -> io::Result<Self> {
let output = ProcessCommand::new("ninja")
.arg("-C")
.arg(build_dir)
.arg("-t")
.arg("compdb")
.output()?;
if output.status.success() {
let json = String::from_utf8_lossy(&output.stdout).to_string();
return Self::parse(&json, Some(PathBuf::from(build_dir)));
}
let json_path = Path::new(build_dir).join("compile_commands.json");
if json_path.exists() {
return Self::load(json_path.to_str().unwrap());
}
Err(io::Error::new(
io::ErrorKind::NotFound,
"No compile_commands.json found in ninja build directory",
))
}
pub fn from_bazel_build(workspace: &str) -> io::Result<Self> {
let output = ProcessCommand::new("bazel")
.arg("run")
.arg("@hedron_compile_commands//:refresh_all")
.current_dir(workspace)
.output()?;
if output.status.success() {
let json_path = Path::new(workspace).join("compile_commands.json");
if json_path.exists() {
return Self::load(json_path.to_str().unwrap());
}
}
Err(io::Error::new(
io::ErrorKind::Other,
"Failed to generate compile_commands.json via Bazel",
))
}
pub fn from_response_file(rsp_path: &str) -> io::Result<Vec<String>> {
let content = fs::read_to_string(rsp_path)?;
Ok(shell_split(&content))
}
}
impl Default for X86CompilationDatabase {
fn default() -> Self {
Self::new()
}
}
impl fmt::Display for X86CompilationDatabase {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(
f,
"X86CompilationDatabase ({} commands):",
self.commands.len()
)?;
for cmd in &self.commands {
writeln!(f, " {} -> {:?}", cmd.file, cmd.output)?;
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct X86SysrootManager {
pub sysroot: Option<PathBuf>,
pub target_triple: String,
pub kind: SysrootKind,
pub auto_detected: bool,
pub extra_search_paths: Vec<PathBuf>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SysrootKind {
LinuxNative,
LinuxCross,
AndroidNdk,
MacOsSdk,
IosSdk,
WindowsSdk,
WasiSysroot,
Custom,
}
impl SysrootKind {
pub fn as_str(&self) -> &'static str {
match self {
Self::LinuxNative => "linux-native",
Self::LinuxCross => "linux-cross",
Self::AndroidNdk => "android-ndk",
Self::MacOsSdk => "macos-sdk",
Self::IosSdk => "ios-sdk",
Self::WindowsSdk => "windows-sdk",
Self::WasiSysroot => "wasi",
Self::Custom => "custom",
}
}
}
impl X86SysrootManager {
pub fn new(target_triple: &str) -> Self {
Self {
sysroot: None,
target_triple: target_triple.to_string(),
kind: SysrootKind::LinuxNative,
auto_detected: true,
extra_search_paths: Vec::new(),
}
}
pub fn detect(&mut self) -> Option<PathBuf> {
let triple = &self.target_triple;
if triple.contains("android") {
self.detect_android_sysroot()
} else if triple.contains("darwin") || triple.contains("macos") {
self.detect_macos_sdk()
} else if triple.contains("ios") {
self.detect_ios_sdk()
} else if triple.contains("windows") {
self.detect_windows_sdk()
} else if triple.contains("wasi") {
self.detect_wasi_sysroot()
} else if self.is_cross_compilation(triple) {
self.detect_linux_cross_sysroot()
} else {
self.kind = SysrootKind::LinuxNative;
self.sysroot = Some(PathBuf::from("/"));
self.auto_detected = true;
self.sysroot.clone()
}
}
fn is_cross_compilation(&self, triple: &str) -> bool {
let host = detect_host_triple();
triple != host && !triple.is_empty()
}
pub fn detect_linux_cross_sysroot(&mut self) -> Option<PathBuf> {
self.kind = SysrootKind::LinuxCross;
let search_paths = vec![
format!("/usr/{}", self.target_triple),
format!("/usr/{}/sysroot", self.target_triple),
format!("/opt/cross/{}/sysroot", self.target_triple),
format!("/sysroot/{}", self.target_triple),
];
for p in &search_paths {
let path = PathBuf::from(p);
if path.exists() && path.join("usr/include").exists() {
self.sysroot = Some(path);
self.auto_detected = true;
return self.sysroot.clone();
}
}
for extra in &self.extra_search_paths {
if extra.exists() && extra.join("usr/include").exists() {
self.sysroot = Some(extra.clone());
self.auto_detected = true;
return self.sysroot.clone();
}
}
for var in &["SYSROOT", "CROSS_SYSROOT", "SDKTARGETSYSROOT"] {
if let Ok(val) = env::var(var) {
let p = PathBuf::from(val);
if p.exists() {
self.sysroot = Some(p);
self.auto_detected = true;
return self.sysroot.clone();
}
}
}
None
}
pub fn detect_android_sysroot(&mut self) -> Option<PathBuf> {
self.kind = SysrootKind::AndroidNdk;
let ndk_paths = vec![
"/opt/android-ndk",
"/usr/local/android-ndk",
"/home/*/Android/Sdk/ndk",
"/usr/lib/android-ndk",
];
if let Ok(ndk_home) = env::var("ANDROID_NDK_HOME") {
let p = PathBuf::from(&ndk_home);
if p.exists() {
let sysroot = p.join("toolchains/llvm/prebuilt/linux-x86_64/sysroot");
if sysroot.exists() {
self.sysroot = Some(sysroot);
self.auto_detected = true;
return self.sysroot.clone();
}
}
}
if let Ok(ndk_root) = env::var("ANDROID_NDK_ROOT") {
let p = PathBuf::from(&ndk_root);
if p.exists() {
let sysroot = p.join("toolchains/llvm/prebuilt/linux-x86_64/sysroot");
if sysroot.exists() {
self.sysroot = Some(sysroot);
self.auto_detected = true;
return self.sysroot.clone();
}
}
}
for ndk_path in &ndk_paths {
let expanded = shellexpand_env(ndk_path);
let p = PathBuf::from(&expanded);
if p.exists() {
let sysroot = p.join("toolchains/llvm/prebuilt/linux-x86_64/sysroot");
if sysroot.exists() {
self.sysroot = Some(sysroot);
self.auto_detected = true;
return self.sysroot.clone();
}
}
}
None
}
pub fn detect_macos_sdk(&mut self) -> Option<PathBuf> {
self.kind = SysrootKind::MacOsSdk;
if let Ok(output) = ProcessCommand::new("xcrun")
.args(["--sdk", "macosx", "--show-sdk-path"])
.output()
{
if output.status.success() {
let path_str = String::from_utf8_lossy(&output.stdout).trim().to_string();
let p = PathBuf::from(&path_str);
if p.exists() {
self.sysroot = Some(p);
self.auto_detected = true;
return self.sysroot.clone();
}
}
}
if let Ok(sdk_root) = env::var("SDKROOT") {
let p = PathBuf::from(&sdk_root);
if p.exists() {
self.sysroot = Some(p);
self.auto_detected = true;
return self.sysroot.clone();
}
}
let fallbacks = vec![
"/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk",
"/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk",
];
for fb in &fallbacks {
let p = PathBuf::from(fb);
if p.exists() {
self.sysroot = Some(p);
self.auto_detected = true;
return self.sysroot.clone();
}
}
None
}
pub fn detect_ios_sdk(&mut self) -> Option<PathBuf> {
self.kind = SysrootKind::IosSdk;
if let Ok(output) = ProcessCommand::new("xcrun")
.args(["--sdk", "iphoneos", "--show-sdk-path"])
.output()
{
if output.status.success() {
let path_str = String::from_utf8_lossy(&output.stdout).trim().to_string();
let p = PathBuf::from(&path_str);
if p.exists() {
self.sysroot = Some(p);
self.auto_detected = true;
return self.sysroot.clone();
}
}
}
None
}
pub fn detect_windows_sdk(&mut self) -> Option<PathBuf> {
self.kind = SysrootKind::WindowsSdk;
let search = vec![
"C:/Program Files (x86)/Windows Kits/10",
"C:/Program Files/Windows Kits/10",
];
for s in &search {
let p = PathBuf::from(s);
if p.exists() {
self.sysroot = Some(p);
self.auto_detected = true;
return self.sysroot.clone();
}
}
if let Ok(winsdk) = env::var("WindowsSdkDir") {
let p = PathBuf::from(&winsdk);
if p.exists() {
self.sysroot = Some(p);
self.auto_detected = true;
return self.sysroot.clone();
}
}
None
}
pub fn detect_wasi_sysroot(&mut self) -> Option<PathBuf> {
self.kind = SysrootKind::WasiSysroot;
let search = vec![
"/opt/wasi-sdk/share/wasi-sysroot",
"/usr/share/wasi-sysroot",
"/usr/local/wasi-sdk/share/wasi-sysroot",
];
for s in &search {
let p = PathBuf::from(s);
if p.exists() {
self.sysroot = Some(p);
self.auto_detected = true;
return self.sysroot.clone();
}
}
if let Ok(wasi_sdk) = env::var("WASI_SDK_PATH") {
let p = PathBuf::from(&wasi_sdk).join("share/wasi-sysroot");
if p.exists() {
self.sysroot = Some(p);
self.auto_detected = true;
return self.sysroot.clone();
}
}
None
}
pub fn set_custom_sysroot(&mut self, path: &str) {
self.sysroot = Some(PathBuf::from(path));
self.kind = SysrootKind::Custom;
self.auto_detected = false;
}
pub fn include_path(&self) -> Option<PathBuf> {
self.sysroot.as_ref().map(|s| s.join("usr/include"))
}
pub fn lib_path(&self) -> Option<PathBuf> {
self.sysroot.as_ref().map(|s| s.join("usr/lib"))
}
pub fn sysroot_flags(&self) -> Vec<String> {
let mut flags = Vec::new();
if let Some(ref sysroot) = self.sysroot {
flags.push(format!("--sysroot={}", sysroot.display()));
match self.kind {
SysrootKind::LinuxNative => {}
SysrootKind::LinuxCross => {
flags.push(format!("-B{}/usr/lib", sysroot.display()));
}
SysrootKind::AndroidNdk => {
flags.push(format!("--target={}", self.target_triple));
}
SysrootKind::MacOsSdk | SysrootKind::IosSdk => {
flags.push(format!("-isysroot {}", sysroot.display()));
}
SysrootKind::WindowsSdk => {
flags.push(format!(
"-internal-isystem \"{}/Include/10.0.22621.0/ucrt\"",
sysroot.display()
));
}
SysrootKind::WasiSysroot => {
flags.push(format!("--target=wasm32-wasi"));
}
_ => {}
}
}
flags
}
}
impl Default for X86SysrootManager {
fn default() -> Self {
Self::new("x86_64-unknown-linux-gnu")
}
}
#[derive(Debug, Clone)]
pub struct X86LibrarySearch {
pub standard_paths: Vec<PathBuf>,
pub gcc_lib_paths: Vec<PathBuf>,
pub env_library_paths: Vec<PathBuf>,
pub ld_library_paths: Vec<PathBuf>,
pub rpath_entries: Vec<String>,
pub runpath_entries: Vec<String>,
pub use_rpath: bool,
pub use_runpath: bool,
pub sysroot: Option<PathBuf>,
pub target_triple: String,
pub multiarch_tuple: Option<String>,
pub strict: bool,
}
impl X86LibrarySearch {
pub fn new(target_triple: &str) -> Self {
Self {
standard_paths: Self::default_standard_paths(target_triple),
gcc_lib_paths: Vec::new(),
env_library_paths: Vec::new(),
ld_library_paths: Vec::new(),
rpath_entries: Vec::new(),
runpath_entries: Vec::new(),
use_rpath: false,
use_runpath: true,
sysroot: None,
target_triple: target_triple.to_string(),
multiarch_tuple: None,
strict: false,
}
}
fn default_standard_paths(target_triple: &str) -> Vec<PathBuf> {
let mut paths = vec![
PathBuf::from("/usr/local/lib"),
PathBuf::from("/usr/lib"),
PathBuf::from("/lib"),
PathBuf::from("/usr/lib64"),
];
if target_triple.contains("x86_64") {
paths.push(PathBuf::from("/usr/lib/x86_64-linux-gnu"));
paths.push(PathBuf::from("/lib/x86_64-linux-gnu"));
} else if target_triple.contains("i686") || target_triple.contains("i386") {
paths.push(PathBuf::from("/usr/lib/i386-linux-gnu"));
paths.push(PathBuf::from("/usr/lib32"));
}
paths
}
pub fn parse_library_path(&mut self) {
if let Ok(lib_path) = env::var("LIBRARY_PATH") {
for p in lib_path.split(':') {
let path = PathBuf::from(p);
if path.exists() && !self.env_library_paths.contains(&path) {
self.env_library_paths.push(path);
}
}
}
}
pub fn parse_ld_library_path(&mut self) {
if let Ok(ld_path) = env::var("LD_LIBRARY_PATH") {
for p in ld_path.split(':') {
let path = PathBuf::from(p);
if path.exists() && !self.ld_library_paths.contains(&path) {
self.ld_library_paths.push(path);
}
}
}
}
pub fn parse_ldflags(&mut self) {
if let Ok(ldflags) = env::var("LDFLAGS") {
let parts = shell_split(&ldflags);
let mut i = 0;
while i < parts.len() {
if parts[i] == "-L" && i + 1 < parts.len() {
let path = PathBuf::from(&parts[i + 1]);
if path.exists() && !self.env_library_paths.contains(&path) {
self.env_library_paths.push(path);
}
i += 1;
} else if parts[i].starts_with("-L") {
let path = PathBuf::from(&parts[i][2..]);
if path.exists() && !self.env_library_paths.contains(&path) {
self.env_library_paths.push(path);
}
}
i += 1;
}
}
}
pub fn scan_gcc_libraries(&mut self, gcc_install: &Path) {
let gcc_lib = gcc_install.join("lib/gcc");
if gcc_lib.exists() {
if let Ok(entries) = fs::read_dir(&gcc_lib) {
for triple_entry in entries.filter_map(|e| e.ok()) {
let triple_dir = triple_entry.path();
if let Ok(ver_entries) = fs::read_dir(&triple_dir) {
for ver_entry in ver_entries.filter_map(|e| e.ok()) {
let p = ver_entry.path();
if p.is_dir() && !self.gcc_lib_paths.contains(&p) {
self.gcc_lib_paths.push(p);
}
}
}
}
}
}
}
pub fn add_gcc_lib_path(&mut self, path: &Path) {
if path.exists() && !self.gcc_lib_paths.contains(&path.to_path_buf()) {
self.gcc_lib_paths.push(path.to_path_buf());
}
}
pub fn add_rpath(&mut self, path: &str) {
if !self.rpath_entries.contains(&path.to_string()) {
self.rpath_entries.push(path.to_string());
}
}
pub fn add_runpath(&mut self, path: &str) {
if !self.runpath_entries.contains(&path.to_string()) {
self.runpath_entries.push(path.to_string());
}
}
pub fn linker_search_flags(&self) -> Vec<String> {
let mut flags = Vec::new();
for p in &self.gcc_lib_paths {
flags.push(format!("-L{}", p.display()));
}
for p in &self.standard_paths {
flags.push(format!("-L{}", p.display()));
}
for p in &self.env_library_paths {
flags.push(format!("-L{}", p.display()));
}
flags
}
pub fn rpath_flags(&self) -> Vec<String> {
let mut flags = Vec::new();
if self.use_rpath {
for entry in &self.rpath_entries {
flags.push(format!("-Wl,-rpath,{}", entry));
}
}
if self.use_runpath {
for entry in &self.runpath_entries {
flags.push(format!("-Wl,--enable-new-dtags,-rpath,{}", entry));
}
}
flags
}
pub fn setup_origin_rpath(&mut self, relative_path: &str) {
self.use_rpath = true;
self.add_rpath("$ORIGIN");
self.add_rpath(&format!("$ORIGIN/{}", relative_path));
self.add_rpath(&format!("$ORIGIN/{}/lib", relative_path));
}
pub fn find_library(&self, name: &str) -> Option<PathBuf> {
let lib_names = vec![
format!("lib{}.so", name),
format!("lib{}.a", name),
format!("{}.lib", name), format!("lib{}.dylib", name), ];
let all_paths: Vec<&PathBuf> = self
.gcc_lib_paths
.iter()
.chain(self.standard_paths.iter())
.chain(self.env_library_paths.iter())
.collect();
for lib_name in &lib_names {
for path in &all_paths {
let candidate = path.join(lib_name);
if candidate.exists() {
return Some(candidate);
}
}
}
None
}
pub fn has_library(&self, name: &str) -> bool {
self.find_library(name).is_some()
}
pub fn list_libraries(&self, pattern: &str) -> Vec<String> {
let mut results = Vec::new();
let all_paths: Vec<&PathBuf> = self
.gcc_lib_paths
.iter()
.chain(self.standard_paths.iter())
.chain(self.env_library_paths.iter())
.collect();
for path in &all_paths {
if let Ok(entries) = fs::read_dir(path) {
for entry in entries.filter_map(|e| e.ok()) {
let fname = entry.file_name().to_string_lossy().to_string();
if fname.contains(pattern)
&& (fname.ends_with(".so")
|| fname.ends_with(".a")
|| fname.ends_with(".dylib"))
{
results.push(fname);
}
}
}
}
results.sort();
results.dedup();
results
}
}
impl Default for X86LibrarySearch {
fn default() -> Self {
Self::new("x86_64-unknown-linux-gnu")
}
}
#[derive(Debug, Clone)]
pub struct X86CompilerWrapper {
pub ccache_available: bool,
pub ccache_path: Option<PathBuf>,
pub distcc_available: bool,
pub distcc_path: Option<PathBuf>,
pub icecream_available: bool,
pub icecream_path: Option<PathBuf>,
pub sccache_available: bool,
pub sccache_path: Option<PathBuf>,
pub active_wrapper: CompilerWrapperKind,
pub cache_dir: Option<PathBuf>,
pub max_cache_size: Option<String>,
pub compress_cache: bool,
pub hash_dir: bool,
pub ccache_options: Vec<String>,
pub distcc_hosts: Vec<String>,
pub icecream_scheduler: Option<String>,
pub sccache_storage: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompilerWrapperKind {
None,
Ccache,
Distcc,
IceCream,
Sccache,
}
impl CompilerWrapperKind {
pub fn as_str(&self) -> &'static str {
match self {
Self::None => "none",
Self::Ccache => "ccache",
Self::Distcc => "distcc",
Self::IceCream => "icecream",
Self::Sccache => "sccache",
}
}
}
impl X86CompilerWrapper {
pub fn new() -> Self {
Self {
ccache_available: false,
ccache_path: None,
distcc_available: false,
distcc_path: None,
icecream_available: false,
icecream_path: None,
sccache_available: false,
sccache_path: None,
active_wrapper: CompilerWrapperKind::None,
cache_dir: None,
max_cache_size: None,
compress_cache: true,
hash_dir: true,
ccache_options: Vec::new(),
distcc_hosts: Vec::new(),
icecream_scheduler: None,
sccache_storage: None,
}
}
pub fn detect(&mut self) {
self.ccache_path = which_in_path("ccache");
self.ccache_available = self.ccache_path.is_some();
self.distcc_path = which_in_path("distcc");
self.distcc_available = self.distcc_path.is_some();
self.icecream_path = which_in_path("icecc");
self.icecream_available = self.icecream_path.is_some();
self.sccache_path = which_in_path("sccache");
self.sccache_available = self.sccache_path.is_some();
if self.sccache_available {
self.active_wrapper = CompilerWrapperKind::Sccache;
} else if self.ccache_available {
self.active_wrapper = CompilerWrapperKind::Ccache;
}
}
pub fn wrap_command(&self, compiler: &str, args: &[String]) -> Vec<String> {
match self.active_wrapper {
CompilerWrapperKind::Ccache => {
let mut cmd = vec!["ccache".to_string()];
for opt in &self.ccache_options {
cmd.push(opt.clone());
}
cmd.push(compiler.to_string());
cmd.extend(args.iter().cloned());
cmd
}
CompilerWrapperKind::Distcc => {
let mut cmd = vec!["distcc".to_string()];
if !self.distcc_hosts.is_empty() {
}
cmd.push(compiler.to_string());
cmd.extend(args.iter().cloned());
cmd
}
CompilerWrapperKind::IceCream => {
let mut cmd = vec!["icecc".to_string()];
cmd.push(compiler.to_string());
cmd.extend(args.iter().cloned());
cmd
}
CompilerWrapperKind::Sccache => {
let mut cmd = vec!["sccache".to_string()];
if self.sccache_storage.is_some() {
}
cmd.push(compiler.to_string());
cmd.extend(args.iter().cloned());
cmd
}
CompilerWrapperKind::None => {
let mut cmd = vec![compiler.to_string()];
cmd.extend(args.iter().cloned());
cmd
}
}
}
pub fn wrapper_environment(&self) -> HashMap<String, String> {
let mut env = HashMap::new();
match self.active_wrapper {
CompilerWrapperKind::Ccache => {
if let Some(ref dir) = self.cache_dir {
env.insert("CCACHE_DIR".to_string(), dir.display().to_string());
}
if let Some(ref size) = self.max_cache_size {
env.insert("CCACHE_MAXSIZE".to_string(), size.clone());
}
if self.compress_cache {
env.insert("CCACHE_COMPRESS".to_string(), "1".to_string());
}
if self.hash_dir {
env.insert("CCACHE_HASHDIR".to_string(), "true".to_string());
}
env.insert(
"CCACHE_SLOPPINESS".to_string(),
"file_macro,time_macros,include_file_mtime,include_file_ctime,pch_defines"
.to_string(),
);
}
CompilerWrapperKind::Distcc => {
if !self.distcc_hosts.is_empty() {
env.insert("DISTCC_HOSTS".to_string(), self.distcc_hosts.join(" "));
}
}
CompilerWrapperKind::IceCream => {
if let Some(ref sched) = self.icecream_scheduler {
env.insert("ICECC_SCHEDULER".to_string(), sched.clone());
}
}
CompilerWrapperKind::Sccache => {
if let Some(ref dir) = self.cache_dir {
env.insert("SCCACHE_DIR".to_string(), dir.display().to_string());
}
if let Some(ref storage) = self.sccache_storage {
env.insert("SCCACHE_STORAGE".to_string(), storage.clone());
}
}
CompilerWrapperKind::None => {}
}
env
}
pub fn configure_ccache(&mut self, cache_dir: &str, max_size: &str) {
self.active_wrapper = CompilerWrapperKind::Ccache;
self.cache_dir = Some(PathBuf::from(cache_dir));
self.max_cache_size = Some(max_size.to_string());
}
pub fn configure_sccache(&mut self, cache_dir: &str, storage: &str) {
self.active_wrapper = CompilerWrapperKind::Sccache;
self.cache_dir = Some(PathBuf::from(cache_dir));
self.sccache_storage = Some(storage.to_string());
}
pub fn configure_distcc(&mut self, hosts: Vec<String>) {
self.active_wrapper = CompilerWrapperKind::Distcc;
self.distcc_hosts = hosts;
}
pub fn configure_icecream(&mut self, scheduler: &str) {
self.active_wrapper = CompilerWrapperKind::IceCream;
self.icecream_scheduler = Some(scheduler.to_string());
}
pub fn clear_cache(&self) -> io::Result<()> {
match self.active_wrapper {
CompilerWrapperKind::Ccache => {
ProcessCommand::new("ccache").arg("-C").status()?;
}
CompilerWrapperKind::Sccache => {
ProcessCommand::new("sccache")
.arg("--zero-stats")
.status()?;
}
_ => {}
}
Ok(())
}
pub fn cache_stats(&self) -> Option<String> {
match self.active_wrapper {
CompilerWrapperKind::Ccache => {
if let Ok(output) = ProcessCommand::new("ccache").arg("-s").output() {
return Some(String::from_utf8_lossy(&output.stdout).to_string());
}
}
CompilerWrapperKind::Sccache => {
if let Ok(output) = ProcessCommand::new("sccache").arg("--show-stats").output() {
return Some(String::from_utf8_lossy(&output.stdout).to_string());
}
}
_ => {}
}
None
}
}
impl Default for X86CompilerWrapper {
fn default() -> Self {
let mut wrapper = Self::new();
wrapper.detect();
wrapper
}
}
pub struct X86BuildSystemIntegration {
pub toolchain: X86Toolchain,
pub target_triple: String,
pub use_ccache: bool,
pub use_lld: bool,
pub use_gold: bool,
pub sysroot: Option<PathBuf>,
pub build_type: String,
}
impl X86BuildSystemIntegration {
pub fn new(toolchain: X86Toolchain) -> Self {
Self {
toolchain,
target_triple: "x86_64-unknown-linux-gnu".to_string(),
use_ccache: false,
use_lld: true,
use_gold: false,
sysroot: None,
build_type: "Release".to_string(),
}
}
pub fn generate_cmake_toolchain(&self) -> String {
let mut content = String::new();
content.push_str("# Auto-generated CMake toolchain file for X86\n");
content.push_str(&format!("# Generated by X86BuildSystemIntegration\n"));
content.push_str(&format!("# Toolchain: {}\n", self.toolchain.description));
content.push('\n');
content.push_str(&format!(
"set(CMAKE_SYSTEM_NAME {})\n",
self.cmake_system_name()
));
content.push_str(&format!(
"set(CMAKE_SYSTEM_PROCESSOR {})\n",
self.cmake_processor()
));
content.push_str(&format!("set(CMAKE_SYSTEM_VERSION 1)\n"));
if !self.toolchain.is_cross {
content.push_str(&format!(
"set(CMAKE_C_COMPILER_TARGET {})\n",
self.target_triple
));
content.push_str(&format!(
"set(CMAKE_CXX_COMPILER_TARGET {})\n",
self.target_triple
));
}
let cc = self
.toolchain
.resolve_cc_path()
.map(|p| p.display().to_string())
.unwrap_or_else(|| "gcc".to_string());
let cxx = self
.toolchain
.resolve_cxx_path()
.map(|p| p.display().to_string())
.unwrap_or_else(|| "g++".to_string());
if self.use_ccache {
content.push_str(&format!("set(CMAKE_C_COMPILER \"ccache {}\")\n", cc));
content.push_str(&format!("set(CMAKE_CXX_COMPILER \"ccache {}\")\n", cxx));
} else {
content.push_str(&format!("set(CMAKE_C_COMPILER \"{}\")\n", cc));
content.push_str(&format!("set(CMAKE_CXX_COMPILER \"{}\")\n", cxx));
}
if let Some(ref asm) = self.toolchain.asm_path {
content.push_str(&format!("set(CMAKE_ASM_COMPILER \"{}\")\n", asm.display()));
}
if self.use_lld && self.toolchain.ld_lld_path.is_some() {
content.push_str("set(CMAKE_LINKER \"lld\")\n");
content.push_str("set(CMAKE_EXE_LINKER_FLAGS_INIT \"-fuse-ld=lld\")\n");
content.push_str("set(CMAKE_SHARED_LINKER_FLAGS_INIT \"-fuse-ld=lld\")\n");
} else if self.use_gold {
content.push_str("set(CMAKE_EXE_LINKER_FLAGS_INIT \"-fuse-ld=gold\")\n");
content.push_str("set(CMAKE_SHARED_LINKER_FLAGS_INIT \"-fuse-ld=gold\")\n");
}
if let Some(ref ar) = self.toolchain.llvm_ar_path {
content.push_str(&format!("set(CMAKE_AR \"{}\")\n", ar.display()));
} else if let Some(ref ar) = self.toolchain.ar_path {
content.push_str(&format!("set(CMAKE_AR \"{}\")\n", ar.display()));
}
if let Some(ref ranlib) = self.toolchain.ranlib_path {
content.push_str(&format!("set(CMAKE_RANLIB \"{}\")\n", ranlib.display()));
}
if let Some(ref sysroot) = self.sysroot {
content.push_str(&format!("set(CMAKE_SYSROOT \"{}\")\n", sysroot.display()));
content.push_str(&format!(
"set(CMAKE_FIND_ROOT_PATH \"{}\")\n",
sysroot.display()
));
content.push_str("set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)\n");
content.push_str("set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)\n");
content.push_str("set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)\n");
content.push_str("set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)\n");
}
content.push_str(&format!(
"set(CMAKE_C_FLAGS_INIT \"{}\")\n",
self.toolchain.default_compiler_flags().join(" ")
));
content.push_str(&format!(
"set(CMAKE_CXX_FLAGS_INIT \"{}\")\n",
self.toolchain.default_compiler_flags().join(" ")
));
if self.toolchain.is_cross {
content.push_str(&format!(
"set(PKG_CONFIG_EXECUTABLE \"{}-pkg-config\")\n",
self.target_triple
.replace("unknown-", "")
.replace("-gnu", "")
.replace("-linux", "")
));
}
content
}
fn cmake_system_name(&self) -> &str {
if self.target_triple.contains("linux") {
"Linux"
} else if self.target_triple.contains("windows") {
"Windows"
} else if self.target_triple.contains("darwin") || self.target_triple.contains("macos") {
"Darwin"
} else {
"Generic"
}
}
fn cmake_processor(&self) -> &str {
if self.target_triple.contains("x86_64") {
"x86_64"
} else if self.target_triple.contains("i686") || self.target_triple.contains("i386") {
"i686"
} else {
"x86_64"
}
}
pub fn write_cmake_toolchain(&self, path: &str) -> io::Result<()> {
let content = self.generate_cmake_toolchain();
let mut file = fs::File::create(path)?;
file.write_all(content.as_bytes())
}
pub fn generate_make_rules(&self, sources: &[String], target: &str) -> String {
let cc = self
.toolchain
.resolve_cc_path()
.map(|p| p.display().to_string())
.unwrap_or_else(|| "gcc".to_string());
let cxx = self
.toolchain
.resolve_cxx_path()
.map(|p| p.display().to_string())
.unwrap_or_else(|| "g++".to_string());
let ar = self
.toolchain
.llvm_ar_path
.as_ref()
.map(|p| p.display().to_string())
.unwrap_or_else(|| "ar".to_string());
let mut makefile = String::new();
makefile.push_str(&format!("# Auto-generated Makefile for X86\n"));
makefile.push_str(&format!("# Target: {}\n", target));
makefile.push_str(&format!("# Toolchain: {}\n", self.toolchain.description));
makefile.push('\n');
let default_flags = self.toolchain.default_compiler_flags().join(" ");
let ldflags = self.toolchain.default_linker_flags().join(" ");
makefile.push_str(&format!("CC := {}\n", cc));
makefile.push_str(&format!("CXX := {}\n", cxx));
makefile.push_str(&format!("AR := {}\n", ar));
makefile.push_str(&format!("CFLAGS := {}\n", default_flags));
makefile.push_str(&format!("CXXFLAGS := $(CFLAGS)\n"));
makefile.push_str(&format!("LDFLAGS := {}\n", ldflags));
makefile.push_str(&format!("BUILD_TYPE := {}\n", self.build_type));
makefile.push('\n');
makefile.push_str("ifeq ($(BUILD_TYPE),Release)\n");
makefile.push_str(" CFLAGS += -O2 -DNDEBUG\n");
makefile.push_str("else ifeq ($(BUILD_TYPE),Debug)\n");
makefile.push_str(" CFLAGS += -O0 -g\n");
makefile.push_str("endif\n\n");
makefile.push_str("# Object files\n");
let objects: Vec<String> = sources
.iter()
.map(|s| {
let path = Path::new(s);
path.file_stem()
.map(|st| format!("{}.o", st.to_string_lossy()))
.unwrap_or_else(|| "unknown.o".to_string())
})
.collect();
makefile.push_str(&format!("OBJECTS := {}\n\n", objects.join(" ")));
makefile.push_str(&format!(".PHONY: all clean\n\n"));
makefile.push_str(&format!("all: {}\n\n", target));
makefile.push_str(&format!("{}: $(OBJECTS)\n", target));
makefile.push_str(&format!("\t$(CC) $(LDFLAGS) $^ -o $@\n\n"));
for (i, source) in sources.iter().enumerate() {
let ext = Path::new(source)
.extension()
.map(|e| e.to_string_lossy().to_string())
.unwrap_or_default();
let compiler = if ext == "cpp" || ext == "cxx" || ext == "cc" {
"$(CXX)"
} else {
"$(CC)"
};
makefile.push_str(&format!("{}: {}\n", objects[i], source));
makefile.push_str(&format!("\t{} $(CFLAGS) -c $< -o $@\n\n", compiler));
}
makefile.push_str(&format!("lib{}.a: $(OBJECTS)\n", target));
makefile.push_str(&format!("\t$(AR) rcs $@ $^\n\n"));
makefile.push_str("clean:\n");
makefile.push_str(&format!("\trm -f {} $(OBJECTS)\n", target));
makefile
}
pub fn write_make_rules(&self, sources: &[String], target: &str, path: &str) -> io::Result<()> {
let content = self.generate_make_rules(sources, target);
let mut file = fs::File::create(path)?;
file.write_all(content.as_bytes())
}
pub fn generate_bazel_toolchain(&self) -> String {
let cc = self
.toolchain
.resolve_cc_path()
.map(|p| p.display().to_string())
.unwrap_or_else(|| "gcc".to_string());
let cxx = self
.toolchain
.resolve_cxx_path()
.map(|p| p.display().to_string())
.unwrap_or_else(|| "g++".to_string());
let mut content = String::new();
content.push_str("# Auto-generated Bazel toolchain for X86\n");
content.push_str(&format!("# Toolchain: {}\n", self.toolchain.description));
content.push('\n');
content.push_str(
"load(\"@rules_cc//cc:defs.bzl\", \"cc_toolchain\", \"cc_toolchain_suite\")\n\n",
);
content.push_str("cc_toolchain_suite(\n");
content.push_str(" name = \"x86_toolchain_suite\",\n");
content.push_str(" toolchains = {\n");
content.push_str(&format!(
" \"{}\": \":x86_toolchain\",\n",
self.cmake_processor()
));
content.push_str(" },\n");
content.push_str(")\n\n");
content.push_str("cc_toolchain(\n");
content.push_str(" name = \"x86_toolchain\",\n");
content.push_str(" all_files = \":empty\",\n");
content.push_str(" compiler_files = \":empty\",\n");
content.push_str(" dwp_files = \":empty\",\n");
content.push_str(" linker_files = \":empty\",\n");
content.push_str(" objcopy_files = \":empty\",\n");
content.push_str(" strip_files = \":empty\",\n");
content.push_str(" supports_param_files = 0,\n");
content.push_str(" toolchain_config = \":x86_toolchain_config\",\n");
content.push_str(")\n\n");
content.push_str("filegroup(name = \"empty\")\n\n");
content.push_str("load(\"@rules_cc//cc:defs.bzl\", \"cc_toolchain_config\")\n");
content.push_str("cc_toolchain_config(\n");
content.push_str(" name = \"x86_toolchain_config\",\n");
content.push_str(&format!(" cpu = \"{}\",\n", self.cmake_processor()));
content.push_str(&format!(" compiler = \"{}\",\n", cc));
content.push_str(&format!(
" toolchain_identifier = \"{}\",\n",
self.target_triple.replace('-', "_")
));
content.push_str(&format!(
" host_system_name = \"{}\",\n",
if cfg!(target_os = "linux") {
"linux"
} else {
"unknown"
}
));
content.push_str(&format!(
" target_system_name = \"{}\",\n",
self.cmake_system_name().to_lowercase()
));
content.push_str(&format!(
" target_cpu = \"{}\",\n",
self.cmake_processor()
));
content.push_str(&format!(
" target_libc = \"{}\",\n",
if self.target_triple.contains("musl") {
"musl"
} else {
"glibc"
}
));
content.push_str(&format!(
" abi_version = \"{}\",\n",
self.toolchain.abi.as_str()
));
content.push_str(&format!(" abi_libc_version = \"{}\",\n", "default"));
content.push('\n');
content.push_str(&format!(" tool_paths = {{\n"));
content.push_str(&format!(
" \"ar\": \"{}\",\n",
self.toolchain
.ar_path
.as_ref()
.map(|p| p.display().to_string())
.unwrap_or_else(|| "ar".to_string())
));
content.push_str(&format!(" \"cpp\": \"{}\",\n", cxx));
content.push_str(&format!(" \"gcc\": \"{}\",\n", cc));
content.push_str(&format!(" \"gcov\": \"gcov\",\n"));
content.push_str(&format!(
" \"ld\": \"{}\",\n",
self.toolchain
.ld_path
.as_ref()
.map(|p| p.display().to_string())
.unwrap_or_else(|| "ld".to_string())
));
content.push_str(&format!(" \"nm\": \"nm\",\n"));
content.push_str(&format!(" \"objcopy\": \"objcopy\",\n"));
content.push_str(&format!(" \"objdump\": \"objdump\",\n"));
content.push_str(&format!(" \"strip\": \"strip\",\n"));
content.push_str(" },\n");
content.push_str(" cxx_builtin_include_directories = [\n");
content.push_str(&format!(
" \"{}\",\n",
self.toolchain.include_dir.display()
));
if let Some(ref cxx) = self.toolchain.cxx_include_dir {
content.push_str(&format!(" \"{}\",\n", cxx.display()));
}
content.push_str(" ],\n");
content.push_str(" compile_flags = [\n");
for flag in self.toolchain.default_compiler_flags() {
content.push_str(&format!(" \"{}\",\n", flag));
}
content.push_str(" ],\n");
content.push_str(" link_flags = [\n");
for flag in self.toolchain.default_linker_flags() {
content.push_str(&format!(" \"{}\",\n", flag));
}
content.push_str(" ],\n");
content.push_str(")\n");
content
}
pub fn write_bazel_toolchain(&self, path: &str) -> io::Result<()> {
let content = self.generate_bazel_toolchain();
let mut file = fs::File::create(path)?;
file.write_all(content.as_bytes())
}
}
pub fn detect_host_triple() -> String {
for cmd in &["gcc", "clang", "cc"] {
if let Ok(output) = ProcessCommand::new(cmd).arg("-dumpmachine").output() {
if output.status.success() {
let triple = String::from_utf8_lossy(&output.stdout).trim().to_string();
if !triple.is_empty() {
return triple;
}
}
}
}
if let Ok(target) = env::var("TARGET") {
if !target.is_empty() {
return target;
}
}
let arch = if cfg!(target_arch = "x86_64") {
"x86_64"
} else if cfg!(target_arch = "x86") {
"i686"
} else {
"unknown"
};
let vendor = "unknown";
let os = if cfg!(target_os = "linux") {
"linux"
} else if cfg!(target_os = "windows") {
"windows"
} else if cfg!(target_os = "macos") {
"darwin"
} else {
"unknown"
};
let env = if cfg!(target_env = "gnu") {
"gnu"
} else if cfg!(target_env = "msvc") {
"msvc"
} else if cfg!(target_env = "musl") {
"musl"
} else {
"unknown"
};
format!("{}-{}-{}-{}", arch, vendor, os, env)
}
pub fn which_in_path(name: &str) -> Option<PathBuf> {
if let Ok(paths) = env::var("PATH") {
for dir in paths.split(':') {
let candidate = Path::new(dir).join(name);
if candidate.exists() && candidate.is_file() {
return Some(candidate);
}
let exe_candidate = Path::new(dir).join(format!("{}.exe", name));
if exe_candidate.exists() && exe_candidate.is_file() {
return Some(exe_candidate);
}
}
}
None
}
pub fn find_file(dir: &Path, name: &str) -> Option<PathBuf> {
let direct = dir.join(name);
if direct.exists() {
return Some(direct);
}
let sub32 = dir.join("32").join(name);
if sub32.exists() {
return Some(sub32);
}
None
}
pub fn find_in_path(dir: &Path, name: &str) -> Option<PathBuf> {
let candidate = dir.join(name);
if candidate.exists() {
return Some(candidate);
}
let exe_candidate = dir.join(format!("{}.exe", name));
if exe_candidate.exists() {
return Some(exe_candidate);
}
None
}
pub fn shell_split(input: &str) -> Vec<String> {
let mut args = Vec::new();
let mut current = String::new();
let mut in_single = false;
let mut in_double = false;
let mut escaped = false;
for c in input.chars() {
if escaped {
current.push(c);
escaped = false;
continue;
}
match c {
'\\' => {
escaped = true;
current.push(c);
}
'\'' => {
if in_double {
current.push(c);
} else {
in_single = !in_single;
}
}
'"' => {
if in_single {
current.push(c);
} else {
in_double = !in_double;
}
}
' ' | '\t' | '\n' | '\r' => {
if in_single || in_double {
current.push(c);
} else if !current.is_empty() {
args.push(current.clone());
current.clear();
}
}
_ => {
current.push(c);
}
}
}
if !current.is_empty() {
args.push(current);
}
args
}
pub fn shellexpand_env(input: &str) -> String {
if input.starts_with("~/") {
if let Ok(home) = env::var("HOME") {
return input.replacen("~", &home, 1);
}
}
if input.contains("$HOME") {
if let Ok(home) = env::var("HOME") {
return input.replace("$HOME", &home);
}
}
if input.contains("${HOME}") {
if let Ok(home) = env::var("HOME") {
return input.replace("${HOME}", &home);
}
}
input.to_string()
}
fn toolchain_priority_score(tc: &X86Toolchain) -> i32 {
let mut score = 0;
if !tc.is_cross {
score += 1000;
}
if tc.is_installed {
score += 500;
}
if tc.is_usable() {
score += 250;
}
match tc.kind {
X86ToolchainKind::Gcc => score += 100,
X86ToolchainKind::Clang => score += 90,
X86ToolchainKind::LlvmFull => score += 95,
X86ToolchainKind::IccOneApi => score += 80,
X86ToolchainKind::IccClassic => score += 70,
X86ToolchainKind::Msvc => score += 60,
X86ToolchainKind::ClangMinGW => score += 50,
X86ToolchainKind::MinGW => score += 40,
X86ToolchainKind::BareMetalGcc => score += 30,
X86ToolchainKind::Custom(_) => score += 10,
}
score += (tc.compiler_version_major * 10 + tc.compiler_version_minor) as i32;
score
}
#[derive(Debug, Clone)]
pub struct X86PgoConfig {
pub instrument: bool,
pub profile_file: Option<PathBuf>,
pub profile_dir: Option<PathBuf>,
pub ir_level: bool,
pub context_sensitive: bool,
pub sample_profile: Option<PathBuf>,
pub minimal: bool,
pub debug_info_for_profiling: bool,
pub runtime: PgoRuntime,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PgoRuntime {
Llvm,
Gcc,
Auto,
}
impl X86PgoConfig {
pub fn new() -> Self {
Self {
instrument: false,
profile_file: None,
profile_dir: None,
ir_level: true,
context_sensitive: false,
sample_profile: None,
minimal: false,
debug_info_for_profiling: false,
runtime: PgoRuntime::Auto,
}
}
pub fn instrumentation_flags(&self) -> Vec<String> {
if !self.instrument {
return Vec::new();
}
let mut flags = vec!["-fprofile-instr-generate".to_string()];
if self.ir_level {
flags.push("-fprofile-generate".to_string());
}
if self.context_sensitive {
flags.push("-fcs-profile-generate".to_string());
}
if self.debug_info_for_profiling {
flags.push("-fdebug-info-for-profiling".to_string());
}
if let Some(ref dir) = self.profile_dir {
flags.push(format!("-fprofile-instr-generate={}", dir.display()));
}
flags
}
pub fn use_flags(&self) -> Vec<String> {
let mut flags = Vec::new();
if let Some(ref file) = self.profile_file {
flags.push(format!("-fprofile-instr-use={}", file.display()));
if self.minimal {
flags.push("-fprofile-sample-use".to_string());
}
}
if let Some(ref sample) = self.sample_profile {
flags.push(format!("-fprofile-sample-use={}", sample.display()));
}
flags
}
pub fn linker_flags(&self) -> Vec<String> {
if !self.instrument {
return Vec::new();
}
match self.runtime {
PgoRuntime::Llvm => vec!["-fprofile-instr-generate".to_string()],
PgoRuntime::Gcc => vec!["-fprofile-generate".to_string()],
PgoRuntime::Auto => {
vec!["-fprofile-instr-generate".to_string()]
}
}
}
pub fn with_instrument(mut self, enable: bool) -> Self {
self.instrument = enable;
self
}
pub fn with_profile_file(mut self, path: &str) -> Self {
self.profile_file = Some(PathBuf::from(path));
self
}
pub fn with_profile_dir(mut self, dir: &str) -> Self {
self.profile_dir = Some(PathBuf::from(dir));
self
}
pub fn with_ir_level(mut self, enable: bool) -> Self {
self.ir_level = enable;
self
}
pub fn with_context_sensitive(mut self, enable: bool) -> Self {
self.context_sensitive = enable;
self
}
pub fn with_sample_profile(mut self, path: &str) -> Self {
self.sample_profile = Some(PathBuf::from(path));
self
}
pub fn detect_runtime(&mut self, tc: &X86Toolchain) {
self.runtime = if tc.kind.is_clang_family() {
PgoRuntime::Llvm
} else if tc.kind.is_gcc_family() {
PgoRuntime::Gcc
} else {
PgoRuntime::Auto
};
}
}
impl Default for X86PgoConfig {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86ResponseFile {
pub path: PathBuf,
pub args: Vec<String>,
pub expand_env: bool,
pub max_command_line: usize,
}
impl X86ResponseFile {
pub const WINDOWS_MAX: usize = 8191;
pub const LINUX_MAX: usize = 131072;
pub fn new(path: &str) -> Self {
Self {
path: PathBuf::from(path),
args: Vec::new(),
expand_env: false,
max_command_line: Self::LINUX_MAX,
}
}
pub fn read(&mut self) -> io::Result<Vec<String>> {
let content = fs::read_to_string(&self.path)?;
let mut args = Vec::new();
for line in content.lines() {
let trimmed = line.trim();
if trimmed.is_empty() || trimmed.starts_with('#') {
continue;
}
let line_args = shell_split(trimmed);
for arg in line_args {
if self.expand_env {
args.push(shellexpand_env(&arg));
} else {
args.push(arg.to_string());
}
}
}
self.args = args.clone();
Ok(args)
}
pub fn write(&self) -> io::Result<()> {
let mut content = String::new();
content.push_str(&format!("# Response file generated by X86Toolchain\n"));
for arg in &self.args {
if arg.contains(' ') {
content.push_str(&format!("\"{}\"\n", arg));
} else {
content.push_str(&format!("{}\n", arg));
}
}
fs::write(&self.path, content)
}
pub fn needs_response_file(&self, args: &[String]) -> bool {
let total_len: usize = args.iter().map(|a| a.len() + 1).sum();
total_len > self.max_command_line
}
pub fn maybe_wrap(&mut self, args: &[String]) -> io::Result<Vec<String>> {
if self.needs_response_file(args) {
self.args = args.to_vec();
self.write()?;
Ok(vec![format!("@{}", self.path.display())])
} else {
Ok(args.to_vec())
}
}
pub fn for_platform(mut self, is_windows: bool) -> Self {
self.max_command_line = if is_windows {
Self::WINDOWS_MAX
} else {
Self::LINUX_MAX
};
self
}
pub fn with_env_expansion(mut self) -> Self {
self.expand_env = true;
self
}
}
impl Default for X86ResponseFile {
fn default() -> Self {
Self::new("/tmp/toolchain_args.rsp")
}
}
#[derive(Debug, Clone)]
pub struct X86CoverageConfig {
pub enabled: bool,
pub format: CoverageFormat,
pub output_file: Option<PathBuf>,
pub branch_coverage: bool,
pub mcdc_coverage: bool,
pub merge_profiles: bool,
pub restrict_to_dirs: Vec<PathBuf>,
pub exclude_files: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CoverageFormat {
Llvm,
Gcc,
SourceBased,
}
impl X86CoverageConfig {
pub fn new() -> Self {
Self {
enabled: false,
format: CoverageFormat::SourceBased,
output_file: None,
branch_coverage: false,
mcdc_coverage: false,
merge_profiles: false,
restrict_to_dirs: Vec::new(),
exclude_files: Vec::new(),
}
}
pub fn compiler_flags(&self) -> Vec<String> {
if !self.enabled {
return Vec::new();
}
let mut flags = Vec::new();
match self.format {
CoverageFormat::Llvm => {
flags.push("-fprofile-instr-generate".to_string());
flags.push("-fcoverage-mapping".to_string());
}
CoverageFormat::Gcc => {
flags.push("--coverage".to_string());
flags.push("-fprofile-arcs".to_string());
flags.push("-ftest-coverage".to_string());
}
CoverageFormat::SourceBased => {
flags.push("-fcoverage-mapping".to_string());
flags.push("-fprofile-instr-generate".to_string());
if self.mcdc_coverage {
flags.push("-fcoverage-mcdc".to_string());
}
}
}
if self.branch_coverage {
flags.push("-fprofile-instr-generate=branch_prob".to_string());
}
flags
}
pub fn linker_flags(&self) -> Vec<String> {
if !self.enabled {
return Vec::new();
}
match self.format {
CoverageFormat::Llvm | CoverageFormat::SourceBased => {
vec!["-fprofile-instr-generate".to_string()]
}
CoverageFormat::Gcc => {
vec!["--coverage".to_string(), "-lgcov".to_string()]
}
}
}
pub fn post_process_commands(&self) -> Vec<String> {
let mut cmds = Vec::new();
if !self.enabled {
return cmds;
}
match self.format {
CoverageFormat::Llvm | CoverageFormat::SourceBased => {
cmds.push(
"llvm-profdata merge -sparse default.profraw -o default.profdata".to_string(),
);
if self.merge_profiles {
cmds.push("llvm-profdata merge *.profraw -o merged.profdata".to_string());
}
}
CoverageFormat::Gcc => {
cmds.push("lcov --capture --directory . --output-file coverage.info".to_string());
cmds.push("genhtml coverage.info --output-directory coverage_html".to_string());
}
}
cmds
}
pub fn with_enabled(mut self, enable: bool) -> Self {
self.enabled = enable;
self
}
pub fn with_format(mut self, format: CoverageFormat) -> Self {
self.format = format;
self
}
pub fn with_branch_coverage(mut self, enable: bool) -> Self {
self.branch_coverage = enable;
self
}
pub fn with_mcdc(mut self, enable: bool) -> Self {
self.mcdc_coverage = enable;
self
}
pub fn detect_format(&mut self, tc: &X86Toolchain) {
self.format = if tc.kind.is_clang_family() {
CoverageFormat::SourceBased
} else if tc.kind.is_gcc_family() {
CoverageFormat::Gcc
} else {
CoverageFormat::Llvm
};
}
}
impl Default for X86CoverageConfig {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86LtoConfig {
pub mode: LtoMode,
pub thinlto_jobs: Option<u32>,
pub cache_dir: Option<PathBuf>,
pub cache_policy: Option<String>,
pub new_pass_manager: bool,
pub emit_summary: bool,
pub debug_thinlto: bool,
pub use_gold_plugin: bool,
pub plugin_path: Option<PathBuf>,
pub split_lto_units: bool,
pub unified_lto: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LtoMode {
None,
Full,
Thin,
}
impl LtoMode {
pub fn flag(&self) -> Option<&'static str> {
match self {
Self::None => None,
Self::Full => Some("-flto"),
Self::Thin => Some("-flto=thin"),
}
}
pub fn linker_flag(&self) -> Option<&'static str> {
match self {
Self::None => None,
Self::Full => Some("-flto"),
Self::Thin => Some("-flto=thin"),
}
}
}
impl X86LtoConfig {
pub fn new() -> Self {
Self {
mode: LtoMode::None,
thinlto_jobs: None,
cache_dir: None,
cache_policy: None,
new_pass_manager: true,
emit_summary: false,
debug_thinlto: false,
use_gold_plugin: false,
plugin_path: None,
split_lto_units: false,
unified_lto: false,
}
}
pub fn compiler_flags(&self) -> Vec<String> {
let mut flags = Vec::new();
match self.mode {
LtoMode::None => return flags,
LtoMode::Full => {
flags.push("-flto".to_string());
}
LtoMode::Thin => {
flags.push("-flto=thin".to_string());
if let Some(jobs) = self.thinlto_jobs {
flags.push(format!("-flto-jobs={}", jobs));
}
}
}
if let Some(ref dir) = self.cache_dir {
flags.push(format!("-flto-cache-dir={}", dir.display()));
}
if let Some(ref policy) = self.cache_policy {
flags.push(format!("-flto-cache-policy={}", policy));
}
if self.new_pass_manager {
flags.push("-fexperimental-new-pass-manager".to_string());
}
if self.emit_summary {
flags.push("-flto-embed-bitcode=summary".to_string());
}
if self.split_lto_units {
flags.push("-fsplit-lto-unit".to_string());
}
if self.unified_lto {
flags.push("-funified-lto".to_string());
}
flags
}
pub fn linker_flags(&self) -> Vec<String> {
let mut flags = Vec::new();
if self.mode == LtoMode::None {
return flags;
}
if let Some(ref plugin) = self.plugin_path {
flags.push(format!("-Wl,-plugin={}", plugin.display()));
} else if self.use_gold_plugin {
let candidates = vec![
"/usr/lib/LLVMgold.so",
"/usr/lib/bfd-plugins/LLVMgold.so",
"/usr/local/lib/LLVMgold.so",
];
for c in &candidates {
if Path::new(c).exists() {
flags.push(format!("-Wl,-plugin={}", c));
break;
}
}
}
match self.mode {
LtoMode::Full => {
flags.push("-Wl,-plugin-opt=O2".to_string());
}
LtoMode::Thin => {
flags.push("-Wl,-plugin-opt=thinlto".to_string());
if let Some(jobs) = self.thinlto_jobs {
flags.push(format!("-Wl,-plugin-opt=jobs={}", jobs));
}
}
_ => {}
}
if self.debug_thinlto {
flags.push("-Wl,-plugin-opt=-debug-only=thinlto".to_string());
}
flags
}
pub fn with_mode(mut self, mode: LtoMode) -> Self {
self.mode = mode;
self
}
pub fn with_thinlto_jobs(mut self, jobs: u32) -> Self {
self.thinlto_jobs = Some(jobs);
self
}
pub fn with_cache_dir(mut self, dir: &str) -> Self {
self.cache_dir = Some(PathBuf::from(dir));
self
}
pub fn detect_support(&mut self, tc: &X86Toolchain) {
if !tc.lto_supported {
self.mode = LtoMode::None;
return;
}
if self.mode == LtoMode::Thin && !tc.thin_lto_supported {
self.mode = LtoMode::Full;
}
}
}
impl Default for X86LtoConfig {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86SanitizerConfig {
pub enabled: Vec<String>,
pub recover: bool,
pub minimal_runtime: bool,
pub shared_runtime: bool,
pub blacklist_file: Option<PathBuf>,
pub extra_flags: Vec<String>,
pub trap_on_error: bool,
pub detect_odr_violations: bool,
pub detect_stack_use_after_return: bool,
pub detect_leaks: bool,
}
impl X86SanitizerConfig {
pub fn new() -> Self {
Self {
enabled: Vec::new(),
recover: false,
minimal_runtime: false,
shared_runtime: true,
blacklist_file: None,
extra_flags: Vec::new(),
trap_on_error: false,
detect_odr_violations: true,
detect_stack_use_after_return: false,
detect_leaks: true,
}
}
pub fn compiler_flags(&self) -> Vec<String> {
if self.enabled.is_empty() {
return Vec::new();
}
let mut flags = Vec::new();
let sanitize_list = self.enabled.join(",");
flags.push(format!("-fsanitize={}", sanitize_list));
if self.recover {
flags.push("-fsanitize-recover=all".to_string());
}
if self.minimal_runtime {
flags.push("-fsanitize-minimal-runtime".to_string());
}
if self.trap_on_error {
flags.push("-fsanitize-trap=all".to_string());
}
if !self.detect_leaks {
flags.push("-fno-sanitize-leak".to_string());
}
if self.enabled.contains(&"address".to_string()) {
flags.push("-fno-omit-frame-pointer".to_string());
flags.push("-fno-optimize-sibling-calls".to_string());
if self.detect_stack_use_after_return {
flags.push("-fsanitize-address-use-after-return=always".to_string());
}
if self.detect_odr_violations {
flags.push("-fsanitize-address-use-odr-indicator".to_string());
}
}
if self.enabled.contains(&"memory".to_string()) {
flags.push("-fsanitize-memory-track-origins".to_string());
}
if self.enabled.contains(&"undefined".to_string()) {
flags.push("-fsanitize=integer".to_string());
flags.push("-fsanitize=nullability".to_string());
}
if self.enabled.contains(&"thread".to_string()) {
flags.push("-fPIE".to_string());
}
if let Some(ref bl) = self.blacklist_file {
flags.push(format!("-fsanitize-blacklist={}", bl.display()));
}
flags.extend(self.extra_flags.clone());
flags
}
pub fn linker_flags(&self) -> Vec<String> {
if self.enabled.is_empty() {
return Vec::new();
}
let mut flags = Vec::new();
let sanitize_list = self.enabled.join(",");
flags.push(format!("-fsanitize={}", sanitize_list));
if self.shared_runtime {
flags.push("-shared-libsan".to_string());
}
flags
}
pub fn enable(&mut self, sanitizer: &str) {
if !self.enabled.contains(&sanitizer.to_string()) {
self.enabled.push(sanitizer.to_string());
}
}
pub fn disable(&mut self, sanitizer: &str) {
self.enabled.retain(|s| s != sanitizer);
}
pub fn with_blacklist(mut self, path: &str) -> Self {
self.blacklist_file = Some(PathBuf::from(path));
self
}
pub fn with_trap(mut self) -> Self {
self.trap_on_error = true;
self
}
pub fn is_supported(&self, sanitizer: &str, tc: &X86Toolchain) -> bool {
tc.supports_sanitizer(sanitizer)
}
}
impl Default for X86SanitizerConfig {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86DiagnosticsReport {
pub toolchains: Vec<X86Toolchain>,
pub host_triple: String,
pub host_cpu_count: u32,
pub host_memory_mb: u64,
pub env_vars: HashMap<String, String>,
pub available_tools: Vec<(String, Option<PathBuf>)>,
pub warnings: Vec<String>,
pub recommendations: Vec<String>,
}
impl X86DiagnosticsReport {
pub fn generate(manager: &X86ToolchainManager) -> Self {
let toolchains: Vec<X86Toolchain> = manager.list_installed().into_iter().cloned().collect();
let host_triple = detect_host_triple();
let mut env_vars = HashMap::new();
for var in &[
"CC",
"CXX",
"CFLAGS",
"CXXFLAGS",
"LDFLAGS",
"PATH",
"LD_LIBRARY_PATH",
"LIBRARY_PATH",
] {
if let Ok(val) = env::var(var) {
env_vars.insert(var.to_string(), val);
}
}
let mut available_tools = Vec::new();
for (name, candidates) in &[
("gcc", &["gcc", "cc"] as &[&str]),
("g++", &["g++", "c++"]),
("clang", &["clang"]),
("clang++", &["clang++"]),
("ld", &["ld", "ld.lld"]),
("ar", &["ar", "llvm-ar"]),
("as", &["as", "gas"]),
("make", &["make", "gmake"]),
("cmake", &["cmake"]),
("pkg-config", &["pkg-config"]),
] {
let found = candidates.iter().find_map(|c| which_in_path(c));
available_tools.push((name.to_string(), found));
}
let mut warnings = Vec::new();
if toolchains.is_empty() {
warnings.push("No X86 toolchains detected.".to_string());
}
if !env_vars.contains_key("CC") {
warnings.push("CC environment variable is not set.".to_string());
}
let mut recommendations = Vec::new();
if manager.has_clang_family() && !manager.has_gcc_family() {
recommendations.push(
"Clang detected but no GCC found. Consider installing GCC for \
system headers and runtime libraries."
.to_string(),
);
}
if available_tools.iter().all(|(_, p)| p.is_none()) {
recommendations.push(
"No compilation tools found in PATH. Install build-essential \
or Xcode Command Line Tools."
.to_string(),
);
}
Self {
toolchains,
host_triple,
host_cpu_count: num_cpus::get() as u32,
host_memory_mb: 0, env_vars,
available_tools,
warnings,
recommendations,
}
}
pub fn print(&self) {
println!("╔════════════════════════════════════════════════╗");
println!("║ X86 Toolchain Diagnostics Report ║");
println!("╚════════════════════════════════════════════════╝");
println!();
println!("Host triple: {}", self.host_triple);
println!("CPU cores: {}", self.host_cpu_count);
println!("Memory: {} MB", self.host_memory_mb);
println!();
println!("Detected toolchains ({}):", self.toolchains.len());
for tc in &self.toolchains {
println!(" - {}", tc);
}
println!();
println!("Available tools:");
for (name, path) in &self.available_tools {
let status = if path.is_some() { "✓" } else { "✗" };
println!(" {} {}", status, name);
}
println!();
if !self.warnings.is_empty() {
println!("Warnings:");
for w in &self.warnings {
println!(" ⚠ {}", w);
}
println!();
}
if !self.recommendations.is_empty() {
println!("Recommendations:");
for r in &self.recommendations {
println!(" → {}", r);
}
println!();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_toolchain_new() {
let tc = X86Toolchain::new(X86ToolchainKind::Gcc);
assert_eq!(tc.kind, X86ToolchainKind::Gcc);
assert_eq!(tc.target_triple, "x86_64-unknown-linux-gnu");
assert!(!tc.is_cross);
}
#[test]
fn test_toolchain_kind_display() {
assert_eq!(X86ToolchainKind::Gcc.as_str(), "GCC");
assert_eq!(X86ToolchainKind::Clang.as_str(), "LLVM Clang");
assert_eq!(X86ToolchainKind::Msvc.as_str(), "MSVC");
assert_eq!(X86ToolchainKind::IccOneApi.as_str(), "Intel oneAPI DPC++");
assert_eq!(X86ToolchainKind::Custom("test".into()).as_str(), "test");
}
#[test]
fn test_toolchain_is_gcc_family() {
assert!(X86ToolchainKind::Gcc.is_gcc_family());
assert!(X86ToolchainKind::MinGW.is_gcc_family());
assert!(!X86ToolchainKind::Clang.is_gcc_family());
assert!(!X86ToolchainKind::Msvc.is_gcc_family());
}
#[test]
fn test_toolchain_is_clang_family() {
assert!(X86ToolchainKind::Clang.is_clang_family());
assert!(X86ToolchainKind::LlvmFull.is_clang_family());
assert!(!X86ToolchainKind::Gcc.is_clang_family());
}
#[test]
fn test_toolchain_default_compiler_flags() {
let mut tc = X86Toolchain::new(X86ToolchainKind::Gcc);
tc.word_size = X86WordSize::Bits64;
let flags = tc.default_compiler_flags();
assert!(flags.contains(&"-m64".to_string()));
}
#[test]
fn test_toolchain_default_compiler_flags_cross() {
let mut tc = X86Toolchain::new(X86ToolchainKind::Gcc);
tc.is_cross = true;
tc.target_triple = "x86_64-linux-musl".to_string();
let flags = tc.default_compiler_flags();
assert!(flags.iter().any(|f| f.contains("--target=")));
}
#[test]
fn test_toolchain_default_linker_flags() {
let tc = X86Toolchain::new(X86ToolchainKind::Gcc);
let flags = tc.default_linker_flags();
assert!(!flags.is_empty());
}
#[test]
fn test_toolchain_default_linker_flags_msvc() {
let tc = X86Toolchain::new(X86ToolchainKind::Msvc);
let flags = tc.default_linker_flags();
assert!(!flags.is_empty());
}
#[test]
fn test_toolchain_supports_shared_libraries() {
let mut tc = X86Toolchain::new(X86ToolchainKind::Gcc);
assert!(!tc.supports_shared_libraries());
tc.crt_begin_s = Some(PathBuf::from("/tmp/crtbeginS.o"));
tc.crt_end_s = Some(PathBuf::from("/tmp/crtendS.o"));
tc.libgcc_s_path = Some(PathBuf::from("/tmp/libgcc_s.so"));
assert!(tc.supports_shared_libraries());
}
#[test]
fn test_toolchain_supports_static_linking() {
let mut tc = X86Toolchain::new(X86ToolchainKind::Gcc);
assert!(!tc.supports_static_linking());
tc.libgcc_path = Some(PathBuf::from("/tmp/libgcc.a"));
assert!(tc.supports_static_linking());
}
#[test]
fn test_toolchain_is_usable() {
let tc = X86Toolchain::new(X86ToolchainKind::Gcc);
let usable = tc.is_usable();
assert!(usable || !usable); }
#[test]
fn test_toolchain_supports_c_standard() {
let tc = X86Toolchain::new(X86ToolchainKind::Gcc);
assert!(tc.supports_c_standard("c99"));
assert!(tc.supports_c_standard("c11"));
assert!(tc.supports_c_standard("c17"));
assert!(!tc.supports_c_standard("c99x"));
}
#[test]
fn test_toolchain_supports_cxx_standard() {
let tc = X86Toolchain::new(X86ToolchainKind::Gcc);
assert!(tc.supports_cxx_standard("c++17"));
assert!(tc.supports_cxx_standard("c++20"));
assert!(!tc.supports_cxx_standard("c++99x"));
}
#[test]
fn test_toolchain_display() {
let mut tc = X86Toolchain::new(X86ToolchainKind::Gcc);
tc.description = "GCC 13.2.0".to_string();
let display = format!("{}", tc);
assert!(display.contains("GCC 13.2.0"));
assert!(display.contains("GCC"));
}
#[test]
fn test_word_size_pointer_width() {
assert_eq!(X86WordSize::Bits16.pointer_width(), 2);
assert_eq!(X86WordSize::Bits32.pointer_width(), 4);
assert_eq!(X86WordSize::Bits64.pointer_width(), 8);
assert_eq!(X86WordSize::X32.pointer_width(), 4);
}
#[test]
fn test_word_size_m_flag() {
assert_eq!(X86WordSize::Bits16.m_flag(), "-m16");
assert_eq!(X86WordSize::Bits32.m_flag(), "-m32");
assert_eq!(X86WordSize::Bits64.m_flag(), "-m64");
assert_eq!(X86WordSize::X32.m_flag(), "-mx32");
}
#[test]
fn test_multilib_standard_variants() {
let variants = X86MultilibVariant::standard_x86_64_variants();
assert_eq!(variants.len(), 3);
assert!(variants.iter().any(|v| v.dir_name == "."));
assert!(variants.iter().any(|v| v.dir_name == "32"));
assert!(variants.iter().any(|v| v.dir_name == "x32"));
}
#[test]
fn test_detector_new() {
let detector = X86ToolchainDetector::new();
assert!(!detector.host_triple.is_empty());
assert!(!detector.deep_scan);
assert!(detector.trust_env);
}
#[test]
fn test_detect_host_triple() {
let triple = detect_host_triple();
assert!(!triple.is_empty());
assert!(triple.contains("x86_64") || triple.contains("i686"));
}
#[test]
fn test_detector_detect_all() {
let mut detector = X86ToolchainDetector::new();
let toolchains = detector.detect_all();
assert!(toolchains.len() >= 0);
}
#[test]
fn test_detector_gcc_search_roots() {
let detector = X86ToolchainDetector::new();
let roots = detector.gcc_search_roots();
assert!(!roots.is_empty());
}
#[test]
fn test_detector_clang_search_paths() {
let detector = X86ToolchainDetector::new();
let paths = detector.clang_search_paths();
assert!(!paths.is_empty());
}
#[test]
fn test_driver_new() {
let tc = X86Toolchain::default();
let driver = X86ToolDriver::new(tc);
assert!(!driver.verbose);
assert!(!driver.use_ccache);
}
#[test]
fn test_driver_build_cc_command() {
let tc = X86Toolchain::default();
let driver = X86ToolDriver::new(tc);
let cmd = driver.build_cc_command("test.c", "test.o");
assert!(format!("{:?}", cmd).contains("test.c"));
}
#[test]
fn test_driver_build_cxx_command() {
let tc = X86Toolchain::default();
let driver = X86ToolDriver::new(tc);
let cmd = driver.build_cxx_command("test.cpp", "test.o");
assert!(format!("{:?}", cmd).contains("test.cpp"));
}
#[test]
fn test_driver_build_compile_link_command() {
let tc = X86Toolchain::default();
let driver = X86ToolDriver::new(tc);
let cmd = driver
.build_compile_link_command(&["main.c".to_string(), "util.c".to_string()], "program");
let repr = format!("{:?}", cmd);
assert!(repr.contains("main.c"));
assert!(repr.contains("program"));
}
#[test]
fn test_driver_build_asm_command() {
let tc = X86Toolchain::default();
let driver = X86ToolDriver::new(tc);
let cmd = driver.build_asm_command("test.s", "test.o");
assert!(format!("{:?}", cmd).contains("test.s"));
}
#[test]
fn test_driver_build_ld_command() {
let tc = X86Toolchain::default();
let driver = X86ToolDriver::new(tc);
let cmd = driver.build_ld_command(&["main.o".to_string(), "util.o".to_string()], "program");
let repr = format!("{:?}", cmd);
assert!(repr.contains("main.o"));
}
#[test]
fn test_driver_build_ar_command() {
let tc = X86Toolchain::default();
let driver = X86ToolDriver::new(tc);
let cmd = driver.build_ar_command(&["main.o".to_string()], "libtest.a");
let repr = format!("{:?}", cmd);
assert!(repr.contains("libtest.a"));
}
#[test]
fn test_config_new() {
let cfg = X86ToolConfig::new(X86ToolKind::Cc);
assert_eq!(cfg.tool_kind, X86ToolKind::Cc);
assert_eq!(cfg.target_triple, "x86_64-unknown-linux-gnu");
}
#[test]
fn test_config_for_c_compiler() {
let cfg = X86ToolConfig::for_c_compiler();
assert_eq!(cfg.tool_kind, X86ToolKind::Cc);
assert!(cfg.default_flags.contains(&"-c".to_string()));
}
#[test]
fn test_config_for_cxx_compiler() {
let cfg = X86ToolConfig::for_cxx_compiler();
assert_eq!(cfg.tool_kind, X86ToolKind::Cxx);
}
#[test]
fn test_config_for_assembler() {
let cfg = X86ToolConfig::for_assembler();
assert_eq!(cfg.tool_kind, X86ToolKind::Assembler);
}
#[test]
fn test_config_for_linker() {
let cfg = X86ToolConfig::for_linker();
assert_eq!(cfg.tool_kind, X86ToolKind::Linker);
assert!(cfg.default_flags.contains(&"--build-id".to_string()));
}
#[test]
fn test_config_with_target_flags_linux() {
let cfg = X86ToolConfig::for_c_compiler().with_target_flags("x86_64-unknown-linux-gnu");
assert!(cfg.target_flags.contains(&"-D__linux__".to_string()));
}
#[test]
fn test_config_with_target_flags_windows() {
let cfg = X86ToolConfig::for_c_compiler().with_target_flags("x86_64-pc-windows-msvc");
assert!(cfg.target_flags.contains(&"-D_WIN32".to_string()));
}
#[test]
fn test_config_with_target_flags_darwin() {
let cfg = X86ToolConfig::for_c_compiler().with_target_flags("x86_64-apple-darwin");
assert!(cfg.target_flags.contains(&"-D__APPLE__".to_string()));
}
#[test]
fn test_config_with_cpu_native() {
let cfg = X86ToolConfig::for_c_compiler().with_cpu("native");
assert!(cfg.cpu_flags.contains(&"-march=native".to_string()));
}
#[test]
fn test_config_cpu_march_flags() {
assert_eq!(X86ToolConfig::cpu_march_flag("native"), "-march=native");
assert_eq!(X86ToolConfig::cpu_march_flag("x86-64"), "-march=x86-64");
assert_eq!(X86ToolConfig::cpu_march_flag("skylake"), "-march=skylake");
}
#[test]
fn test_config_with_optimization() {
let cfg = X86ToolConfig::for_c_compiler().with_optimization("3");
assert!(cfg.optimization_flags.contains(&"-O3".to_string()));
let cfg2 = X86ToolConfig::for_c_compiler().with_optimization("s");
assert!(cfg2.optimization_flags.contains(&"-Os".to_string()));
}
#[test]
fn test_config_with_lto() {
let mut cfg = X86ToolConfig::for_c_compiler();
cfg = cfg.with_lto("thin");
assert!(cfg.optimization_flags.contains(&"-flto=thin".to_string()));
}
#[test]
fn test_config_with_debug() {
let cfg = X86ToolConfig::for_c_compiler().with_debug("3");
assert!(cfg.debug_flags.contains(&"-g3".to_string()));
let cfg2 = X86ToolConfig::for_c_compiler().with_debug("none");
assert!(cfg2.debug_flags.is_empty());
}
#[test]
fn test_config_with_sanitizer() {
let cfg = X86ToolConfig::for_c_compiler().with_sanitizer("address");
assert!(cfg
.sanitizer_flags
.contains(&"-fsanitize=address".to_string()));
}
#[test]
fn test_config_all_flags() {
let cfg = X86ToolConfig::for_c_compiler()
.with_target_flags("x86_64-unknown-linux-gnu")
.with_cpu("native");
let all = cfg.all_flags();
assert!(!all.is_empty());
}
#[test]
fn test_config_gcc_defaults() {
let flags = X86ToolConfig::gcc_defaults(X86WordSize::Bits64);
assert!(flags.contains(&"-m64".to_string()));
assert!(flags.contains(&"-fno-common".to_string()));
}
#[test]
fn test_config_clang_defaults() {
let flags = X86ToolConfig::clang_defaults(X86WordSize::Bits64);
assert!(flags.contains(&"-fcolor-diagnostics".to_string()));
}
#[test]
fn test_config_msvc_defaults() {
let flags = X86ToolConfig::msvc_defaults(X86WordSize::Bits64);
assert!(flags.contains(&"/c".to_string()));
assert!(flags.contains(&"/O2".to_string()));
}
#[test]
fn test_config_icc_defaults() {
let flags = X86ToolConfig::icc_defaults(X86WordSize::Bits64);
assert!(flags.contains(&"-c".to_string()));
assert!(flags.contains(&"-fPIC".to_string()));
}
#[test]
fn test_workaround_new() {
let w =
X86ToolchainWorkaround::new("Test", X86ToolchainKind::Gcc, vec!["-flag".to_string()]);
assert_eq!(w.description, "Test");
assert!(w.enabled);
}
#[test]
fn test_workaround_with_version_range() {
let w =
X86ToolchainWorkaround::new("Test", X86ToolchainKind::Gcc, vec!["-flag".to_string()])
.with_version_range("0.0", "5.0");
assert_eq!(
w.version_range,
Some(("0.0".to_string(), "5.0".to_string()))
);
}
#[test]
fn test_workaround_disabled() {
let w =
X86ToolchainWorkaround::new("Test", X86ToolchainKind::Gcc, vec!["-flag".to_string()])
.disabled();
assert!(!w.enabled);
}
#[test]
fn test_known_gcc_workarounds() {
let workarounds = X86ToolchainWorkaround::known_gcc_workarounds();
assert!(!workarounds.is_empty());
}
#[test]
fn test_known_clang_workarounds() {
let workarounds = X86ToolchainWorkaround::known_clang_workarounds();
assert!(!workarounds.is_empty());
}
#[test]
fn test_known_msvc_workarounds() {
let workarounds = X86ToolchainWorkaround::known_msvc_workarounds();
assert!(!workarounds.is_empty());
}
#[test]
fn test_manager_new() {
let manager = X86ToolchainManager::new();
assert_eq!(manager.count_installed(), 0);
}
#[test]
fn test_manager_add_toolchain() {
let mut manager = X86ToolchainManager::new();
let mut tc = X86Toolchain::new(X86ToolchainKind::Gcc);
tc.is_installed = true;
manager.add_toolchain(tc);
assert_eq!(manager.count_installed(), 1);
}
#[test]
fn test_manager_register_custom() {
let mut manager = X86ToolchainManager::new();
let tc = X86Toolchain::new(X86ToolchainKind::Custom("test".into()));
manager.register_custom(tc);
assert!(
manager
.list_by_kind(X86ToolchainKind::Custom("test".into()))
.len()
== 1
);
}
#[test]
fn test_manager_has_gcc_family() {
let mut manager = X86ToolchainManager::new();
let mut tc = X86Toolchain::new(X86ToolchainKind::Gcc);
tc.is_installed = true;
manager.add_toolchain(tc);
assert!(manager.has_gcc_family());
assert!(!manager.has_clang_family());
}
#[test]
fn test_manager_has_clang_family() {
let mut manager = X86ToolchainManager::new();
let mut tc = X86Toolchain::new(X86ToolchainKind::Clang);
tc.is_installed = true;
manager.add_toolchain(tc);
assert!(manager.has_clang_family());
}
#[test]
fn test_manager_select_by_kind() {
let mut manager = X86ToolchainManager::new();
let mut tc = X86Toolchain::new(X86ToolchainKind::Gcc);
tc.is_installed = true;
tc.description = "Test GCC".to_string();
manager.add_toolchain(tc);
let selected = manager.select_by_kind(X86ToolchainKind::Gcc);
assert!(selected.is_some());
assert_eq!(selected.unwrap().description, "Test GCC");
}
#[test]
fn test_manager_register_from_paths() {
let mut manager = X86ToolchainManager::new();
manager.register_from_paths(
X86ToolchainKind::Gcc,
"/usr/bin/gcc",
Some("/usr/bin/g++"),
Some("/usr/bin/ld"),
Some("/usr/bin/ar"),
"x86_64-linux-gnu",
);
assert_eq!(manager.count_installed(), 1);
}
#[test]
fn test_compile_command_new() {
let cmd = X86CompileCommand::new(
"/tmp",
"test.c",
vec!["gcc".to_string(), "-c".to_string(), "test.c".to_string()],
);
assert_eq!(cmd.directory, "/tmp");
assert_eq!(cmd.file, "test.c");
assert_eq!(cmd.arguments.len(), 3);
}
#[test]
fn test_compile_command_derive_output() {
let mut cmd = X86CompileCommand::new("/tmp", "test.c", vec!["gcc".to_string()]);
cmd.derive_output();
assert_eq!(cmd.output, Some("/tmp/test.o".to_string()));
}
#[test]
fn test_database_new() {
let db = X86CompilationDatabase::new();
assert!(db.is_empty());
assert_eq!(db.len(), 0);
}
#[test]
fn test_database_add_command() {
let mut db = X86CompilationDatabase::new();
db.add_command(X86CompileCommand::new(
"/tmp",
"test.c",
vec!["gcc".to_string()],
));
assert_eq!(db.len(), 1);
assert!(!db.is_empty());
}
#[test]
fn test_database_find_by_file() {
let mut db = X86CompilationDatabase::new();
db.add_command(X86CompileCommand::new(
"/tmp",
"test.c",
vec!["gcc".to_string()],
));
db.add_command(X86CompileCommand::new(
"/tmp",
"other.c",
vec!["gcc".to_string()],
));
let found = db.find_by_file("test.c");
assert_eq!(found.len(), 1);
}
#[test]
fn test_database_all_files() {
let mut db = X86CompilationDatabase::new();
db.add_command(X86CompileCommand::new("/tmp", "a.c", vec![]));
db.add_command(X86CompileCommand::new("/tmp", "b.c", vec![]));
db.add_command(X86CompileCommand::new("/tmp", "a.c", vec![]));
let files = db.all_files();
assert_eq!(files.len(), 2);
}
#[test]
fn test_database_generate_json() {
let mut db = X86CompilationDatabase::new();
db.add_command(X86CompileCommand::new(
"/home/user",
"main.c",
vec![
"gcc".to_string(),
"-c".to_string(),
"main.c".to_string(),
"-o".to_string(),
"main.o".to_string(),
],
));
let json = db.generate_json();
assert!(json.contains("\"directory\""));
assert!(json.contains("\"file\""));
assert!(json.contains("\"arguments\""));
}
#[test]
fn test_database_parse_simple() {
let json = r#"[
{
"directory": "/home/user",
"file": "main.c",
"arguments": ["gcc", "-c", "main.c", "-o", "main.o"]
}
]"#;
let db = X86CompilationDatabase::parse(json, None).unwrap();
assert_eq!(db.len(), 1);
assert_eq!(db.commands[0].file, "main.c");
}
#[test]
fn test_database_parse_empty() {
let json = "[]";
let db = X86CompilationDatabase::parse(json, None).unwrap();
assert!(db.is_empty());
}
#[test]
fn test_database_parse_invalid() {
let result = X86CompilationDatabase::parse("not json", None);
assert!(result.is_err());
}
#[test]
fn test_database_display() {
let mut db = X86CompilationDatabase::new();
db.add_command(X86CompileCommand::new("/tmp", "test.c", vec![]));
let display = format!("{}", db);
assert!(display.contains("1 commands"));
}
#[test]
fn test_sysroot_manager_new() {
let mgr = X86SysrootManager::new("x86_64-unknown-linux-gnu");
assert!(mgr.auto_detected);
assert_eq!(mgr.kind, SysrootKind::LinuxNative);
}
#[test]
fn test_sysroot_manager_set_custom() {
let mut mgr = X86SysrootManager::new("x86_64-unknown-linux-gnu");
mgr.set_custom_sysroot("/tmp/mysysroot");
assert_eq!(mgr.sysroot, Some(PathBuf::from("/tmp/mysysroot")));
assert!(!mgr.auto_detected);
assert_eq!(mgr.kind, SysrootKind::Custom);
}
#[test]
fn test_sysroot_manager_include_path() {
let mut mgr = X86SysrootManager::new("x86_64-unknown-linux-gnu");
mgr.set_custom_sysroot("/tmp/mysysroot");
assert_eq!(
mgr.include_path(),
Some(PathBuf::from("/tmp/mysysroot/usr/include"))
);
}
#[test]
fn test_sysroot_manager_lib_path() {
let mut mgr = X86SysrootManager::new("x86_64-unknown-linux-gnu");
mgr.set_custom_sysroot("/tmp/mysysroot");
assert_eq!(
mgr.lib_path(),
Some(PathBuf::from("/tmp/mysysroot/usr/lib"))
);
}
#[test]
fn test_sysroot_manager_flags() {
let mut mgr = X86SysrootManager::new("x86_64-unknown-linux-gnu");
mgr.set_custom_sysroot("/tmp/mysysroot");
let flags = mgr.sysroot_flags();
assert!(flags.iter().any(|f| f.contains("--sysroot=")));
}
#[test]
fn test_sysroot_kind_display() {
assert_eq!(SysrootKind::LinuxNative.as_str(), "linux-native");
assert_eq!(SysrootKind::AndroidNdk.as_str(), "android-ndk");
assert_eq!(SysrootKind::Custom.as_str(), "custom");
}
#[test]
fn test_library_search_new() {
let search = X86LibrarySearch::new("x86_64-unknown-linux-gnu");
assert!(!search.standard_paths.is_empty());
assert!(search.standard_paths.contains(&PathBuf::from("/usr/lib")));
}
#[test]
fn test_library_search_parse_library_path() {
let mut search = X86LibrarySearch::new("x86_64-unknown-linux-gnu");
search.parse_library_path();
}
#[test]
fn test_library_search_parse_ldflags() {
let mut search = X86LibrarySearch::new("x86_64-unknown-linux-gnu");
search.parse_ldflags();
}
#[test]
fn test_library_search_linker_search_flags() {
let search = X86LibrarySearch::new("x86_64-unknown-linux-gnu");
let flags = search.linker_search_flags();
assert!(flags.iter().any(|f| f.contains("-L")));
}
#[test]
fn test_library_search_rpath_flags() {
let mut search = X86LibrarySearch::new("x86_64-unknown-linux-gnu");
search.use_rpath = true;
search.add_rpath("/usr/local/lib");
search.add_rpath("$ORIGIN");
let flags = search.rpath_flags();
assert!(flags.iter().any(|f| f.contains("rpath")));
}
#[test]
fn test_library_search_setup_origin_rpath() {
let mut search = X86LibrarySearch::new("x86_64-unknown-linux-gnu");
search.setup_origin_rpath("lib");
assert!(search.use_rpath);
assert!(search.rpath_entries.contains(&"$ORIGIN".to_string()));
}
#[test]
fn test_library_search_find_library_not_found() {
let search = X86LibrarySearch::new("x86_64-unknown-linux-gnu");
assert!(search.find_library("nonexistent_lib_xyz_123").is_none());
}
#[test]
fn test_wrapper_new() {
let wrapper = X86CompilerWrapper::new();
assert_eq!(wrapper.active_wrapper, CompilerWrapperKind::None);
}
#[test]
fn test_wrapper_detect() {
let mut wrapper = X86CompilerWrapper::new();
wrapper.detect();
assert!(
wrapper.active_wrapper == CompilerWrapperKind::Ccache
|| wrapper.active_wrapper == CompilerWrapperKind::Sccache
|| wrapper.active_wrapper == CompilerWrapperKind::None
);
}
#[test]
fn test_wrapper_wrap_command_none() {
let wrapper = X86CompilerWrapper::new();
let wrapped = wrapper.wrap_command("gcc", &["-c".to_string(), "test.c".to_string()]);
assert_eq!(wrapped[0], "gcc");
}
#[test]
fn test_wrapper_configure_ccache() {
let mut wrapper = X86CompilerWrapper::new();
wrapper.configure_ccache("/tmp/ccache", "5G");
assert_eq!(wrapper.active_wrapper, CompilerWrapperKind::Ccache);
assert_eq!(wrapper.max_cache_size, Some("5G".to_string()));
}
#[test]
fn test_wrapper_configure_sccache() {
let mut wrapper = X86CompilerWrapper::new();
wrapper.configure_sccache("/tmp/sccache", "s3://bucket");
assert_eq!(wrapper.active_wrapper, CompilerWrapperKind::Sccache);
}
#[test]
fn test_wrapper_configure_distcc() {
let mut wrapper = X86CompilerWrapper::new();
wrapper.configure_distcc(vec!["host1".to_string(), "host2".to_string()]);
assert_eq!(wrapper.active_wrapper, CompilerWrapperKind::Distcc);
assert_eq!(wrapper.distcc_hosts.len(), 2);
}
#[test]
fn test_wrapper_configure_icecream() {
let mut wrapper = X86CompilerWrapper::new();
wrapper.configure_icecream("scheduler.local");
assert_eq!(wrapper.active_wrapper, CompilerWrapperKind::IceCream);
}
#[test]
fn test_wrapper_environment() {
let mut wrapper = X86CompilerWrapper::new();
wrapper.configure_ccache("/tmp/ccache", "10G");
let env = wrapper.wrapper_environment();
assert!(env.contains_key("CCACHE_DIR"));
assert_eq!(env.get("CCACHE_DIR").unwrap(), "/tmp/ccache");
}
#[test]
fn test_compiler_wrapper_kind_display() {
assert_eq!(CompilerWrapperKind::None.as_str(), "none");
assert_eq!(CompilerWrapperKind::Ccache.as_str(), "ccache");
assert_eq!(CompilerWrapperKind::Distcc.as_str(), "distcc");
assert_eq!(CompilerWrapperKind::IceCream.as_str(), "icecream");
assert_eq!(CompilerWrapperKind::Sccache.as_str(), "sccache");
}
#[test]
fn test_build_system_integration_new() {
let tc = X86Toolchain::default();
let bsi = X86BuildSystemIntegration::new(tc);
assert_eq!(bsi.build_type, "Release");
assert!(bsi.use_lld);
}
#[test]
fn test_generate_cmake_toolchain() {
let tc = X86Toolchain::default();
let bsi = X86BuildSystemIntegration::new(tc);
let content = bsi.generate_cmake_toolchain();
assert!(content.contains("CMAKE_SYSTEM_NAME"));
assert!(content.contains("CMAKE_C_COMPILER"));
assert!(content.contains("CMAKE_CXX_COMPILER"));
}
#[test]
fn test_generate_make_rules() {
let tc = X86Toolchain::default();
let bsi = X86BuildSystemIntegration::new(tc);
let content =
bsi.generate_make_rules(&["main.c".to_string(), "util.c".to_string()], "myprogram");
assert!(content.contains("CC :="));
assert!(content.contains("myprogram"));
assert!(content.contains("main.o"));
assert!(content.contains("util.o"));
}
#[test]
fn test_generate_bazel_toolchain() {
let tc = X86Toolchain::default();
let bsi = X86BuildSystemIntegration::new(tc);
let content = bsi.generate_bazel_toolchain();
assert!(content.contains("cc_toolchain"));
assert!(content.contains("tool_paths"));
}
#[test]
fn test_shell_split_simple() {
let args = shell_split("gcc -c test.c -o test.o");
assert_eq!(args.len(), 5);
assert_eq!(args[0], "gcc");
assert_eq!(args[4], "test.o");
}
#[test]
fn test_shell_split_quotes() {
let args = shell_split(r#"gcc -DNAME="hello world" test.c"#);
assert!(args.len() >= 3);
}
#[test]
fn test_shell_split_empty() {
let args = shell_split("");
assert!(args.is_empty());
}
#[test]
fn test_which_in_path() {
let result = which_in_path("ls");
assert!(result.is_some() || !cfg!(target_os = "linux"));
}
#[test]
fn test_which_in_path_missing() {
let result = which_in_path("nonexistent_tool_xyz_12345");
assert!(result.is_none());
}
#[test]
fn test_shellexpand_home() {
let result = shellexpand_env("~/test");
assert!(!result.starts_with("~"));
}
#[test]
fn test_toolchain_priority_score_native() {
let mut tc = X86Toolchain::new(X86ToolchainKind::Gcc);
tc.is_installed = true;
tc.is_cross = false;
let score = toolchain_priority_score(&tc);
assert!(score > 1000);
}
#[test]
fn test_toolchain_priority_score_cross() {
let mut tc = X86Toolchain::new(X86ToolchainKind::Gcc);
tc.is_installed = true;
tc.is_cross = true;
let score = toolchain_priority_score(&tc);
assert!(score < 1000);
}
#[test]
fn test_find_file_direct() {
let result = find_file(&PathBuf::from("/tmp"), "nonexistent.o");
assert!(result.is_none());
}
#[test]
fn test_abi_is_windows() {
assert!(!X86Abi::SysV.is_windows());
assert!(X86Abi::MsAbi.is_windows());
assert!(!X86Abi::Intel.is_windows());
}
#[test]
fn test_abi_display() {
assert_eq!(X86Abi::SysV.as_str(), "sysv");
assert_eq!(X86Abi::MsAbi.as_str(), "ms");
}
#[test]
fn test_tool_kind_display() {
assert_eq!(X86ToolKind::Cc.display_name(), "C Compiler");
assert_eq!(X86ToolKind::Cxx.display_name(), "C++ Compiler");
assert_eq!(X86ToolKind::Assembler.display_name(), "Assembler");
assert_eq!(X86ToolKind::Linker.display_name(), "Linker");
assert_eq!(X86ToolKind::Archiver.display_name(), "Archiver");
assert_eq!(X86ToolKind::Debugger.display_name(), "Debugger");
}
#[test]
fn test_tool_output_success() {
let output = X86ToolOutput {
exit_code: 0,
stdout: String::new(),
stderr: String::new(),
success: true,
elapsed: Duration::from_millis(100),
};
assert!(output.is_success());
assert!(!output.has_errors());
assert!(!output.has_warnings());
}
#[test]
fn test_tool_output_failure() {
let output = X86ToolOutput {
exit_code: 1,
stdout: String::new(),
stderr: "error: something went wrong".to_string(),
success: false,
elapsed: Duration::from_millis(100),
};
assert!(!output.is_success());
assert!(output.has_errors());
}
#[test]
fn test_tool_output_warnings() {
let output = X86ToolOutput {
exit_code: 0,
stdout: String::new(),
stderr: "warning: unused variable".to_string(),
success: true,
elapsed: Duration::from_millis(100),
};
assert!(output.has_warnings());
}
#[test]
fn test_full_toolchain_config_workflow() {
let mut tc = X86Toolchain::new(X86ToolchainKind::Gcc);
tc.description = "Test GCC".to_string();
tc.is_installed = true;
let config = X86ToolConfig::for_c_compiler()
.with_cpu("x86-64")
.with_optimization("2")
.with_target_flags("x86_64-unknown-linux-gnu");
let flags = config.all_flags();
assert!(!flags.is_empty());
assert!(flags.contains(&"-c".to_string()));
assert!(flags.contains(&"-pipe".to_string()));
}
#[test]
fn test_full_manager_detect_and_select_workflow() {
let mut manager = X86ToolchainManager::new();
let mut tc = X86Toolchain::new(X86ToolchainKind::Gcc);
tc.description = "Mock GCC".to_string();
tc.is_installed = true;
tc.target_triple = "x86_64-unknown-linux-gnu".to_string();
manager.add_toolchain(tc);
let selected = manager.select_for_target("x86_64-unknown-linux-gnu");
assert!(selected.is_some());
assert_eq!(selected.unwrap().kind, X86ToolchainKind::Gcc);
}
#[test]
fn test_full_database_workflow() {
let mut db = X86CompilationDatabase::new();
db.add_command(X86CompileCommand::new(
"/project",
"src/main.c",
vec![
"gcc".to_string(),
"-c".to_string(),
"src/main.c".to_string(),
"-o".to_string(),
"build/main.o".to_string(),
],
));
db.add_command(X86CompileCommand::new(
"/project",
"src/util.c",
vec![
"gcc".to_string(),
"-c".to_string(),
"src/util.c".to_string(),
"-o".to_string(),
"build/util.o".to_string(),
],
));
let json = db.generate_json();
assert!(json.contains("src/main.c"));
assert!(json.contains("src/util.c"));
let db2 = X86CompilationDatabase::parse(&json, None).unwrap();
assert_eq!(db2.len(), 2);
assert_eq!(db2.all_files().len(), 2);
let found = db2.find_by_file("src/main.c");
assert_eq!(found.len(), 1);
}
#[test]
fn test_full_sysroot_workflow() {
let mut sysroot_mgr = X86SysrootManager::new("x86_64-unknown-linux-gnu");
sysroot_mgr.set_custom_sysroot("/opt/cross/sysroot");
let mut search = X86LibrarySearch::new("x86_64-unknown-linux-gnu");
search.sysroot = sysroot_mgr.sysroot.clone();
let linker_flags = search.linker_search_flags();
assert!(!linker_flags.is_empty());
}
#[test]
fn test_full_wrapper_integration() {
let mut wrapper = X86CompilerWrapper::new();
wrapper.configure_ccache("/var/cache/ccache", "50G");
let wrapped = wrapper.wrap_command(
"gcc",
&[
"-c".to_string(),
"test.c".to_string(),
"-o".to_string(),
"test.o".to_string(),
],
);
assert_eq!(wrapped[0], "ccache");
let env = wrapper.wrapper_environment();
assert!(env.contains_key("CCACHE_DIR"));
}
#[test]
fn test_full_build_system_integration() {
let mut tc = X86Toolchain::new(X86ToolchainKind::Gcc);
tc.description = "GCC 13.2.0".to_string();
tc.is_cross = true;
tc.target_triple = "x86_64-linux-gnu".to_string();
let bsi = X86BuildSystemIntegration::new(tc);
let cmake_content = bsi.generate_cmake_toolchain();
assert!(cmake_content.contains("CMAKE_SYSTEM_NAME"));
assert!(cmake_content.contains("CMAKE_C_COMPILER"));
let make_content = bsi.generate_make_rules(&["src/main.c".to_string()], "program");
assert!(make_content.contains("CC :="));
assert!(make_content.contains("program"));
let bazel_content = bsi.generate_bazel_toolchain();
assert!(bazel_content.contains("cc_toolchain_suite"));
assert!(bazel_content.contains("tool_paths"));
}
#[test]
fn test_driver_full_pipeline() {
let tc = X86Toolchain::default();
let driver = X86ToolDriver::new(tc);
let _ = driver.build_cc_command("test.c", "test.o");
let _ = driver.build_cxx_command("test.cpp", "test.o");
let _ = driver.build_compile_link_command(&["test.c".to_string()], "test");
let _ = driver.build_asm_command("test.s", "test.o");
let _ = driver.build_ld_command(&["test.o".to_string()], "test");
let _ = driver.build_gold_command(&["test.o".to_string()], "test");
let _ = driver.build_lld_command(&["test.o".to_string()], "test");
let _ = driver.build_ar_command(&["test.o".to_string()], "libtest.a");
let _ = driver.build_llvm_ar_command(&["test.o".to_string()], "libtest.a");
let _ = driver.build_objcopy_command("test.o", "test2.o");
let _ = driver.build_objdump_command("test.o");
let _ = driver.build_nm_command("test.o");
let _ = driver.build_strip_command("test.o");
let _ = driver.build_size_command("test.o");
let _ = driver.build_readelf_command("test.o");
let _ = driver.build_debugger_command("test");
}
#[test]
fn test_config_workaround_integration() {
let mut config = X86ToolConfig::for_c_compiler();
for wa in X86ToolchainWorkaround::known_gcc_workarounds() {
config.add_workaround(wa);
}
let flags = config.all_flags();
assert!(
flags.contains(&"-fno-ipa-sra".to_string())
|| flags.contains(&"-Wno-implicit-fallthrough".to_string())
|| flags.contains(&"-fno-lifetime-dse".to_string())
);
}
#[test]
fn test_manager_fallback_chain() {
let manager = X86ToolchainManager::new();
let fallback = manager.get_fallback(X86ToolchainKind::IccClassic);
let _ = fallback;
}
#[test]
fn test_large_multilib_workflow() {
let variants = X86MultilibVariant::standard_x86_64_variants();
let mut tc = X86Toolchain::new(X86ToolchainKind::Gcc);
for v in &variants {
tc.add_multilib_variant(v.clone());
}
assert_eq!(tc.multilib_variants.len(), 3);
}
#[test]
fn test_pgo_config_default() {
let pgo = X86PgoConfig::default();
assert!(!pgo.instrument);
assert!(pgo.ir_level);
assert!(!pgo.context_sensitive);
}
#[test]
fn test_pgo_instrumentation_flags() {
let mut pgo = X86PgoConfig::new();
pgo.instrument = true;
let flags = pgo.instrumentation_flags();
assert!(flags.contains(&"-fprofile-instr-generate".to_string()));
}
#[test]
fn test_pgo_instrumentation_flags_disabled() {
let pgo = X86PgoConfig::new();
let flags = pgo.instrumentation_flags();
assert!(flags.is_empty());
}
#[test]
fn test_pgo_use_flags() {
let mut pgo = X86PgoConfig::new();
pgo.profile_file = Some(PathBuf::from("/tmp/default.profdata"));
let flags = pgo.use_flags();
assert!(flags.iter().any(|f| f.contains("default.profdata")));
}
#[test]
fn test_pgo_detect_runtime_clang() {
let mut pgo = X86PgoConfig::new();
let tc = X86Toolchain::new(X86ToolchainKind::Clang);
pgo.detect_runtime(&tc);
assert_eq!(pgo.runtime, PgoRuntime::Llvm);
}
#[test]
fn test_pgo_detect_runtime_gcc() {
let mut pgo = X86PgoConfig::new();
let tc = X86Toolchain::new(X86ToolchainKind::Gcc);
pgo.detect_runtime(&tc);
assert_eq!(pgo.runtime, PgoRuntime::Gcc);
}
#[test]
fn test_coverage_config_default() {
let cov = X86CoverageConfig::default();
assert!(!cov.enabled);
assert_eq!(cov.format, CoverageFormat::SourceBased);
}
#[test]
fn test_coverage_compiler_flags_llvm() {
let mut cov = X86CoverageConfig::new();
cov.enabled = true;
cov.format = CoverageFormat::Llvm;
let flags = cov.compiler_flags();
assert!(flags.contains(&"-fprofile-instr-generate".to_string()));
assert!(flags.contains(&"-fcoverage-mapping".to_string()));
}
#[test]
fn test_coverage_compiler_flags_gcc() {
let mut cov = X86CoverageConfig::new();
cov.enabled = true;
cov.format = CoverageFormat::Gcc;
let flags = cov.compiler_flags();
assert!(flags.contains(&"--coverage".to_string()));
}
#[test]
fn test_coverage_disabled() {
let cov = X86CoverageConfig::new();
let flags = cov.compiler_flags();
assert!(flags.is_empty());
}
#[test]
fn test_coverage_detect_format_clang() {
let mut cov = X86CoverageConfig::new();
let tc = X86Toolchain::new(X86ToolchainKind::Clang);
cov.detect_format(&tc);
assert_eq!(cov.format, CoverageFormat::SourceBased);
}
#[test]
fn test_coverage_detect_format_gcc() {
let mut cov = X86CoverageConfig::new();
let tc = X86Toolchain::new(X86ToolchainKind::Gcc);
cov.detect_format(&tc);
assert_eq!(cov.format, CoverageFormat::Gcc);
}
#[test]
fn test_lto_config_default() {
let lto = X86LtoConfig::default();
assert_eq!(lto.mode, LtoMode::None);
assert!(lto.new_pass_manager);
}
#[test]
fn test_lto_full_flags() {
let mut lto = X86LtoConfig::new();
lto.mode = LtoMode::Full;
let flags = lto.compiler_flags();
assert!(flags.contains(&"-flto".to_string()));
}
#[test]
fn test_lto_thin_flags() {
let mut lto = X86LtoConfig::new();
lto.mode = LtoMode::Thin;
lto.thinlto_jobs = Some(8);
let flags = lto.compiler_flags();
assert!(flags.contains(&"-flto=thin".to_string()));
assert!(flags.iter().any(|f| f.contains("flto-jobs=8")));
}
#[test]
fn test_lto_none_flags() {
let lto = X86LtoConfig::new();
let flags = lto.compiler_flags();
assert!(flags.is_empty());
}
#[test]
fn test_lto_mode_flags() {
assert_eq!(LtoMode::None.flag(), None);
assert_eq!(LtoMode::Full.flag(), Some("-flto"));
assert_eq!(LtoMode::Thin.flag(), Some("-flto=thin"));
}
#[test]
fn test_lto_detect_support_no_lto() {
let mut lto = X86LtoConfig::new();
lto.mode = LtoMode::Full;
let mut tc = X86Toolchain::new(X86ToolchainKind::Gcc);
tc.lto_supported = false;
lto.detect_support(&tc);
assert_eq!(lto.mode, LtoMode::None);
}
#[test]
fn test_lto_detect_support_thin_fallback() {
let mut lto = X86LtoConfig::new();
lto.mode = LtoMode::Thin;
let mut tc = X86Toolchain::new(X86ToolchainKind::Gcc);
tc.lto_supported = true;
tc.thin_lto_supported = false;
lto.detect_support(&tc);
assert_eq!(lto.mode, LtoMode::Full);
}
#[test]
fn test_sanitizer_config_default() {
let san = X86SanitizerConfig::default();
assert!(san.enabled.is_empty());
assert!(san.shared_runtime);
assert!(!san.trap_on_error);
}
#[test]
fn test_sanitizer_enable_address() {
let mut san = X86SanitizerConfig::new();
san.enable("address");
assert!(san.enabled.contains(&"address".to_string()));
let flags = san.compiler_flags();
assert!(flags.iter().any(|f| f.contains("address")));
}
#[test]
fn test_sanitizer_enable_multiple() {
let mut san = X86SanitizerConfig::new();
san.enable("address");
san.enable("undefined");
assert_eq!(san.enabled.len(), 2);
}
#[test]
fn test_sanitizer_disable() {
let mut san = X86SanitizerConfig::new();
san.enable("address");
san.enable("undefined");
san.disable("address");
assert_eq!(san.enabled.len(), 1);
assert!(!san.enabled.contains(&"address".to_string()));
}
#[test]
fn test_sanitizer_no_flags_when_empty() {
let san = X86SanitizerConfig::new();
assert!(san.compiler_flags().is_empty());
assert!(san.linker_flags().is_empty());
}
#[test]
fn test_sanitizer_trap_mode() {
let mut san = X86SanitizerConfig::new();
san.enable("undefined");
san.trap_on_error = true;
let flags = san.compiler_flags();
assert!(flags.contains(&"-fsanitize-trap=all".to_string()));
}
#[test]
fn test_response_file_new() {
let rf = X86ResponseFile::new("/tmp/test.rsp");
assert_eq!(rf.max_command_line, X86ResponseFile::LINUX_MAX);
assert!(!rf.expand_env);
}
#[test]
fn test_response_file_for_platform_windows() {
let rf = X86ResponseFile::new("/tmp/test.rsp").for_platform(true);
assert_eq!(rf.max_command_line, X86ResponseFile::WINDOWS_MAX);
}
#[test]
fn test_response_file_for_platform_linux() {
let rf = X86ResponseFile::new("/tmp/test.rsp").for_platform(false);
assert_eq!(rf.max_command_line, X86ResponseFile::LINUX_MAX);
}
#[test]
fn test_response_file_needs_response() {
let rf = X86ResponseFile::new("/tmp/test.rsp");
let small_args: Vec<String> = (0..10).map(|i| format!("arg{}", i)).collect();
assert!(!rf.needs_response_file(&small_args));
let large_arg = "x".repeat(100000);
let large_args = vec![large_arg];
assert!(rf.needs_response_file(&large_args));
}
#[test]
fn test_diagnostics_report_generate() {
let manager = X86ToolchainManager::new();
let report = X86DiagnosticsReport::generate(&manager);
assert!(!report.host_triple.is_empty());
assert!(report.host_cpu_count > 0);
}
#[test]
fn test_diagnostics_report_no_toolchains_warning() {
let manager = X86ToolchainManager::new();
let report = X86DiagnosticsReport::generate(&manager);
assert!(report.warnings.iter().any(|w| w.contains("No X86")));
}
#[test]
fn test_priority_scoring_native_gcc() {
let mut tc = X86Toolchain::new(X86ToolchainKind::Gcc);
tc.is_installed = true;
tc.is_cross = false;
tc.compiler_version_major = 13;
let score = toolchain_priority_score(&tc);
assert!(score > 1800);
}
#[test]
fn test_priority_scoring_cross_baremetal() {
let mut tc = X86Toolchain::new(X86ToolchainKind::BareMetalGcc);
tc.is_installed = true;
tc.is_cross = true;
let score = toolchain_priority_score(&tc);
assert!(score < 1000);
}
#[test]
fn test_driver_with_extra_flags() {
let tc = X86Toolchain::default();
let mut driver = X86ToolDriver::new(tc);
driver.extra_flags = vec!["-DDEBUG".to_string(), "-O0".to_string()];
let cmd = driver.build_cc_command("test.c", "test.o");
let repr = format!("{:?}", cmd);
assert!(repr.contains("-DDEBUG"));
}
#[test]
fn test_driver_with_working_dir() {
let tc = X86Toolchain::default();
let mut driver = X86ToolDriver::new(tc);
driver.working_dir = Some(PathBuf::from("/tmp"));
let cmd = driver.build_cc_command("test.c", "test.o");
assert!(format!("{:?}", cmd).contains("/tmp"));
}
#[test]
fn test_manager_detect_all() {
let mut manager = X86ToolchainManager::new();
manager.detect_all();
let count = manager.count_installed();
assert!(count >= 0);
}
#[test]
fn test_manager_best_overall() {
let mut manager = X86ToolchainManager::new();
assert!(manager.best_overall().is_none());
let mut tc = X86Toolchain::new(X86ToolchainKind::Clang);
tc.is_installed = true;
manager.add_toolchain(tc);
assert!(manager.best_overall().is_some());
}
#[test]
fn test_config_with_all_options() {
let config = X86ToolConfig::for_c_compiler()
.with_cpu("haswell")
.with_optimization("3")
.with_target_flags("x86_64-unknown-linux-gnu")
.with_debug("2");
let all = config.all_flags();
assert!(all.contains(&"-march=haswell".to_string()));
assert!(all.contains(&"-O3".to_string()));
assert!(all.contains(&"-D__linux__".to_string()));
assert!(all.contains(&"-g".to_string()));
}
#[test]
fn test_sysroot_manager_detect_native() {
let mut mgr = X86SysrootManager::new("x86_64-unknown-linux-gnu");
mgr.detect();
assert_eq!(mgr.kind, SysrootKind::LinuxNative);
assert!(mgr.sysroot.is_some());
}
#[test]
fn test_library_search_multiarch() {
let mut search = X86LibrarySearch::new("x86_64-unknown-linux-gnu");
search.multiarch_tuple = Some("x86_64-linux-gnu".to_string());
assert!(search
.standard_paths
.iter()
.any(|p| p.to_string_lossy().contains("x86_64-linux-gnu")));
}
#[test]
fn test_wrapper_environment_distcc() {
let mut wrapper = X86CompilerWrapper::new();
wrapper.configure_distcc(vec!["host1".to_string(), "host2".to_string()]);
let env = wrapper.wrapper_environment();
assert!(env.contains_key("DISTCC_HOSTS"));
}
#[test]
fn test_shell_split_complex() {
let args = shell_split(r#"gcc -DFOO="bar baz" -c "file name.c""#);
assert!(!args.is_empty());
}
#[test]
fn test_shellexpand_with_dollar() {
let result = shellexpand_env("$HOME/test");
assert!(!result.contains("$HOME"));
}
#[test]
fn test_compile_command_with_toolchain() {
let mut cmd = X86CompileCommand::new("/src", "main.c", vec!["clang".to_string()]);
cmd.toolchain_kind = Some(X86ToolchainKind::Clang);
assert_eq!(cmd.toolchain_kind, Some(X86ToolchainKind::Clang));
}
#[test]
fn test_database_parse_multiple_commands() {
let json = r#"[
{"directory": "/a", "file": "a.c", "arguments": ["cc", "-c", "a.c"]},
{"directory": "/b", "file": "b.c", "arguments": ["cc", "-c", "b.c"]}
]"#;
let db = X86CompilationDatabase::parse(json, None).unwrap();
assert_eq!(db.len(), 2);
}
#[test]
fn test_build_system_cmake_cross_compilation() {
let mut tc = X86Toolchain::new(X86ToolchainKind::Gcc);
tc.is_cross = true;
tc.target_triple = "x86_64-linux-musl".to_string();
let mut bsi = X86BuildSystemIntegration::new(tc);
bsi.sysroot = Some(PathBuf::from("/opt/cross/x86_64-linux-musl"));
let content = bsi.generate_cmake_toolchain();
assert!(content.contains("CMAKE_SYSROOT"));
assert!(content.contains("CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER"));
}
#[test]
fn test_all_x86_cpus_listed() {
let cpus = X86Toolchain::known_x86_cpus();
assert!(cpus.contains(&"x86-64".to_string()));
assert!(cpus.contains(&"znver4".to_string()));
assert!(cpus.contains(&"sapphirerapids".to_string()));
assert!(cpus.contains(&"alderlake".to_string()));
assert!(cpus.contains(&"native".to_string()));
}
#[test]
fn test_toolchain_all_abi_variants() {
for abi in &[
X86Abi::SysV,
X86Abi::MsAbi,
X86Abi::Intel,
X86Abi::GnuIlp32,
X86Abi::BareMetal,
] {
let mut tc = X86Toolchain::new(X86ToolchainKind::Gcc);
tc.abi = *abi;
assert_eq!(tc.abi, *abi);
}
}
#[test]
fn test_word_size_all_variants() {
for ws in &[
X86WordSize::Bits16,
X86WordSize::Bits32,
X86WordSize::Bits64,
X86WordSize::X32,
] {
let mut tc = X86Toolchain::new(X86ToolchainKind::Gcc);
tc.word_size = *ws;
assert_eq!(tc.word_size, *ws);
}
}
#[test]
fn test_driver_cross_compile_command() {
let tc = X86Toolchain::default();
let driver = X86ToolDriver::new(tc);
let cmd = driver.build_cross_compile_command("test.c", "test.o", "i686-linux-gnu");
let repr = format!("{:?}", cmd);
assert!(repr.contains("--target=i686-linux-gnu"));
}
#[test]
fn test_manager_fallback_exists() {
let mut manager = X86ToolchainManager::new();
let mut tc = X86Toolchain::new(X86ToolchainKind::Gcc);
tc.is_installed = true;
manager.add_toolchain(tc);
let fallback = manager.get_fallback(X86ToolchainKind::IccClassic);
assert!(fallback.is_some());
}
#[test]
fn test_compiler_wrapper_clear_cache() {
let mut wrapper = X86CompilerWrapper::new();
wrapper.configure_ccache("/tmp/ccache_test", "1G");
let _ = wrapper.clear_cache();
}
#[test]
fn test_coverage_post_process_commands() {
let mut cov = X86CoverageConfig::new();
cov.enabled = true;
cov.format = CoverageFormat::Llvm;
let cmds = cov.post_process_commands();
assert!(cmds.iter().any(|c| c.contains("llvm-profdata")));
}
#[test]
fn test_driver_tool_output() {
let output = X86ToolOutput {
exit_code: 0,
stdout: "test output".to_string(),
stderr: String::new(),
success: true,
elapsed: Duration::from_secs(1),
};
assert!(output.is_success());
assert!(!output.has_errors());
}
#[test]
fn test_config_all_tool_kinds() {
for kind in &[
X86ToolKind::Cc,
X86ToolKind::Cxx,
X86ToolKind::Assembler,
X86ToolKind::Linker,
X86ToolKind::Archiver,
] {
let cfg = X86ToolConfig::new(*kind);
assert_eq!(cfg.tool_kind, *kind);
}
}
#[test]
fn test_toolchain_default_cpu() {
let tc = X86Toolchain::new(X86ToolchainKind::Gcc);
assert_eq!(tc.default_cpu, "x86-64");
}
#[test]
fn test_multiarch_paths() {
let tuples = vec!["x86_64-linux-gnu", "i386-linux-gnu", "aarch64-linux-gnu"];
for t in &tuples {
assert!(X86Toolchain::known_x86_cpus().len() > 0); }
}
#[test]
fn test_search_library_detection() {
let search = X86LibrarySearch::new("x86_64-unknown-linux-gnu");
let found = search.find_library("c");
assert!(found.is_some() || !cfg!(target_os = "linux"));
}
#[test]
fn test_toolchain_builder_pattern() {
let tc = X86Toolchain::new(X86ToolchainKind::Clang)
.with_target_triple("x86_64-linux-gnu")
.with_word_size(X86WordSize::Bits64)
.with_abi(X86Abi::SysV)
.with_cross(false);
assert_eq!(tc.target_triple, "x86_64-linux-gnu");
assert_eq!(tc.word_size, X86WordSize::Bits64);
assert!(!tc.is_cross);
}
#[test]
fn test_full_integration_toolchain_to_build() {
let tc = X86Toolchain::new(X86ToolchainKind::Gcc);
let config = X86ToolConfig::for_c_compiler().with_optimization("2");
let driver = X86ToolDriver::new(tc.clone());
let bsi = X86BuildSystemIntegration::new(tc);
let cmake = bsi.generate_cmake_toolchain();
assert!(cmake.contains("CMAKE_C_COMPILER"));
}
#[test]
fn test_all_sysroot_kinds() {
let kinds = vec![
SysrootKind::LinuxNative,
SysrootKind::LinuxCross,
SysrootKind::AndroidNdk,
SysrootKind::MacOsSdk,
SysrootKind::IosSdk,
SysrootKind::WindowsSdk,
SysrootKind::WasiSysroot,
SysrootKind::Custom,
];
for k in &kinds {
let s = k.as_str();
assert!(!s.is_empty());
}
}
#[test]
fn test_all_compiler_wrapper_kinds() {
let kinds = vec![
CompilerWrapperKind::None,
CompilerWrapperKind::Ccache,
CompilerWrapperKind::Distcc,
CompilerWrapperKind::IceCream,
CompilerWrapperKind::Sccache,
];
for k in &kinds {
let s = k.as_str();
assert!(!s.is_empty());
}
}
#[test]
fn test_coverage_format_enum() {
assert_eq!(CoverageFormat::Llvm as i32, CoverageFormat::Llvm as i32);
assert!(CoverageFormat::Llvm != CoverageFormat::Gcc);
}
#[test]
fn test_pgo_runtime_enum() {
assert_eq!(PgoRuntime::Llvm as i32, PgoRuntime::Llvm as i32);
assert!(PgoRuntime::Llvm != PgoRuntime::Gcc);
}
#[test]
fn test_lto_mode_fallthrough() {
assert_eq!(LtoMode::None.flag(), None);
assert_eq!(LtoMode::None.linker_flag(), None);
assert_eq!(LtoMode::Thin.flag(), Some("-flto=thin"));
assert_eq!(LtoMode::Full.flag(), Some("-flto"));
}
#[test]
fn test_cmake_project_setup_full() {
let mut tc = X86Toolchain::new(X86ToolchainKind::Gcc);
tc.description = "GCC 13.2.0".to_string();
tc.target_triple = "x86_64-linux-gnu".to_string();
tc.word_size = X86WordSize::Bits64;
tc.is_cross = false;
tc.use_lld = true;
let bsi = X86BuildSystemIntegration::new(tc.clone());
let cmake_content = bsi.generate_cmake_toolchain();
let mut db = X86CompilationDatabase::new();
db.add_command(X86CompileCommand::new(
"/project",
"src/main.c",
vec![
"gcc".to_string(),
"-c".to_string(),
"-O2".to_string(),
"-march=x86-64".to_string(),
"src/main.c".to_string(),
"-o".to_string(),
"build/main.o".to_string(),
],
));
let mut lib_search = X86LibrarySearch::new("x86_64-linux-gnu");
lib_search.multiarch_tuple = Some("x86_64-linux-gnu".to_string());
lib_search.setup_origin_rpath("lib");
assert!(cmake_content.contains("CMAKE_SYSTEM_NAME Linux"));
assert_eq!(db.len(), 1);
assert!(lib_search.use_rpath);
}
#[test]
fn test_android_cross_compile_setup() {
let mut tc = X86Toolchain::new(X86ToolchainKind::Clang);
tc.target_triple = "x86_64-linux-android".to_string();
tc.is_cross = true;
tc.word_size = X86WordSize::Bits64;
let mut sysroot = X86SysrootManager::new("x86_64-linux-android");
sysroot
.set_custom_sysroot("/opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/sysroot");
let mut lib_search = X86LibrarySearch::new("x86_64-linux-android");
lib_search.sysroot = sysroot.sysroot.clone();
let config = X86ToolConfig::for_c_compiler()
.with_target_flags("x86_64-linux-android")
.with_cpu("x86-64")
.with_optimization("s");
let driver = X86ToolDriver::new(tc.clone());
let cmd = driver.build_cross_compile_command("test.c", "test.o", "x86_64-linux-android");
let repr = format!("{:?}", cmd);
assert!(repr.contains("x86_64-linux-android"));
assert!(config.all_flags().contains(&"-Os".to_string()));
}
#[test]
fn test_msvc_build_setup() {
let mut tc = X86Toolchain::new(X86ToolchainKind::Msvc);
tc.target_triple = "x86_64-pc-windows-msvc".to_string();
tc.abi = X86Abi::MsAbi;
tc.word_size = X86WordSize::Bits64;
let config = X86ToolConfig::msvc_defaults(X86WordSize::Bits64);
assert!(config.contains(&"/c".to_string()));
assert!(config.contains(&"/nologo".to_string()));
let driver = X86ToolDriver::new(tc.clone());
let _ = driver.build_msvc_link_command(&["main.obj".to_string()], "program.exe");
let _ = driver.build_lib_command(&["main.obj".to_string()], "main.lib");
}
#[test]
fn test_combined_lto_sanitizer_coverage() {
let mut tc = X86Toolchain::new(X86ToolchainKind::Clang);
tc.lto_supported = true;
tc.thin_lto_supported = true;
let mut lto = X86LtoConfig::new();
lto.mode = LtoMode::Thin;
lto.thinlto_jobs = Some(4);
let mut san = X86SanitizerConfig::new();
san.enable("address");
let mut cov = X86CoverageConfig::new();
cov.enabled = true;
cov.format = CoverageFormat::SourceBased;
let lto_flags = lto.compiler_flags();
let san_flags = san.compiler_flags();
let cov_flags = cov.compiler_flags();
assert!(lto_flags.contains(&"-flto=thin".to_string()));
assert!(san_flags.iter().any(|f| f.contains("address")));
assert!(!cov_flags.is_empty());
}
#[test]
fn test_pgo_full_workflow() {
let mut pgo = X86PgoConfig::new();
pgo.instrument = true;
pgo.profile_dir = Some(PathBuf::from("/tmp/profiles"));
pgo.ir_level = true;
let instr_flags = pgo.instrumentation_flags();
assert!(instr_flags
.iter()
.any(|f| f.contains("fprofile-instr-generate")));
pgo.instrument = false;
pgo.profile_file = Some(PathBuf::from("/tmp/profiles/default.profdata"));
let use_flags = pgo.use_flags();
assert!(use_flags.iter().any(|f| f.contains("fprofile-instr-use")));
}
#[test]
fn test_detect_and_select_pipeline() {
let mut manager = X86ToolchainManager::new();
let mut gcc = X86Toolchain::new(X86ToolchainKind::Gcc);
gcc.description = "GCC 13".to_string();
gcc.target_triple = "x86_64-unknown-linux-gnu".to_string();
gcc.is_installed = true;
manager.add_toolchain(gcc);
let mut clang = X86Toolchain::new(X86ToolchainKind::Clang);
clang.description = "Clang 18".to_string();
clang.target_triple = "x86_64-unknown-linux-gnu".to_string();
clang.is_installed = true;
manager.add_toolchain(clang);
let selected = manager.select_for_target("x86_64-unknown-linux-gnu");
assert!(selected.is_some());
assert_eq!(selected.unwrap().description, "GCC 13");
manager.prefer_clang = true;
let selected2 = manager.select_for_target("x86_64-unknown-linux-gnu");
assert_eq!(selected2.unwrap().description, "Clang 18");
}
#[test]
fn test_wrapper_driver_integration() {
let mut wrapper = X86CompilerWrapper::new();
wrapper.configure_ccache("/var/cache/ccache", "10G");
let tc = X86Toolchain::default();
let mut driver = X86ToolDriver::new(tc);
let compiler = "gcc";
let original_args = vec![
"-c".to_string(),
"test.c".to_string(),
"-o".to_string(),
"test.o".to_string(),
];
let wrapped_args = wrapper.wrap_command(compiler, &original_args);
assert_eq!(wrapped_args[0], "ccache");
assert_eq!(wrapped_args[1], "gcc");
let env = wrapper.wrapper_environment();
assert!(env.contains_key("CCACHE_DIR"));
}
#[test]
fn test_response_file_integration() {
let mut rf = X86ResponseFile::new("/tmp/cmake_args.rsp");
rf.max_command_line = 100;
let args: Vec<String> = (0..50)
.map(|i| format!("very-long-argument-{}", i))
.collect();
assert!(rf.needs_response_file(&args));
rf.args = args.clone();
let write_result = rf.write();
assert!(write_result.is_ok());
let _ = fs::remove_file("/tmp/cmake_args.rsp");
}
#[test]
fn test_all_tool_kinds_complete() {
let all_kinds = [
X86ToolKind::Cc,
X86ToolKind::Cxx,
X86ToolKind::Assembler,
X86ToolKind::Linker,
X86ToolKind::Archiver,
X86ToolKind::ObjCopy,
X86ToolKind::ObjDump,
X86ToolKind::Nm,
X86ToolKind::Strip,
X86ToolKind::Size,
X86ToolKind::ReadElf,
X86ToolKind::Debugger,
X86ToolKind::Profiler,
X86ToolKind::Ranlib,
X86ToolKind::DllTool,
X86ToolKind::ResourceCompiler,
X86ToolKind::LtoPlugin,
];
for kind in &all_kinds {
let name = kind.display_name();
assert!(!name.is_empty());
}
}
#[test]
fn test_library_search_by_target() {
let search64 = X86LibrarySearch::new("x86_64-unknown-linux-gnu");
assert!(search64
.standard_paths
.iter()
.any(|p| { p.to_string_lossy().contains("x86_64-linux-gnu") }));
let search32 = X86LibrarySearch::new("i686-unknown-linux-gnu");
assert!(search32.standard_paths.iter().any(|p| {
p.to_string_lossy().contains("i386-linux-gnu") || p.to_string_lossy().contains("lib32")
}));
}
#[test]
fn test_gcc_env_collection() {
let detector = X86ToolchainDetector::new();
let tc = X86Toolchain::new(X86ToolchainKind::Gcc);
let env = detector.collect_gcc_env(&tc);
assert!(env.len() >= 0);
}
#[test]
fn test_all_cpus_have_march_flags() {
for cpu in &X86Toolchain::known_x86_cpus() {
let flag = X86ToolConfig::cpu_march_flag(cpu);
assert!(flag.starts_with("-march="));
}
}
#[test]
fn test_sysroot_manager_all_kinds() {
let targets = vec![
("x86_64-unknown-linux-gnu", SysrootKind::LinuxNative),
("x86_64-linux-android", SysrootKind::AndroidNdk),
("x86_64-apple-darwin", SysrootKind::MacOsSdk),
];
for (triple, expected_kind) in &targets {
let mut mgr = X86SysrootManager::new(triple);
mgr.detect();
let _ = mgr.sysroot_flags();
}
}
#[test]
fn test_priority_sorting_many() {
let mut tcs: Vec<X86Toolchain> = Vec::new();
for kind in &[
X86ToolchainKind::Gcc,
X86ToolchainKind::Clang,
X86ToolchainKind::Msvc,
X86ToolchainKind::IccClassic,
X86ToolchainKind::Custom("test".to_string()),
] {
for ver in 1..=15 {
let mut tc = X86Toolchain::new(kind.clone());
tc.compiler_version_major = ver;
tc.is_installed = true;
tc.is_cross = false;
tc.description = format!("{} v{}", kind.as_str(), ver);
tcs.push(tc);
}
}
tcs.sort_by(|a, b| {
let pa = toolchain_priority_score(a);
let pb = toolchain_priority_score(b);
pb.cmp(&pa)
});
assert!(tcs[0].kind == X86ToolchainKind::Gcc);
}
#[test]
fn test_defaults_are_consistent() {
let tc1 = X86Toolchain::default();
let tc2 = X86Toolchain::default();
assert_eq!(tc1.target_triple, tc2.target_triple);
let cfg1 = X86ToolConfig::default();
let cfg2 = X86ToolConfig::default();
assert_eq!(cfg1.tool_kind, cfg2.tool_kind);
let db1 = X86CompilationDatabase::default();
let db2 = X86CompilationDatabase::default();
assert_eq!(db1.len(), db2.len());
let pgo1 = X86PgoConfig::default();
let pgo2 = X86PgoConfig::default();
assert_eq!(pgo1.instrument, pgo2.instrument);
let lto1 = X86LtoConfig::default();
let lto2 = X86LtoConfig::default();
assert_eq!(lto1.mode, lto2.mode);
let cov1 = X86CoverageConfig::default();
let cov2 = X86CoverageConfig::default();
assert_eq!(cov1.enabled, cov2.enabled);
let san1 = X86SanitizerConfig::default();
let san2 = X86SanitizerConfig::default();
assert_eq!(san1.enabled.len(), san2.enabled.len());
let man1 = X86ToolchainManager::default();
let man2 = X86ToolchainManager::default();
assert_eq!(man1.count_installed(), man2.count_installed());
let det1 = X86ToolchainDetector::default();
let det2 = X86ToolchainDetector::default();
assert_eq!(det1.host_triple, det2.host_triple);
let sys1 = X86SysrootManager::default();
let sys2 = X86SysrootManager::default();
assert_eq!(sys1.target_triple, sys2.target_triple);
let lib1 = X86LibrarySearch::default();
let lib2 = X86LibrarySearch::default();
assert_eq!(lib1.target_triple, lib2.target_triple);
let wrap1 = X86CompilerWrapper::default();
let wrap2 = X86CompilerWrapper::default();
assert_eq!(wrap1.ccache_available, wrap2.ccache_available);
let rf1 = X86ResponseFile::default();
let rf2 = X86ResponseFile::default();
assert_eq!(rf1.max_command_line, rf2.max_command_line);
}
#[test]
fn test_known_cpus_no_duplicates() {
let mut cpus = X86Toolchain::known_x86_cpus();
let original_len = cpus.len();
cpus.sort();
cpus.dedup();
assert_eq!(
cpus.len(),
original_len,
"CPU list should have no duplicates"
);
}
#[test]
fn test_all_workaround_sets() {
let gcc_wa = X86ToolchainWorkaround::known_gcc_workarounds();
let clang_wa = X86ToolchainWorkaround::known_clang_workarounds();
let msvc_wa = X86ToolchainWorkaround::known_msvc_workarounds();
let icc_wa = X86ToolchainWorkaround::known_icc_workarounds();
assert!(gcc_wa.len() >= 3);
assert!(clang_wa.len() >= 2);
assert!(msvc_wa.len() >= 1);
assert!(icc_wa.len() >= 1);
}
#[test]
fn test_wrapper_default_is_usable() {
let wrapper = X86CompilerWrapper::default();
let env = wrapper.wrapper_environment();
assert!(env.len() >= 0);
}
}