const LIB_NAME: &str = "arceos_rust_interface";
use std::{
env,
path::{Path, PathBuf},
process::Command,
};
fn main() {
println!(
"cargo:warning=Running build script for ArceOS rust library. Time: {:?}",
std::time::SystemTime::now()
);
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let lib_dir = manifest_dir.join("lib");
let config_path = get_config_path(&manifest_dir, &out_dir);
println!("cargo:warning=config path: {}", config_path.display());
let artifact_path = compile_project(&lib_dir, &out_dir, &config_path);
let lib_file = artifact_path.join(format!("lib{}.a", LIB_NAME));
let rename_list = generate_rename_list(&lib_file);
rename_symbols(&lib_file, &rename_list);
let linker_script_path = artifact_path.join("linker.x");
if !linker_script_path.exists() {
panic!(
"Linker script not found at {}. Expected from axhal build.",
linker_script_path.display()
);
}
println!(
"cargo:warning=Linker script path: {}",
linker_script_path.display()
);
println!("cargo:rustc-link-search=native={}", artifact_path.display());
println!("cargo:rustc-link-lib=static={}", LIB_NAME);
println!("cargo:rerun-if-changed=always");
}
fn get_config_path(manifest_dir: &Path, out_dir: &Path) -> PathBuf {
if let Ok(path) = env::var("ARCEOS_RUST_CONFIG")
&& !path.trim().is_empty()
{
return PathBuf::from(path);
}
generate_config(manifest_dir, out_dir)
}
fn generate_config(manifest_dir: &Path, out_dir: &Path) -> PathBuf {
let template = manifest_dir.join("defconfig.toml");
let arch = get_arch();
let platform = get_platform();
let platform_config_path = get_platform_config_path(platform);
let out_config_path = out_dir.join("axconfig.toml");
let command = Command::new("axconfig-gen")
.arg(&template)
.arg(platform_config_path)
.arg("-w")
.arg(format!(r#"arch="{}""#, &arch))
.arg("-w")
.arg(format!(r#"platform="{}""#, get_platform()))
.arg("-o")
.arg(&out_config_path)
.status()
.expect("Failed to generate configuration file.");
if !command.success() {
panic!("Failed to generate configuration file.");
}
out_config_path
}
fn get_platform_config_path(platform: &str) -> PathBuf {
if let Ok(path) = env::var("ARCEOS_RUST_PLATFORM_CONFIG") {
return PathBuf::from(path);
}
let output = Command::new(cargo())
.arg("axplat")
.arg("info")
.arg(format!("ax-plat-{}", platform))
.arg("-c")
.output()
.expect("Failed to get platform config path.");
if !output.status.success() {
println!(
"cargo:warning=axplat output: {} {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
panic!("Failed to get platform config path.");
}
PathBuf::from(String::from_utf8_lossy(&output.stdout).trim())
}
fn compile_project(lib_dir: &PathBuf, out_dir: &PathBuf, config_path: &PathBuf) -> PathBuf {
let profile = env::var("PROFILE").unwrap();
let is_debug = profile == "debug";
let arch = get_arch();
let target = get_target(&arch);
let features = env::var("CARGO_CFG_FEATURE").unwrap();
let feature_list = features.replace(",", " ");
let mut command = Command::new(cargo());
command.env("AX_TARGET", target);
command.env("AX_MODE", profile);
command.env("AX_CONFIG_PATH", config_path);
command.env("AX_LOG", get_log_level(&features));
if env::var("AX_IP").is_err() {
command.env("AX_IP", "10.0.2.15");
}
if env::var("AX_GW").is_err() {
command.env("AX_GW", "10.0.2.2");
}
command
.current_dir(lib_dir)
.arg("build")
.arg("--target-dir")
.arg(out_dir)
.arg("--target")
.arg(target)
.arg("--no-default-features");
if !feature_list.is_empty() {
command.arg("--features").arg(feature_list);
}
if !is_debug {
command.arg("--release");
}
println!(
"cargo:warning=FATURES are {}",
env::var("CARGO_CFG_FEATURE").unwrap_or("none".to_string())
);
println!("cargo:warning=command: {:?}", command);
let status = command.status().expect("Failed to build ArceOS library.");
if !status.success() {
panic!("Failed to build ArceOS library.");
}
let build_type = if is_debug { "debug" } else { "release" };
out_dir.join(target).join(build_type)
}
fn cargo() -> String {
env::var("CARGO").unwrap()
}
fn generate_rename_list(lib_path: &Path) -> PathBuf {
let nm_output = Command::new("rust-nm")
.arg(lib_path)
.output()
.expect("Failed to run rust-nm. Please ensure llvm-tools-preview is installed.");
if !nm_output.status.success() {
panic!(
"rust-nm failed:\n{}",
String::from_utf8_lossy(&nm_output.stderr)
);
}
let symbols_output = String::from_utf8_lossy(&nm_output.stdout);
let mut rename_pairs = std::collections::HashSet::new();
for line in symbols_output.lines() {
if let Some(symbol) = line.split_whitespace().last()
&& symbol.contains("___rustc")
{
rename_pairs.insert((symbol.to_string(), format!("{}_1", symbol)));
}
}
let rename_list_path = lib_path.parent().unwrap().join("symbol_rename_auto.txt");
let mut file = std::fs::File::create(&rename_list_path)
.expect("Failed to create auto-generated rename list file");
use std::io::Write;
for (old_symbol, new_symbol) in &rename_pairs {
writeln!(file, "{} {}", old_symbol, new_symbol)
.expect("Failed to write to rename list file");
}
println!(
"cargo:warning=Auto-generated {} symbol rename rules",
rename_pairs.len()
);
rename_list_path
}
fn rename_symbols(lib_path: &Path, rename_list: &Path) {
let output = Command::new("rust-objcopy")
.arg("--redefine-syms")
.arg(rename_list)
.arg(lib_path)
.output();
match output {
Ok(output) if output.status.success() => {}
Ok(output) => panic!(
"Failed to rename symbols with rust-objcopy (exit: {}).\nstdout:\n{}\nstderr:\n{}",
output
.status
.code()
.map_or_else(|| "signal".to_string(), |c| c.to_string()),
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
),
Err(_) => panic!(
"Failed to run rust-objcopy. Please install required tools with:\n rustup component \
add llvm-tools-preview\n cargo install cargo-binutils"
),
}
}
fn get_arch() -> String {
env::var("CARGO_CFG_TARGET_ARCH").unwrap()
}
fn get_target(arch: &str) -> &'static str {
match arch {
"x86_64" => "x86_64-unknown-none",
"aarch64" => "aarch64-unknown-none-softfloat",
"riscv64" => "riscv64gc-unknown-none-elf",
"loongarch64" => "loongarch64-unknown-none-softfloat",
_ => panic!("Unsupported architecture: {}", arch),
}
}
fn get_platform() -> &'static str {
let arch = get_arch();
match arch.as_ref() {
"x86_64" => "x86-pc",
"aarch64" => "aarch64-qemu-virt",
"riscv64" => "riscv64-qemu-virt",
"loongarch64" => "loongarch64-qemu-virt",
_ => panic!("Unsupported architecture: {}", arch),
}
}
fn get_log_level(feature_list: &str) -> &str {
let mut level = "off";
for feature in feature_list.split(',') {
if let Some(stripped) = feature.strip_prefix("log-level-") {
level = stripped;
}
}
level
}