//! Cross-compilation support //! //! Provides configuration for cross-compiling packages with a target triplet prefix. use anyhow::{Context, Result}; use std::fs; use std::io::Write; use std::path::{Path, PathBuf}; use std::process::Command; /// Cross-compilation configuration #[derive(Debug, Clone)] pub struct CrossConfig { /// Target triplet prefix (e.g., "x86_64-linux-musl") pub prefix: String, /// C compiler pub cc: String, /// C++ compiler pub cxx: String, /// Archiver pub ar: String, /// Ranlib pub ranlib: String, /// Strip pub strip: String, /// Linker (ld) pub ld: String, /// nm pub nm: String, /// objcopy pub objcopy: String, /// objdump pub objdump: String, /// readelf pub readelf: String, } impl CrossConfig { /// Create a cross-compilation config by discovering tools in PATH pub fn from_prefix(prefix: &str) -> Result { let cc = find_tool(prefix, &["gcc", "clang"], true)?; let cxx = find_tool(prefix, &["g++", "clang++"], false) .unwrap_or_else(|_| format!("{}-g++", prefix)); let ar = find_tool(prefix, &["ar", "llvm-ar"], true)?; let ranlib = find_tool(prefix, &["ranlib", "llvm-ranlib"], false) .unwrap_or_else(|_| format!("{}-ranlib", prefix)); let strip = find_tool(prefix, &["strip", "llvm-strip"], false) .unwrap_or_else(|_| format!("{}-strip", prefix)); let ld = find_tool(prefix, &["ld", "ld.lld"], false) .unwrap_or_else(|_| format!("{}-ld", prefix)); let nm = find_tool(prefix, &["nm", "llvm-nm"], false) .unwrap_or_else(|_| format!("{}-nm", prefix)); let objcopy = find_tool(prefix, &["objcopy", "llvm-objcopy"], false) .unwrap_or_else(|_| format!("{}-objcopy", prefix)); let objdump = find_tool(prefix, &["objdump", "llvm-objdump"], false) .unwrap_or_else(|_| format!("{}-objdump", prefix)); let readelf = find_tool(prefix, &["readelf", "llvm-readelf"], false) .unwrap_or_else(|_| format!("{}-readelf", prefix)); println!("Cross-compilation tools discovered:"); println!(" CC: {}", cc); println!(" CXX: {}", cxx); println!(" AR: {}", ar); println!(" RANLIB: {}", ranlib); println!(" STRIP: {}", strip); Ok(Self { prefix: prefix.to_string(), cc, cxx, ar, ranlib, strip, ld, nm, objcopy, objdump, readelf, }) } /// Get the host triple for autotools --host flag pub fn host_triple(&self) -> &str { &self.prefix } /// Get the build machine triple (native compiler) pub fn build_triple() -> Result { let output = Command::new("gcc") .arg("-dumpmachine") .output() .or_else(|_| Command::new("cc").arg("-dumpmachine").output()) .context("Failed to determine build machine triple")?; Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()) } /// Generate a CMake toolchain file for cross-compilation pub fn generate_cmake_toolchain(&self, build_dir: &Path) -> Result { let toolchain_path = build_dir.join("cross-toolchain.cmake"); let content = format!( r#"# CMake toolchain file for cross-compilation # Generated by nyapm for target: {} set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR {}) set(CMAKE_C_COMPILER {}) set(CMAKE_CXX_COMPILER {}) set(CMAKE_AR {} CACHE FILEPATH "Archiver") set(CMAKE_RANLIB {} CACHE FILEPATH "Ranlib") set(CMAKE_STRIP {} CACHE FILEPATH "Strip") set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) "#, self.prefix, self.target_arch(), self.cc, self.cxx, self.ar, self.ranlib, self.strip, ); fs::create_dir_all(build_dir)?; let mut file = fs::File::create(&toolchain_path)?; file.write_all(content.as_bytes())?; Ok(toolchain_path) } /// Generate a Meson cross file for cross-compilation pub fn generate_meson_cross_file(&self, build_dir: &Path) -> Result { let cross_path = build_dir.join("cross-file.ini"); let content = format!( r#"# Meson cross file for cross-compilation # Generated by nyapm for target: {} [binaries] c = '{}' cpp = '{}' ar = '{}' strip = '{}' ld = '{}' nm = '{}' objcopy = '{}' objdump = '{}' readelf = '{}' [host_machine] system = 'linux' cpu_family = '{}' cpu = '{}' endian = 'little' "#, self.prefix, self.cc, self.cxx, self.ar, self.strip, self.ld, self.nm, self.objcopy, self.objdump, self.readelf, self.cpu_family(), self.target_arch(), ); fs::create_dir_all(build_dir)?; let mut file = fs::File::create(&cross_path)?; file.write_all(content.as_bytes())?; Ok(cross_path) } /// Extract target architecture from prefix fn target_arch(&self) -> &str { self.prefix.split('-').next().unwrap_or("unknown") } /// Get CPU family for Meson fn cpu_family(&self) -> &str { let arch = self.target_arch(); match arch { "x86_64" | "amd64" => "x86_64", "i686" | "i586" | "i486" | "i386" => "x86", "aarch64" | "arm64" => "aarch64", "arm" | "armv7" | "armv7l" => "arm", "riscv64" => "riscv64", "riscv32" => "riscv32", "powerpc64" | "ppc64" => "ppc64", "powerpc" | "ppc" => "ppc", "mips64" => "mips64", "mips" => "mips", _ => arch, } } } /// Find a cross-compilation tool in PATH fn find_tool(prefix: &str, suffixes: &[&str], required: bool) -> Result { for suffix in suffixes { let tool_name = format!("{}-{}", prefix, suffix); if tool_exists(&tool_name) { return Ok(tool_name); } } if required { anyhow::bail!( "Could not find cross tool: {}-{{{}}}", prefix, suffixes.join("|") ); } Err(anyhow::anyhow!("Tool not found")) } /// Check if a tool exists in PATH fn tool_exists(name: &str) -> bool { Command::new("which") .arg(name) .output() .map(|o| o.status.success()) .unwrap_or(false) }