use super::*;
pub(super) struct StdBuildTarget {
pub(super) target_name: String,
pub(super) target: String,
pub(super) cargo_args: Vec<String>,
pub(super) env: HashMap<String, String>,
}
pub(super) fn std_build_target_for(target: &str, plat_dyn: bool) -> anyhow::Result<StdBuildTarget> {
let (target_name, tool_prefix) = if target.starts_with("x86_64-") {
("x86_64-unknown-linux-musl", "x86_64-linux-musl")
} else if target.starts_with("aarch64-") {
("aarch64-unknown-linux-musl", "aarch64-linux-musl")
} else if target.starts_with("riscv64") {
("riscv64gc-unknown-linux-musl", "riscv64-linux-musl")
} else if target.starts_with("loongarch64-") {
("loongarch64-unknown-linux-musl", "loongarch64-linux-musl")
} else {
bail!("unsupported ArceOS std target triple `{target}`");
};
let mut env = HashMap::new();
env.insert(
"CARGO_UNSTABLE_JSON_TARGET_SPEC".to_string(),
"true".to_string(),
);
env.extend(std_c_toolchain_env(target_name, tool_prefix));
Ok(StdBuildTarget {
target_name: target_name.to_string(),
target: std_target_json_path(target_name, plat_dyn)
.display()
.to_string(),
cargo_args: vec!["-Z".to_string(), "json-target-spec".to_string()],
env,
})
}
pub(super) fn std_c_toolchain_env(target_name: &str, tool_prefix: &str) -> HashMap<String, String> {
let mut env = HashMap::new();
let target_env = target_name.replace('-', "_");
let cc = format!("{tool_prefix}-cc");
let ar = format!("{tool_prefix}-ar");
let c_flags = std_c_target_flags(target_name).join(" ");
env.insert(format!("CC_{target_env}"), cc.clone());
env.insert(format!("AR_{target_env}"), ar);
if !c_flags.is_empty() {
env.insert(format!("CFLAGS_{target_env}"), c_flags.clone());
env.insert(format!("CXXFLAGS_{target_env}"), c_flags.clone());
}
if let Some(sysroot) = musl_toolchain_sysroot(&cc) {
let mut bindgen_args = vec![
format!("--target={tool_prefix}"),
format!("--sysroot={sysroot}"),
];
bindgen_args.extend(
std_c_target_flags(target_name)
.into_iter()
.map(str::to_string),
);
env.insert(
format!("BINDGEN_EXTRA_CLANG_ARGS_{target_env}"),
bindgen_args.join(" "),
);
}
env
}
pub(super) fn std_c_target_flags(target_name: &str) -> Vec<&'static str> {
if target_name.starts_with("x86_64-") {
vec![
"-mno-mmx",
"-mno-sse",
"-mno-sse2",
"-mno-sse3",
"-mno-ssse3",
"-mno-sse4.1",
"-mno-sse4.2",
"-mno-avx",
"-mno-avx2",
"-msoft-float",
]
} else if target_name.starts_with("aarch64-") {
vec!["-mgeneral-regs-only"]
} else if target_name.starts_with("riscv64") {
vec!["-march=rv64gc", "-mabi=lp64d", "-mcmodel=medany"]
} else if target_name.starts_with("loongarch64-") {
vec!["-mabi=lp64s", "-msoft-float"]
} else {
Vec::new()
}
}
pub(super) fn musl_toolchain_sysroot(cc: &str) -> Option<String> {
let output = std::process::Command::new(cc)
.arg("-print-sysroot")
.output()
.ok()?;
if !output.status.success() {
return None;
}
let sysroot = String::from_utf8(output.stdout).ok()?;
let sysroot = sysroot.trim();
(!sysroot.is_empty()).then(|| sysroot.to_string())
}
pub(super) fn std_target_json_path(target: &str, plat_dyn: bool) -> PathBuf {
let path = Path::new(TARGET_JSON_ROOT).join(STD_TARGET_DIR);
if plat_dyn {
path.join(PIE_TARGET_DIR).join(format!("{target}.json"))
} else {
path.join(format!("{target}.json"))
}
}
pub(crate) fn prepare_std_build_env(
envs: &mut HashMap<String, String>,
target: &str,
plat_dyn: bool,
metadata: &Metadata,
) -> anyhow::Result<()> {
prepare_std_build_env_for_package(
envs,
AXSTD_STD_PACKAGE,
target,
plat_dyn,
&[],
metadata,
&[],
)
}
pub(super) fn prepare_std_build_env_for_package(
envs: &mut HashMap<String, String>,
package: &str,
target: &str,
plat_dyn: bool,
features: &[String],
metadata: &Metadata,
axconfig_overrides: &[String],
) -> anyhow::Result<()> {
envs.insert("AX_TARGET".to_string(), target.to_string());
if plat_dyn {
return Ok(());
}
let platform_config = resolve_platform_config(package, target, features, metadata)?;
let out_config = generated_axconfig_path(package, target)?;
generate_axconfig(
&crate::context::workspace_root_path()?,
target,
&platform_config.name,
&platform_config.config_path,
&out_config,
envs.get("SMP")
.map(|smp| {
smp.parse()
.with_context(|| format!("invalid SMP value `{smp}`"))
})
.transpose()?,
axconfig_overrides,
)?;
envs.insert(
"AX_CONFIG_PATH".to_string(),
out_config.display().to_string(),
);
envs.insert("AX_PLATFORM".to_string(), platform_config.name);
Ok(())
}
pub(super) fn pass_std_build_nested_features(
_envs: &mut HashMap<String, String>,
features: &mut Vec<String>,
app_features: &[String],
axstd_features: &[String],
) {
let mut cargo_features = Vec::new();
for feature in features.drain(..) {
let feature = normalize_std_feature(&feature);
if matches!(feature.as_str(), "ax-std" | "ax-feat") {
continue;
}
if is_log_level_feature(&feature) {
continue;
}
if is_axstd_std_check_feature(&feature) {
if std_feature_stays_on_app(&feature, app_features) {
cargo_features.push(feature.clone());
}
let axstd_feature = axstd_feature_name(&feature);
if axstd_feature_is_available(axstd_feature, axstd_features) {
cargo_features.push(format!("ax-std/{axstd_feature}"));
} else if feature.contains('/') {
cargo_features.push(feature);
}
} else {
cargo_features.push(feature);
}
}
if axstd_feature_is_available("std-compat", axstd_features) {
cargo_features.push("ax-std/std-compat".to_string());
}
cargo_features.sort();
cargo_features.dedup();
*features = cargo_features;
}
pub(super) fn inject_arceos_feature_for_std_build(
features: &mut Vec<String>,
app_features: &[String],
) {
if app_features.iter().any(|feature| feature == "arceos")
&& !features.iter().any(|feature| feature == "arceos")
{
features.push("arceos".to_string());
}
}
pub(super) fn axstd_feature_name(feature: &str) -> &str {
feature
.strip_prefix("ax-hal/")
.or_else(|| feature.strip_prefix("ax-driver/"))
.unwrap_or(feature)
}
pub(super) fn package_feature_names(
package: &str,
metadata: &Metadata,
) -> anyhow::Result<Vec<String>> {
Ok(workspace_package(metadata, package)?
.features
.keys()
.cloned()
.collect())
}
pub(super) fn axstd_feature_is_available(feature: &str, axstd_features: &[String]) -> bool {
axstd_features
.iter()
.any(|axstd_feature| axstd_feature == feature)
}
pub(super) fn std_cargo_config_path(
target: &str,
linker: &Path,
plat_dyn: bool,
extra_rustflags: &[String],
) -> anyhow::Result<PathBuf> {
let mode = std_link_mode_suffix(plat_dyn);
let path = std_build_dir()?.join(format!("config-{target}-{mode}.toml"));
let rustflags = std_rustflags_toml(extra_rustflags);
write_if_changed(
&path,
&format!(
r#"[unstable]
build-std = ["std", "panic_abort"]
build-std-features = []
[profile.release]
lto = false
panic = "abort"
[target.{target}]
linker = "{}"
rustflags = [
{}
]
"#,
toml_escape_string(&linker.display().to_string()),
rustflags,
),
)?;
Ok(path)
}
pub(super) fn std_link_mode_suffix(plat_dyn: bool) -> &'static str {
if plat_dyn { "dynamic" } else { "static" }
}
pub(super) fn std_rustflags_toml(extra_rustflags: &[String]) -> String {
extra_rustflags
.iter()
.map(|flag| format!(" {},", toml::Value::String(flag.clone())))
.collect::<Vec<_>>()
.join("\n")
}
pub(super) fn std_fake_lib_dir(target: &str) -> anyhow::Result<PathBuf> {
let dir = axbuild_tmp_dir(&crate::context::workspace_root_path()?)
.join("std-libs")
.join(target)
.join("release");
fs::create_dir_all(&dir)
.with_context(|| format!("failed to create std fake lib dir {}", dir.display()))?;
Ok(dir)
}
pub(super) fn std_linker_wrapper_path(
target: &str,
fake_lib_dir: &Path,
plat_dyn: bool,
) -> anyhow::Result<PathBuf> {
let mode = std_link_mode_suffix(plat_dyn);
let path = std_build_dir()?.join(format!("linker-{target}-{mode}.sh"));
write_if_changed(
&path,
&std_linker_wrapper_script(target, fake_lib_dir, plat_dyn)?,
)?;
set_executable(&path)?;
Ok(path)
}
pub(super) fn std_fake_lib_prebuild_script_path(
target_name: &str,
fake_lib_dir: &Path,
envs: &HashMap<String, String>,
) -> anyhow::Result<PathBuf> {
let contents = std_fake_lib_prebuild_script(target_name, fake_lib_dir, envs);
let hash = short_content_hash(&contents);
let path = std_build_dir()?
.join("prebuild")
.join(format!("prebuild-{target_name}-{hash}.sh"));
write_if_changed(&path, &contents)?;
set_executable(&path)?;
Ok(path)
}
pub(super) fn std_fake_lib_prebuild_script(
target_name: &str,
fake_lib_dir: &Path,
envs: &HashMap<String, String>,
) -> String {
let mut env_exports = Vec::new();
let mut sorted_envs: Vec<_> = envs.iter().collect();
sorted_envs.sort_by_key(|(key, _)| *key);
for (key, value) in sorted_envs {
if key
.chars()
.all(|ch| ch == '_' || ch.is_ascii_uppercase() || ch.is_ascii_digit())
{
env_exports.push(format!("export {key}={}", shell_single_quote(value)));
}
}
format!(
r#"#!/usr/bin/env bash
set -euo pipefail
{}
target_name={}
fake_dir={}
archive_tool() {{
if command -v rust-ar >/dev/null 2>&1; then
command -v rust-ar
return
fi
local sysroot_llvm_ar
sysroot_llvm_ar="$(rustc --print sysroot)/lib/rustlib/$(rustc -vV | sed -n 's/^host: //p')/bin/llvm-ar"
if [[ -x "$sysroot_llvm_ar" ]]; then
printf '%s\n' "$sysroot_llvm_ar"
return
fi
if command -v llvm-ar >/dev/null 2>&1; then
command -v llvm-ar
return
fi
if command -v ar >/dev/null 2>&1; then
command -v ar
return
fi
echo "failed to find archive tool; tried rust-ar, rust toolchain llvm-ar, llvm-ar, ar" >&2
exit 127
}}
create_empty_archive() {{
local archive="$1"
rm -f "$archive"
"$(archive_tool)" crs "$archive"
}}
mkdir -p "$fake_dir"
create_empty_archive "$fake_dir/libc.a"
create_empty_archive "$fake_dir/libunwind.a"
"#,
env_exports.join("\n"),
shell_single_quote(target_name),
shell_single_quote(&fake_lib_dir.display().to_string()),
)
}
pub(super) fn std_linker_wrapper_script(
target: &str,
fake_lib_dir: &Path,
plat_dyn: bool,
) -> anyhow::Result<String> {
let machine = lld_machine_for_std_target(target)?;
let dynamic_platform = if plat_dyn { "1" } else { "0" };
Ok(format!(
r#"#!/usr/bin/env bash
set -euo pipefail
fake_dir={}
target_name={}
lld_args=("-m" "{}")
link_search_dirs=()
archive_args=()
dynamic_platform={}
add_link_search_dir() {{
local dir="$1"
[[ -n "$dir" ]] && link_search_dirs+=("$dir")
}}
append_lld_arg() {{
local arg="$1"
case "$arg" in
*.a|*.rlib)
archive_args+=("$arg")
;;
*)
flush_archive_group
lld_args+=("$arg")
;;
esac
}}
flush_archive_group() {{
if (( ${{#archive_args[@]}} == 0 )); then
return
fi
lld_args+=("--start-group" "${{archive_args[@]}}" "--end-group")
archive_args=()
}}
find_linker_script() {{
local dir
for dir in "${{link_search_dirs[@]}}"; do
if [[ -f "$dir/linker.x" ]]; then
printf '%s\n' "$dir/linker.x"
return
fi
done
}}
add_arg() {{
local arg="$1"
if [[ "${{expect_link_search_dir:-0}}" == "1" ]]; then
add_link_search_dir "$arg"
append_lld_arg "$arg"
expect_link_search_dir=0
return
fi
if [[ "${{skip_next_lld_driver_arg:-0}}" == "1" ]]; then
skip_next_lld_driver_arg=0
return
fi
case "$arg" in
*/rcrt1.o|rcrt1.o|*/crt1.o|crt1.o|*/Scrt1.o|Scrt1.o|*/crti.o|crti.o|*/crtn.o|crtn.o|*/crtbegin*.o|crtbegin*.o|*/crtend*.o|crtend*.o)
return
;;
-L)
append_lld_arg "$arg"
expect_link_search_dir=1
return
;;
-L*)
add_link_search_dir "${{arg#-L}}"
append_lld_arg "$arg"
return
;;
-flavor)
skip_next_lld_driver_arg=1
return
;;
-flavor=*|-T*)
return
;;
-lc)
append_lld_arg "$fake_dir/libc.a"
return
;;
-lgcc_s|-lgcc)
return
;;
-lunwind)
if [[ -f "$fake_dir/libunwind.a" ]]; then
append_lld_arg "$fake_dir/libunwind.a"
fi
return
;;
-static-pie)
if [[ "$dynamic_platform" == "1" ]]; then
append_lld_arg "-pie"
else
append_lld_arg "-static"
append_lld_arg "-no-pie"
fi
return
;;
-static)
if [[ "$dynamic_platform" == "0" ]]; then
append_lld_arg "-static"
fi
return
;;
-pie)
if [[ "$dynamic_platform" == "1" ]]; then
append_lld_arg "-pie"
fi
return
;;
-no-pie)
if [[ "$dynamic_platform" == "0" ]]; then
append_lld_arg "-no-pie"
fi
return
;;
-nostartfiles|-nodefaultlibs|-nostdlib|-m*)
return
;;
--eh-frame-hdr|-z|relro|norelro|now|noexecstack)
return
;;
-Wl,*)
local IFS=,
local parts=(${{arg#-Wl,}})
local part
for part in "${{parts[@]}}"; do
[[ -n "$part" ]] && add_arg "$part"
done
return
;;
esac
append_lld_arg "$arg"
}}
for arg in "$@"; do
add_arg "$arg"
done
linker_script="$(find_linker_script)"
if [[ -z "$linker_script" ]]; then
echo "failed to find linker.x in current linker search dirs for $target_name" >&2
exit 1
fi
flush_archive_group
lld_args+=("-L$fake_dir" "-T$linker_script")
exec rust-lld -flavor gnu "${{lld_args[@]}}"
"#,
shell_single_quote(&fake_lib_dir.display().to_string()),
shell_single_quote(target),
machine,
dynamic_platform,
))
}
pub(super) fn lld_machine_for_std_target(target: &str) -> anyhow::Result<&'static str> {
if target.starts_with("x86_64-") {
Ok("elf_x86_64")
} else if target.starts_with("aarch64-") {
Ok("aarch64elf")
} else if target.starts_with("riscv64") {
Ok("elf64lriscv")
} else if target.starts_with("loongarch64-") {
Ok("elf64loongarch")
} else {
bail!("unsupported ArceOS std linker target `{target}`")
}
}
pub(super) fn toml_escape_string(value: &str) -> String {
value.replace('\\', "\\\\").replace('"', "\\\"")
}
pub(super) fn shell_single_quote(value: &str) -> String {
format!("'{}'", value.replace('\'', "'\\''"))
}
pub(super) fn short_content_hash(contents: &str) -> String {
let digest = Sha256::digest(contents.as_bytes());
format!("{digest:x}").chars().take(16).collect()
}
pub(super) fn set_executable(path: &Path) -> anyhow::Result<()> {
#[cfg(unix)]
{
let mut permissions = fs::metadata(path)?.permissions();
permissions.set_mode(0o755);
fs::set_permissions(path, permissions)?;
}
Ok(())
}
pub(super) fn std_build_dir() -> anyhow::Result<PathBuf> {
let dir = axbuild_tmp_dir(&crate::context::workspace_root_path()?).join("std");
fs::create_dir_all(&dir)
.with_context(|| format!("failed to create std build dir {}", dir.display()))?;
Ok(dir)
}
pub(super) fn write_if_changed(path: &Path, contents: &str) -> anyhow::Result<()> {
if fs::read_to_string(path).is_ok_and(|existing| existing == contents) {
return Ok(());
}
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)
.with_context(|| format!("failed to create parent dir {}", parent.display()))?;
}
let tmp_path = temporary_sibling_path(path);
fs::write(&tmp_path, contents)
.with_context(|| format!("failed to write {}", tmp_path.display()))?;
if let Err(err) = fs::rename(&tmp_path, path) {
let _ = fs::remove_file(path);
fs::rename(&tmp_path, path).with_context(|| {
format!(
"failed to replace {} with {} after initial rename error: {err}",
path.display(),
tmp_path.display()
)
})?;
}
Ok(())
}
pub(super) fn temporary_sibling_path(path: &Path) -> PathBuf {
let file_name = path
.file_name()
.map(|name| name.to_string_lossy())
.unwrap_or_else(|| "axbuild".into());
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|duration| duration.as_nanos())
.unwrap_or_default();
path.with_file_name(format!(".{file_name}.{}.{}.tmp", std::process::id(), nanos))
}