use std::path::PathBuf;
use anyhow::{bail, Context, Result};
use super::{detect_default_compiler, find_executable, CompilerInfo, CompilerType, Toolchain};
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LinuxArch {
X86_64,
Aarch64,
}
impl LinuxArch {
pub fn triple_prefix(self) -> &'static str {
match self {
LinuxArch::X86_64 => "x86_64-linux-gnu",
LinuxArch::Aarch64 => "aarch64-linux-gnu",
}
}
pub fn arch_string(self) -> &'static str {
match self {
LinuxArch::X86_64 => "x86_64",
LinuxArch::Aarch64 => "aarch64",
}
}
pub fn cmake_system_processor(self) -> &'static str {
match self {
LinuxArch::X86_64 => "x86_64",
LinuxArch::Aarch64 => "aarch64",
}
}
pub fn parse(s: &str) -> Result<Self> {
match s {
"x86_64" | "x64" => Ok(LinuxArch::X86_64),
"aarch64" | "arm64" => Ok(LinuxArch::Aarch64),
other => bail!("Unknown Linux architecture '{}'. Supported: x86_64, aarch64", other),
}
}
}
pub struct LinuxToolchain {
compiler: CompilerInfo,
arch: LinuxArch,
}
impl LinuxToolchain {
pub fn detect() -> Result<Self> {
Self::detect_for_arch(LinuxArch::X86_64)
}
pub fn detect_for_arch(arch: LinuxArch) -> Result<Self> {
let compiler = match arch {
LinuxArch::X86_64 => {
detect_default_compiler()
.context("No C/C++ compiler found. Please install GCC or Clang.")?
}
LinuxArch::Aarch64 => {
let prefix = arch.triple_prefix();
let cc_name = format!("{}-gcc", prefix);
let cxx_name = format!("{}-g++", prefix);
let cc = find_executable(&cc_name).ok_or_else(|| {
anyhow::anyhow!(
"aarch64 cross-compiler not found: {}\n\
Install it with: apt-get install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu",
cc_name
)
})?;
let cxx = find_executable(&cxx_name).ok_or_else(|| {
anyhow::anyhow!("aarch64 C++ cross-compiler not found: {}", cxx_name)
})?;
let version = super::get_compiler_version(&cc).unwrap_or_else(|| "unknown".to_string());
CompilerInfo { compiler_type: CompilerType::Gcc, cc, cxx, version }
}
};
Ok(Self { compiler, arch })
}
pub fn prefer_gcc() -> Result<Self> {
if let (Some(cc), Some(cxx)) = (find_executable("gcc"), find_executable("g++")) {
let version = super::get_compiler_version(&cc).unwrap_or_else(|| "unknown".to_string());
return Ok(Self {
compiler: CompilerInfo {
compiler_type: CompilerType::Gcc,
cc,
cxx,
version,
},
arch: LinuxArch::X86_64,
});
}
Self::detect()
}
pub fn prefer_clang() -> Result<Self> {
if let (Some(cc), Some(cxx)) = (find_executable("clang"), find_executable("clang++")) {
let version = super::get_compiler_version(&cc).unwrap_or_else(|| "unknown".to_string());
return Ok(Self {
compiler: CompilerInfo {
compiler_type: CompilerType::Clang,
cc,
cxx,
version,
},
arch: LinuxArch::X86_64,
});
}
Self::detect()
}
pub fn compiler(&self) -> &CompilerInfo {
&self.compiler
}
pub fn arch(&self) -> LinuxArch {
self.arch
}
pub fn ar_path(&self) -> PathBuf {
match self.arch {
LinuxArch::X86_64 => {
find_executable("ar").unwrap_or_else(|| PathBuf::from("ar"))
}
LinuxArch::Aarch64 => {
let ar_name = format!("{}-ar", self.arch.triple_prefix());
find_executable(&ar_name).unwrap_or_else(|| PathBuf::from("ar"))
}
}
}
pub fn cross_cmake_variables(&self) -> Vec<(String, String)> {
match self.arch {
LinuxArch::X86_64 => vec![],
LinuxArch::Aarch64 => {
let prefix = self.arch.triple_prefix();
vec![
("CMAKE_SYSTEM_NAME".to_string(), "Linux".to_string()),
("CMAKE_SYSTEM_PROCESSOR".to_string(), self.arch.cmake_system_processor().to_string()),
("CMAKE_C_COMPILER".to_string(), self.compiler.cc.display().to_string()),
("CMAKE_CXX_COMPILER".to_string(), self.compiler.cxx.display().to_string()),
("CMAKE_FIND_ROOT_PATH".to_string(), format!("/usr/{}", prefix)),
("CMAKE_FIND_ROOT_PATH_MODE_PROGRAM".to_string(), "NEVER".to_string()),
("CMAKE_FIND_ROOT_PATH_MODE_LIBRARY".to_string(), "ONLY".to_string()),
("CMAKE_FIND_ROOT_PATH_MODE_INCLUDE".to_string(), "ONLY".to_string()),
]
}
}
}
pub fn merge_static_libs(&self, src_libs: &[PathBuf], dst_lib: &PathBuf) -> Result<()> {
if src_libs.is_empty() {
bail!("No source libraries to merge");
}
if let Some(parent) = dst_lib.parent() {
std::fs::create_dir_all(parent)?;
}
let temp_dir = std::env::temp_dir().join(format!(
"ccgo-merge-{}",
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs()
));
std::fs::create_dir_all(&temp_dir)?;
let ar_cmd = self.ar_path();
for (idx, lib) in src_libs.iter().enumerate() {
let extract_dir = temp_dir.join(format!("lib{}", idx));
std::fs::create_dir_all(&extract_dir)?;
let output = std::process::Command::new(&ar_cmd)
.arg("x")
.arg(lib)
.current_dir(&extract_dir)
.output()
.context("Failed to run ar for extraction")?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
std::fs::remove_dir_all(&temp_dir).ok();
bail!("ar extraction failed for {}: {}", lib.display(), stderr);
}
}
let mut object_files: Vec<PathBuf> = Vec::new();
for entry in walkdir::WalkDir::new(&temp_dir) {
let entry = entry?;
let ext = entry.path().extension().and_then(|e| e.to_str());
if matches!(ext, Some("o") | Some("obj")) {
object_files.push(entry.path().to_path_buf());
}
}
if object_files.is_empty() {
std::fs::remove_dir_all(&temp_dir).ok();
bail!("No object files found in source libraries");
}
if dst_lib.exists() {
std::fs::remove_file(dst_lib)?;
}
let mut cmd = std::process::Command::new(&ar_cmd);
cmd.arg("rcs").arg(dst_lib);
for obj in &object_files {
cmd.arg(obj);
}
let output = cmd.output().context("Failed to run ar for merging")?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
std::fs::remove_dir_all(&temp_dir).ok();
bail!("ar merge failed: {}", stderr);
}
std::fs::remove_dir_all(&temp_dir).ok();
Ok(())
}
}
impl Toolchain for LinuxToolchain {
fn name(&self) -> &str {
match self.compiler.compiler_type {
CompilerType::Gcc => "gcc",
CompilerType::Clang => "clang",
_ => "cc",
}
}
fn is_available(&self) -> bool {
self.compiler.cc.exists() && self.compiler.cxx.exists()
}
fn path(&self) -> Option<PathBuf> {
self.compiler.cc.parent().map(|p| p.to_path_buf())
}
fn cmake_variables(&self) -> Vec<(String, String)> {
match self.arch {
LinuxArch::X86_64 => self.compiler.cmake_variables(),
LinuxArch::Aarch64 => self.cross_cmake_variables(),
}
}
fn validate(&self) -> Result<()> {
if !self.compiler.cc.exists() {
bail!("C compiler not found at: {}", self.compiler.cc.display());
}
if !self.compiler.cxx.exists() {
bail!("C++ compiler not found at: {}", self.compiler.cxx.display());
}
Ok(())
}
}
fn get_compiler_version(compiler: &PathBuf) -> Option<String> {
let output = std::process::Command::new(compiler)
.arg("--version")
.output()
.ok()?;
let stdout = String::from_utf8_lossy(&output.stdout);
stdout.lines().next().map(|s| s.to_string())
}