axbuild 0.4.15

An OS build lib toolkit used by arceos
Documentation
use std::{
    fs,
    path::{Path, PathBuf},
};

use anyhow::Context;

use super::{discovery::ensure_file_exists, types::PreparedAxvisorQemuCase};
use crate::{
    context::{ResolvedAxvisorRequest, ResolvedBuildRequest},
    test::case as test_case,
};

const ARCEOS_QEMU_GUEST_PACKAGE: &str = "ax-helloworld";
const ARCEOS_QEMU_GUEST_KERNEL_PATH: &str = "/guest/arceos/ax-helloworld-x86_64.bin";

pub(super) fn arceos_x86_64_guest_request() -> anyhow::Result<ResolvedBuildRequest> {
    let target = "x86_64-unknown-none".to_string();
    Ok(ResolvedBuildRequest {
        package: ARCEOS_QEMU_GUEST_PACKAGE.to_string(),
        arch: "x86_64".to_string(),
        target: target.clone(),
        plat_dyn: Some(false),
        smp: None,
        debug: false,
        build_info_path: crate::arceos::build::resolve_build_info_path(
            ARCEOS_QEMU_GUEST_PACKAGE,
            &target,
            None,
        )?,
        qemu_config: None,
        uboot_config: None,
    })
}

pub(super) fn arceos_x86_64_guest_elf_path(workspace_root: &Path, debug: bool) -> PathBuf {
    crate::backtrace::arceos_rust_elf_path(
        workspace_root,
        "x86_64-unknown-none",
        ARCEOS_QEMU_GUEST_PACKAGE,
        debug,
    )
}

pub(super) fn arceos_x86_64_guest_bin_path(workspace_root: &Path) -> PathBuf {
    arceos_x86_64_guest_elf_path(workspace_root, false).with_extension("bin")
}

pub(super) fn inject_arceos_x86_64_guest_image(
    workspace_root: &Path,
    request: &ResolvedAxvisorRequest,
    case: &PreparedAxvisorQemuCase,
    prepared_assets: &mut test_case::PreparedCaseAssets,
) -> anyhow::Result<()> {
    let guest_image = arceos_x86_64_guest_bin_path(workspace_root);
    ensure_file_exists(&guest_image, "ArceOS guest image")?;

    let mut temporary_overlay_run_dir = None;
    let overlay_dir = if prepared_assets.rootfs_copy_to_remove.is_none() {
        let layout = test_case::case_asset_layout(
            workspace_root,
            &request.target,
            &case.case.case.display_name,
        )?;
        fs::create_dir_all(&layout.run_dir)
            .with_context(|| format!("failed to create {}", layout.run_dir.display()))?;
        test_case::copy_shared_rootfs_for_case(&prepared_assets.rootfs_path, &layout)?;
        prepared_assets.rootfs_path = layout.case_rootfs_copy.clone();
        prepared_assets.rootfs_copy_to_remove = Some(layout.case_rootfs_copy.clone());
        prepared_assets.run_dir_to_remove = Some(layout.run_dir.clone());
        layout.overlay_dir
    } else {
        let layout = test_case::case_asset_layout(
            workspace_root,
            &request.target,
            &case.case.case.display_name,
        )?;
        fs::create_dir_all(&layout.run_dir)
            .with_context(|| format!("failed to create {}", layout.run_dir.display()))?;
        temporary_overlay_run_dir = Some(layout.run_dir);
        layout.overlay_dir
    };
    copy_guest_overlay_file(
        &guest_image,
        &overlay_dir,
        ARCEOS_QEMU_GUEST_KERNEL_PATH,
        "ArceOS guest image",
    )?;
    let result = crate::rootfs::inject::inject_overlay(&prepared_assets.rootfs_path, &overlay_dir);
    test_case::remove_case_run_dir(temporary_overlay_run_dir.as_deref());
    result
}

fn copy_guest_overlay_file(
    source: &Path,
    overlay_dir: &Path,
    guest_path: &str,
    label: &str,
) -> anyhow::Result<()> {
    let overlay_path = overlay_dir.join(guest_path.trim_start_matches('/'));
    if let Some(parent) = overlay_path.parent() {
        fs::create_dir_all(parent)
            .with_context(|| format!("failed to create {}", parent.display()))?;
    }
    fs::copy(source, &overlay_path).with_context(|| {
        format!(
            "failed to copy {label} {} to {}",
            source.display(),
            overlay_path.display()
        )
    })?;
    Ok(())
}

pub(super) fn build_group_needs_arceos_x86_64_guest(request: &ResolvedAxvisorRequest) -> bool {
    request.arch == "x86_64"
        && request.vmconfigs.iter().any(|path| {
            path.file_name()
                .and_then(|name| name.to_str())
                .is_some_and(|name| name.starts_with("arceos"))
        })
}

pub(super) fn case_needs_arceos_x86_64_guest(
    request: &ResolvedAxvisorRequest,
    case: &PreparedAxvisorQemuCase,
) -> bool {
    build_group_needs_arceos_x86_64_guest(request) || case.case.case.name.contains("arceos")
}

pub(super) fn axvisor_case_asset_config() -> test_case::CaseAssetConfig {
    test_case::CaseAssetConfig {
        grouped_runner: test_case::GroupedCaseRunnerConfig {
            runner_name: "axvisor-run-case-tests".to_string(),
            runner_path: "/usr/bin/axvisor-run-case-tests".to_string(),
            autorun_profile_script: None,
            begin_marker: "AXVISOR_GROUPED_TEST_BEGIN".to_string(),
            passed_marker: "AXVISOR_GROUPED_TEST_PASSED".to_string(),
            failed_marker: "AXVISOR_GROUPED_TEST_FAILED".to_string(),
            all_passed_marker: "AXVISOR_GROUPED_TESTS_PASSED".to_string(),
            all_failed_marker: "AXVISOR_GROUPED_TESTS_FAILED".to_string(),
            success_regex: r"(?m)^AXVISOR_GROUPED_TESTS_PASSED\s*$".to_string(),
            fail_regex: r"(?m)^AXVISOR_GROUPED_TEST_FAILED:".to_string(),
        },
        script_env: test_case::CaseScriptEnvConfig {
            staging_root: "AXVISOR_TEST_STAGING_ROOT".to_string(),
            case_dir: "AXVISOR_TEST_CASE_DIR".to_string(),
            case_c_dir: "AXVISOR_TEST_CASE_C_DIR".to_string(),
            case_work_dir: "AXVISOR_TEST_CASE_WORK_DIR".to_string(),
            case_build_dir: "AXVISOR_TEST_CASE_BUILD_DIR".to_string(),
            case_overlay_dir: "AXVISOR_TEST_CASE_OVERLAY_DIR".to_string(),
        },
        cache_env_vars: Vec::new(),
        prepare_staging_root: |_| Ok(()),
        prepare_guest_package_env: None,
    }
}