axbuild 0.4.19

An OS build lib toolkit used by arceos
Documentation
use std::fs;

use ostool::build::config::LogLevel;

use super::{
    features::{
        c_compiler_features, c_config_features, c_defines, dynamic_pie_for_c_app,
        map_c_app_features,
    },
    flags::{CFlagsInput, cflags, pthread_mutex_header_contents},
    libc::{PIC_RUSTFLAG, append_pic_rustflag},
    link::{find_final_linker_script, find_link_scripts, find_linker_search_dirs},
};
use crate::build::ARCEOS_LINKER_SCRIPT;

fn strings(items: &[&str]) -> Vec<String> {
    items.iter().map(|item| item.to_string()).collect()
}

#[test]
fn c_config_features_skips_nested_cargo_only_features() {
    let features = c_config_features(&strings(&[
        "ax-libc/net",
        "ax-runtime/paging",
        "ax-driver/virtio-net",
        "ax-hal/custom-board",
        "some-crate/feature",
    ]));

    assert_eq!(
        features.into_iter().collect::<Vec<_>>(),
        vec!["net".to_string()]
    );
}

#[test]
fn c_config_features_ignore_removed_dynamic_platform_feature() {
    let features = c_config_features(&strings(&["plat-dyn", "multitask"]));

    assert!(features.contains("multitask"));
    assert!(!features.contains("plat-dyn"));
    assert!(!features.contains("smp"));
}

#[test]
fn c_config_features_skips_case_define_features() {
    let features = c_config_features(&strings(&["alloc", "c-define:ARCEOS_C_TEST_CASE_MEM"]));

    assert_eq!(
        features.into_iter().collect::<Vec<_>>(),
        vec!["alloc".to_string()]
    );
}

#[test]
fn c_defines_extracts_case_define_features() {
    let defines = c_defines(&strings(&[
        "alloc",
        "c-define:ARCEOS_C_TEST_CASE_MEM",
        "c-define:ARCEOS_C_TEST_CASE_NET_HTTP",
    ]));

    assert_eq!(
        defines.into_iter().collect::<Vec<_>>(),
        vec![
            "ARCEOS_C_TEST_CASE_MEM".to_string(),
            "ARCEOS_C_TEST_CASE_NET_HTTP".to_string()
        ]
    );
}

#[test]
fn c_compiler_features_keep_case_defines_for_cflags() {
    let features = c_compiler_features(
        &strings(&["alloc"]),
        &strings(&["c-define:ARCEOS_C_TEST_CASE_MEM"]),
    );
    let flags = cflags(CFlagsInput {
        workspace_root: std::path::Path::new("/workspace"),
        arch: "x86_64",
        mode: "release",
        generated_include_dir: std::path::Path::new("/generated"),
        include_dir: std::path::Path::new("/include"),
        features: &features,
        log: Some(LogLevel::Info),
        dynamic_pie: false,
    });

    assert!(flags.contains(&"-DAX_CONFIG_ALLOC".to_string()));
    assert!(flags.contains(&"-DARCEOS_C_TEST_CASE_MEM=1".to_string()));
}

#[test]
fn map_c_app_features_preserves_driver_features() {
    let removed_static_driver_feature = concat!("ax-driver/", "plat", "-static");
    let features = map_c_app_features(
        &strings(&["net", removed_static_driver_feature, "ax-driver/virtio-net"]),
        &[],
    );

    assert!(features.contains(&"net".to_string()));
    assert!(features.contains(&"fd".to_string()));
    assert!(!features.contains(&removed_static_driver_feature.to_string()));
    assert!(features.contains(&"ax-driver/virtio-net".to_string()));
}

#[test]
fn map_c_app_features_does_not_forward_case_define_features_to_cargo() {
    let features = map_c_app_features(&strings(&["alloc", "c-define:ARCEOS_C_TEST_CASE_MEM"]), &[]);

    assert_eq!(features, vec!["alloc".to_string()]);
}

#[test]
fn map_c_app_features_ignores_removed_dynamic_platform_feature() {
    let features = map_c_app_features(&strings(&["alloc"]), &strings(&["plat-dyn"]));

    assert!(features.contains(&"alloc".to_string()));
    assert!(!features.contains(&"plat-dyn".to_string()));
    assert!(!features.contains(&"smp".to_string()));
}

#[test]
fn c_apps_always_use_pie() {
    assert!(dynamic_pie_for_c_app(&[]));
    assert!(dynamic_pie_for_c_app(&strings(&["plat-dyn"])));
    assert!(dynamic_pie_for_c_app(&strings(&["ax-std/plat-dyn"])));
    assert!(dynamic_pie_for_c_app(&strings(&["smp"])));
}

#[test]
fn pic_rustflag_is_appended_to_axlibc_cargo_env() {
    let mut env = std::collections::HashMap::new();
    append_pic_rustflag(&mut env);
    assert_eq!(
        env.get("CARGO_ENCODED_RUSTFLAGS"),
        Some(&PIC_RUSTFLAG.to_string())
    );

    let mut env = std::collections::HashMap::from([(
        "CARGO_ENCODED_RUSTFLAGS".to_string(),
        "-Cforce-frame-pointers=yes".to_string(),
    )]);
    append_pic_rustflag(&mut env);
    assert_eq!(
        env.get("CARGO_ENCODED_RUSTFLAGS"),
        Some(&format!("-Cforce-frame-pointers=yes\x1f{PIC_RUSTFLAG}"))
    );

    let mut env = std::collections::HashMap::from([(
        "RUSTFLAGS".to_string(),
        "-Cforce-frame-pointers=yes".to_string(),
    )]);
    append_pic_rustflag(&mut env);
    assert_eq!(
        env.get("RUSTFLAGS"),
        Some(&format!("-Cforce-frame-pointers=yes {PIC_RUSTFLAG}"))
    );
}

#[test]
fn map_c_app_features_forwards_multitask_to_runtime_features() {
    let features = map_c_app_features(&strings(&["multitask"]), &[]);

    assert!(features.contains(&"multitask".to_string()));
}

#[test]
fn map_c_app_features_preserves_paging_facade_feature() {
    let features = map_c_app_features(&strings(&["paging"]), &[]);

    assert_eq!(features, vec!["paging".to_string()]);
}

#[test]
fn pthread_mutex_header_matches_lockdep_smp_layout() {
    let header = pthread_mutex_header_contents(&strings(&["multitask", "lockdep", "smp"]));

    assert!(header.contains("long __l[10];"));
    assert!(header.contains("{-1, 0, 0, 0, 0, 0, 0, 0, 0, 0}"));
}

#[test]
fn pthread_mutex_header_matches_plain_smp_layout() {
    let header = pthread_mutex_header_contents(&strings(&["multitask", "smp"]));

    assert!(header.contains("long __l[6];"));
    assert!(header.contains("{0, 0, 8, 0, 0, 0}"));
}

#[test]
fn pthread_mutex_header_ignores_removed_dynamic_platform_feature() {
    let header = pthread_mutex_header_contents(&strings(&["multitask", "plat-dyn"]));

    assert!(header.contains("long __l[5];"));
    assert!(header.contains("{0, 8, 0, 0, 0}"));
}

#[test]
fn final_linker_script_comes_from_axruntime_build_out_dir() {
    let root = tempfile::tempdir().unwrap();
    let target_dir = root.path().join("target");
    let target = "x86_64-unknown-none";
    let mode = "release";
    let stable_dir = target_dir.join(target).join(mode);
    let out_dir = stable_dir.join("build/ax-runtime-abc/out");
    fs::create_dir_all(&out_dir).unwrap();
    fs::create_dir_all(&stable_dir).unwrap();
    fs::write(stable_dir.join(ARCEOS_LINKER_SCRIPT), "stable").unwrap();
    fs::write(out_dir.join(ARCEOS_LINKER_SCRIPT), "runtime").unwrap();

    let linker = find_final_linker_script(&target_dir, target, mode).unwrap();

    assert_eq!(linker, out_dir.join(ARCEOS_LINKER_SCRIPT));
}

#[test]
fn linker_search_dirs_use_current_platform_script_owner() {
    let root = tempfile::tempdir().unwrap();
    let target_dir = root.path().join("target");
    let target = "loongarch64-unknown-none-softfloat";
    let mode = "release";
    let build_dir = target_dir.join(target).join(mode).join("build");
    let runtime_out = build_dir.join("ax-runtime-def/out");
    let axplat_out = build_dir.join("axplat-dyn-def/out");
    let somehal_out = build_dir.join("somehal-ghi/out");
    let someboot_out = build_dir.join("someboot-jkl/out");
    fs::create_dir_all(&runtime_out).unwrap();
    fs::create_dir_all(&axplat_out).unwrap();
    fs::create_dir_all(&somehal_out).unwrap();
    fs::create_dir_all(&someboot_out).unwrap();
    fs::write(runtime_out.join(ARCEOS_LINKER_SCRIPT), "").unwrap();
    fs::write(axplat_out.join("axplat.x"), "").unwrap();
    fs::write(somehal_out.join("link.x"), "").unwrap();
    fs::write(someboot_out.join("someboot.x"), "").unwrap();

    let link_scripts = find_link_scripts(
        &target_dir,
        target,
        mode,
        "plat-dyn",
        &strings(&["plat-dyn"]),
    )
    .unwrap();

    assert_eq!(link_scripts.script, runtime_out.join(ARCEOS_LINKER_SCRIPT));
    assert!(link_scripts.pie);
    assert!(link_scripts.search_dirs.contains(&runtime_out));
    assert!(link_scripts.search_dirs.contains(&axplat_out));
    assert!(link_scripts.search_dirs.contains(&somehal_out));
    assert!(link_scripts.search_dirs.contains(&someboot_out));
}

#[test]
fn linker_search_dirs_use_axplat_dyn_for_generic_dynamic_platforms() {
    let root = tempfile::tempdir().unwrap();
    let target_dir = root.path().join("target");
    let target = "riscv64gc-unknown-none-elf";
    let mode = "release";
    let build_dir = target_dir.join(target).join(mode).join("build");
    let axplat_out = build_dir.join("axplat-dyn-abc/out");
    let runtime_out = build_dir.join("ax-runtime-def/out");
    fs::create_dir_all(&axplat_out).unwrap();
    fs::create_dir_all(&runtime_out).unwrap();
    fs::write(axplat_out.join("axplat.x"), "").unwrap();
    fs::write(runtime_out.join(ARCEOS_LINKER_SCRIPT), "").unwrap();

    let dirs = find_linker_search_dirs(&target_dir, target, mode, "riscv64-generic", &[]).unwrap();

    assert_eq!(dirs, vec![runtime_out, axplat_out]);
}

#[test]
fn dynamic_link_scripts_use_runtime_script_as_entrypoint() {
    let root = tempfile::tempdir().unwrap();
    let target_dir = root.path().join("target");
    let target = "aarch64-unknown-none-softfloat";
    let mode = "release";
    let build_dir = target_dir.join(target).join(mode).join("build");
    let runtime_out = build_dir.join("ax-runtime-abc/out");
    let axplat_out = build_dir.join("axplat-dyn-def/out");
    let somehal_out = build_dir.join("somehal-ghi/out");
    let someboot_out = build_dir.join("someboot-jkl/out");
    fs::create_dir_all(&runtime_out).unwrap();
    fs::create_dir_all(&axplat_out).unwrap();
    fs::create_dir_all(&somehal_out).unwrap();
    fs::create_dir_all(&someboot_out).unwrap();
    fs::write(runtime_out.join(ARCEOS_LINKER_SCRIPT), "").unwrap();
    fs::write(axplat_out.join("axplat.x"), "").unwrap();
    fs::write(somehal_out.join("link.x"), "").unwrap();
    fs::write(someboot_out.join("someboot.x"), "").unwrap();

    let link_scripts = find_link_scripts(
        &target_dir,
        target,
        mode,
        "aarch64-generic",
        &strings(&["plat-dyn"]),
    )
    .unwrap();

    assert_eq!(link_scripts.script, runtime_out.join(ARCEOS_LINKER_SCRIPT));
    assert!(link_scripts.pie);
    assert!(link_scripts.search_dirs.contains(&runtime_out));
    assert!(link_scripts.search_dirs.contains(&axplat_out));
    assert!(link_scripts.search_dirs.contains(&somehal_out));
    assert!(link_scripts.search_dirs.contains(&someboot_out));
}