axbuild 0.4.16

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

use anyhow::anyhow;

use super::board::{self, Board};
use crate::context::{
    ArceosCommandSnapshot, ArceosQemuSnapshot, ArceosUbootSnapshot, arch_for_target_checked,
    snapshot_path_value,
};

pub(crate) fn available_board_names(workspace_root: &Path) -> anyhow::Result<Vec<String>> {
    board::board_names(workspace_root)
}

fn resolve_board(workspace_root: &Path, name: &str) -> anyhow::Result<Board> {
    board::find_board(workspace_root, name)?.ok_or_else(|| {
        let available = available_board_names(workspace_root).unwrap_or_default();
        anyhow!(
            "unknown ArceOS board `{name}` in {}; available boards: {}",
            board::board_dir(workspace_root)
                .map(|path| path.display().to_string())
                .unwrap_or_else(|_| "os/arceos/configs/board".to_string()),
            available.join(", ")
        )
    })
}

fn write_board_to_default_build_config(
    workspace_root: &Path,
    board: &Board,
) -> anyhow::Result<PathBuf> {
    let build_config_path = crate::build::default_build_info_path_in_workspace(
        workspace_root,
        &board.package,
        &board.target,
    );
    if let Some(parent) = build_config_path.parent() {
        fs::create_dir_all(parent)?;
    }
    fs::copy(&board.path, &build_config_path).map_err(|e| {
        anyhow!(
            "failed to copy ArceOS board config {} to {}: {e}",
            board.path.display(),
            build_config_path.display()
        )
    })?;
    Ok(build_config_path)
}

fn update_snapshot_for_board(
    workspace_root: &Path,
    board: &Board,
    build_config_path: &Path,
) -> anyhow::Result<()> {
    let snapshot = ArceosCommandSnapshot {
        package: Some(board.package.clone()),
        arch: Some(arch_for_target_checked(&board.target)?.to_string()),
        target: Some(board.target.clone()),
        plat_dyn: Some(true),
        smp: board.build_config.build_info.max_cpu_num,
        config: Some(snapshot_path_value(workspace_root, build_config_path)),
        qemu: ArceosQemuSnapshot::default(),
        uboot: ArceosUbootSnapshot::default(),
    };
    snapshot.store(workspace_root)?;
    Ok(())
}

pub(crate) fn write_defconfig(workspace_root: &Path, board_name: &str) -> anyhow::Result<PathBuf> {
    let board = resolve_board(workspace_root, board_name)?;
    let build_config_path = write_board_to_default_build_config(workspace_root, &board)?;
    update_snapshot_for_board(workspace_root, &board, &build_config_path)?;
    Ok(build_config_path)
}

#[cfg(test)]
mod tests {
    use std::{fs, path::Path};

    use tempfile::tempdir;

    use super::*;
    use crate::context::{ArceosCommandSnapshot, ArceosQemuSnapshot, ArceosUbootSnapshot};

    fn write_workspace(root: &Path) {
        fs::create_dir_all(root.join("os/arceos")).unwrap();
        fs::write(
            root.join("Cargo.toml"),
            "[workspace]\nmembers = [\"apps/arceos/helloworld\"]\n",
        )
        .unwrap();
    }

    fn write_board(root: &Path, name: &str, body: &str) {
        let path = crate::arceos::board::board_dir(root)
            .unwrap()
            .join(format!("{name}.toml"));
        fs::create_dir_all(path.parent().unwrap()).unwrap();
        fs::write(path, body).unwrap();
    }

    #[test]
    fn write_defconfig_generates_dynamic_build_toml_and_updates_snapshot() {
        let root = tempdir().unwrap();
        write_workspace(root.path());
        write_board(
            root.path(),
            "orangepi-5-plus",
            r#"
package = "arceos-helloworld"
target = "aarch64-unknown-none-softfloat"
plat_dyn = true
features = []
log = "Info"
max_cpu_num = 1
"#,
        );
        let existing_snapshot = ArceosCommandSnapshot {
            package: Some("old-package".to_string()),
            arch: Some("riscv64".to_string()),
            target: Some("riscv64gc-unknown-none-elf".to_string()),
            plat_dyn: Some(false),
            smp: Some(4),
            config: None,
            qemu: ArceosQemuSnapshot {
                qemu_config: Some("configs/qemu.toml".into()),
            },
            uboot: ArceosUbootSnapshot {
                uboot_config: Some("configs/uboot.toml".into()),
            },
        };
        existing_snapshot.store(root.path()).unwrap();

        let path = write_defconfig(root.path(), "orangepi-5-plus").unwrap();

        assert_eq!(
            path,
            root.path().join(
                "tmp/axbuild/config/arceos-helloworld/build-aarch64-unknown-none-softfloat.toml"
            )
        );
        let written = fs::read_to_string(&path).unwrap();
        assert!(written.contains("package = \"arceos-helloworld\""));
        assert!(written.contains("target = \"aarch64-unknown-none-softfloat\""));
        assert!(written.contains("plat_dyn = true"));
        assert!(!written.contains("axconfig"));

        let snapshot = ArceosCommandSnapshot::load(root.path()).unwrap();
        assert_eq!(snapshot.package.as_deref(), Some("arceos-helloworld"));
        assert_eq!(snapshot.arch.as_deref(), Some("aarch64"));
        assert_eq!(
            snapshot.target.as_deref(),
            Some("aarch64-unknown-none-softfloat")
        );
        assert_eq!(snapshot.plat_dyn, Some(true));
        assert_eq!(snapshot.smp, Some(1));
        assert_eq!(
            snapshot.config,
            Some(
                "tmp/axbuild/config/arceos-helloworld/build-aarch64-unknown-none-softfloat.toml"
                    .into()
            )
        );
        assert_eq!(snapshot.qemu.qemu_config, None);
        assert_eq!(snapshot.uboot.uboot_config, None);
    }

    #[test]
    fn write_defconfig_reports_board_directory_for_unknown_name() {
        let root = tempdir().unwrap();
        write_workspace(root.path());
        write_board(
            root.path(),
            "orangepi-5-plus",
            r#"
package = "arceos-helloworld"
target = "aarch64-unknown-none-softfloat"
plat_dyn = true
features = []
log = "Info"
"#,
        );

        let err = write_defconfig(root.path(), "missing")
            .unwrap_err()
            .to_string();
        assert!(err.contains("unknown ArceOS board `missing`"));
        assert!(err.contains("orangepi-5-plus"));
    }
}