Files
depot/src/cross.rs
T

230 lines
6.8 KiB
Rust
Executable File

//! 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<Self> {
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));
crate::log_info!("Cross-compilation tools discovered:");
crate::log_info!(" CC: {}", cc);
crate::log_info!(" CXX: {}", cxx);
crate::log_info!(" AR: {}", ar);
crate::log_info!(" RANLIB: {}", ranlib);
crate::log_info!(" 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<String> {
let output = Command::new("cc")
.arg("-dumpmachine")
.output()
.or_else(|_| Command::new("clang").arg("-dumpmachine").output())
.or_else(|_| Command::new("gcc").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<PathBuf> {
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<PathBuf> {
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 and return its absolute path
fn find_tool(prefix: &str, suffixes: &[&str], required: bool) -> Result<String> {
for suffix in suffixes {
let tool_name = format!("{}-{}", prefix, suffix);
// Use `which` to resolve to an absolute path (so callers get a usable path)
if let Ok(output) = Command::new("which").arg(&tool_name).output()
&& output.status.success()
{
let path = String::from_utf8_lossy(&output.stdout).trim().to_string();
return Ok(path);
}
}
if required {
anyhow::bail!(
"Could not find cross tool: {}-{{{}}}",
prefix,
suffixes.join("|")
);
}
Err(anyhow::anyhow!("Tool not found"))
}