axbuild 0.4.19

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

use anyhow::{Context, anyhow};
use serde::Deserialize;

use super::build::{ArceosBuildConfig, ArceosBuildFile};

#[derive(Debug, Clone, PartialEq, Deserialize)]
pub(crate) struct ArceosBoardFile {
    pub(crate) package: String,
    pub(crate) target: String,
    #[serde(flatten)]
    pub(crate) build_config: ArceosBuildConfig,
}

#[derive(Debug, Clone, PartialEq)]
pub(crate) struct Board {
    pub(crate) name: String,
    pub(crate) path: PathBuf,
    pub(crate) package: String,
    pub(crate) target: String,
    pub(crate) build_config: ArceosBuildConfig,
}

pub(crate) fn arceos_dir(workspace_root: &Path) -> anyhow::Result<PathBuf> {
    let path = workspace_root.join("os/arceos");
    if path.exists() {
        Ok(path)
    } else {
        Err(anyhow!(
            "failed to locate ArceOS directory under {}",
            workspace_root.display()
        ))
    }
}

pub(crate) fn board_dir(workspace_root: &Path) -> anyhow::Result<PathBuf> {
    Ok(arceos_dir(workspace_root)?.join("configs/board"))
}

pub(crate) fn load_build_file(path: &Path) -> anyhow::Result<ArceosBuildFile> {
    super::build::load_arceos_build_file(path)
}

pub(crate) fn load_board_file(path: &Path) -> anyhow::Result<ArceosBoardFile> {
    let contents = fs::read_to_string(path)
        .with_context(|| format!("failed to read ArceOS board config {}", path.display()))?;
    crate::build::reject_removed_std_field(path, &contents)?;
    let board_file: ArceosBoardFile = toml::from_str(&contents)
        .with_context(|| format!("failed to parse ArceOS board config {}", path.display()))?;
    Ok(board_file)
}

pub(crate) fn board_default_list(workspace_root: &Path) -> anyhow::Result<Vec<Board>> {
    let board_dir = board_dir(workspace_root)?;
    let mut boards = Vec::new();
    for entry in fs::read_dir(&board_dir).map_err(|e| {
        anyhow!(
            "failed to read ArceOS board config directory {}: {e}",
            board_dir.display()
        )
    })? {
        let entry = entry?;
        let path = entry.path();
        if path.extension() != Some(OsStr::new("toml")) {
            continue;
        }

        let name = path
            .file_stem()
            .and_then(OsStr::to_str)
            .ok_or_else(|| anyhow!("invalid ArceOS board filename {}", path.display()))?
            .to_string();
        let board_file = load_board_file(&path)?;
        boards.push(Board {
            name,
            path,
            package: board_file.package,
            target: board_file.target,
            build_config: board_file.build_config,
        });
    }
    boards.sort_by(|left, right| left.name.cmp(&right.name));
    Ok(boards)
}

pub(crate) fn find_board(workspace_root: &Path, name: &str) -> anyhow::Result<Option<Board>> {
    Ok(board_default_list(workspace_root)?
        .into_iter()
        .find(|board| board.name == name))
}

pub(crate) fn board_names(workspace_root: &Path) -> anyhow::Result<Vec<String>> {
    Ok(board_default_list(workspace_root)?
        .into_iter()
        .map(|board| board.name)
        .collect())
}

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

    use tempfile::tempdir;

    use super::*;

    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 = board_dir(root).unwrap().join(format!("{name}.toml"));
        fs::create_dir_all(path.parent().unwrap()).unwrap();
        fs::write(path, body).unwrap();
    }

    #[test]
    fn loads_dynamic_board_names_in_filename_order() {
        let root = tempdir().unwrap();
        write_workspace(root.path());
        write_board(
            root.path(),
            "z-board",
            r#"
package = "arceos-helloworld"
target = "aarch64-unknown-none-softfloat"
features = []
log = "Info"
"#,
        );
        write_board(
            root.path(),
            "a-board",
            r#"
package = "arceos-helloworld"
target = "x86_64-unknown-none"
features = []
log = "Info"
"#,
        );

        assert_eq!(
            board_names(root.path()).unwrap(),
            vec!["a-board".to_string(), "z-board".to_string()]
        );
    }

    #[test]
    fn load_board_accepts_dynamic_platform_template_without_plat_dyn_field() {
        let root = tempdir().unwrap();
        write_workspace(root.path());
        let path = board_dir(root.path()).unwrap().join("static.toml");
        fs::create_dir_all(path.parent().unwrap()).unwrap();
        fs::write(
            &path,
            r#"
package = "arceos-helloworld"
target = "aarch64-unknown-none-softfloat"
features = []
log = "Info"
"#,
        )
        .unwrap();

        let board = load_board_file(&path).unwrap();
        assert_eq!(board.target, "aarch64-unknown-none-softfloat");
    }
}