axbuild 0.4.6

An OS build lib toolkit used by arceos
use anyhow::anyhow;

use super::{
    DEFAULT_ARCEOS_ARCH, DEFAULT_ARCEOS_TARGET, DEFAULT_AXVISOR_ARCH, DEFAULT_AXVISOR_TARGET,
    DEFAULT_STARRY_ARCH, DEFAULT_STARRY_TARGET,
};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct CrossCompileSpec {
    pub(crate) llvm_target: &'static str,
    pub(crate) cmake_system_processor: &'static str,
    pub(crate) guest_tool_dir: &'static str,
    pub(crate) gnu_tool_prefix: &'static str,
    pub(crate) qemu_user_binaries: &'static [&'static str],
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct ArchSpec {
    pub(crate) arch: &'static str,
    pub(crate) target: &'static str,
    pub(crate) default_rootfs_image: &'static str,
    pub(crate) starry_default_platform: &'static str,
    pub(crate) cross_compile: CrossCompileSpec,
}

const ARCH_SPECS: &[ArchSpec] = &[
    ArchSpec {
        arch: "aarch64",
        target: "aarch64-unknown-none-softfloat",
        default_rootfs_image: "rootfs-aarch64-alpine.img",
        starry_default_platform: "aarch64-qemu-virt",
        cross_compile: CrossCompileSpec {
            llvm_target: "aarch64-linux-musl",
            cmake_system_processor: "aarch64",
            guest_tool_dir: "usr/aarch64-alpine-linux-musl/bin",
            gnu_tool_prefix: "aarch64-linux-musl",
            qemu_user_binaries: &["qemu-aarch64-static", "qemu-aarch64"],
        },
    },
    ArchSpec {
        arch: "x86_64",
        target: "x86_64-unknown-none",
        default_rootfs_image: "rootfs-x86_64-alpine.img",
        starry_default_platform: "x86-pc",
        cross_compile: CrossCompileSpec {
            llvm_target: "x86_64-linux-musl",
            cmake_system_processor: "x86_64",
            guest_tool_dir: "usr/x86_64-alpine-linux-musl/bin",
            gnu_tool_prefix: "x86_64-linux-musl",
            qemu_user_binaries: &["qemu-x86_64-static", "qemu-x86_64"],
        },
    },
    ArchSpec {
        arch: "riscv64",
        target: "riscv64gc-unknown-none-elf",
        default_rootfs_image: "rootfs-riscv64-alpine.img",
        starry_default_platform: "riscv64-qemu-virt",
        cross_compile: CrossCompileSpec {
            llvm_target: "riscv64-linux-musl",
            cmake_system_processor: "riscv64",
            guest_tool_dir: "usr/riscv64-alpine-linux-musl/bin",
            gnu_tool_prefix: "riscv64-linux-musl",
            qemu_user_binaries: &["qemu-riscv64-static", "qemu-riscv64"],
        },
    },
    ArchSpec {
        arch: "loongarch64",
        target: "loongarch64-unknown-none-softfloat",
        default_rootfs_image: "rootfs-loongarch64-alpine.img",
        starry_default_platform: "loongarch64-qemu-virt",
        cross_compile: CrossCompileSpec {
            llvm_target: "loongarch64-linux-musl",
            cmake_system_processor: "loongarch64",
            guest_tool_dir: "usr/loongarch64-alpine-linux-musl/bin",
            gnu_tool_prefix: "loongarch64-linux-musl",
            qemu_user_binaries: &["qemu-loongarch64-static", "qemu-loongarch64"],
        },
    },
];

const SUPPORTED_ARCH_VALUES: &str = "aarch64, x86_64, riscv64, loongarch64";
const SUPPORTED_TARGET_VALUES: &str = "x86_64-unknown-none, aarch64-unknown-none-softfloat, \
                                       riscv64gc-unknown-none-elf, \
                                       loongarch64-unknown-none-softfloat";

pub(crate) fn supported_arches() -> Vec<&'static str> {
    ARCH_SPECS.iter().map(|spec| spec.arch).collect()
}

pub(crate) fn supported_targets() -> Vec<&'static str> {
    ARCH_SPECS.iter().map(|spec| spec.target).collect()
}

pub(crate) fn arch_spec(arch: &str) -> Option<&'static ArchSpec> {
    ARCH_SPECS.iter().find(|spec| spec.arch == arch)
}

pub(crate) fn arch_spec_for_target(target: &str) -> Option<&'static ArchSpec> {
    ARCH_SPECS.iter().find(|spec| spec.target == target)
}

pub(crate) fn arch_for_target(target: &str) -> Option<&'static str> {
    arch_spec_for_target(target).map(|spec| spec.arch)
}

pub(crate) fn starry_target_for_arch_checked(arch: &str) -> anyhow::Result<&'static str> {
    target_for_arch_checked_impl(arch, "Starry")
}

pub(crate) fn starry_arch_for_target_checked(target: &str) -> anyhow::Result<&'static str> {
    arch_for_target_checked_impl(target, "Starry")
}

pub(crate) fn arch_for_target_checked(target: &str) -> anyhow::Result<&'static str> {
    arch_for_target_checked_impl(target, "Starry")
}

pub(crate) fn default_rootfs_image_for_arch(arch: &str) -> Option<&'static str> {
    arch_spec(arch).map(|spec| spec.default_rootfs_image)
}

pub(crate) fn starry_default_platform_for_arch_checked(arch: &str) -> anyhow::Result<&'static str> {
    arch_spec(arch)
        .map(|spec| spec.starry_default_platform)
        .ok_or_else(|| unsupported_arch_error(arch, "Starry"))
}

pub(crate) fn cross_compile_spec_for_arch_checked(arch: &str) -> anyhow::Result<CrossCompileSpec> {
    arch_spec(arch)
        .map(|spec| spec.cross_compile)
        .ok_or_else(|| {
            anyhow!(
                "C-based QEMU test cases are only supported on {SUPPORTED_ARCH_VALUES}, but got \
                 `{arch}`"
            )
        })
}

pub(crate) fn resolve_starry_arch_and_target(
    arch: Option<String>,
    target: Option<String>,
) -> anyhow::Result<(String, String)> {
    resolve_arch_and_target(
        arch,
        target,
        DEFAULT_STARRY_ARCH,
        DEFAULT_STARRY_TARGET,
        "Starry",
    )
}

pub(crate) fn resolve_arceos_arch_and_target(
    arch: Option<String>,
    target: Option<String>,
) -> anyhow::Result<(String, String)> {
    resolve_arch_and_target(
        arch,
        target,
        DEFAULT_ARCEOS_ARCH,
        DEFAULT_ARCEOS_TARGET,
        "ArceOS",
    )
}

pub(crate) fn resolve_axvisor_arch_and_target(
    arch: Option<String>,
    target: Option<String>,
) -> anyhow::Result<(String, String)> {
    resolve_arch_and_target(
        arch,
        target,
        DEFAULT_AXVISOR_ARCH,
        DEFAULT_AXVISOR_TARGET,
        "Axvisor",
    )
}

pub(crate) fn validate_supported_target(
    target: &str,
    suite_name: &str,
    supported_kind: &str,
    supported: &[&str],
) -> anyhow::Result<()> {
    if supported.contains(&target) {
        Ok(())
    } else {
        anyhow::bail!(
            "unsupported target `{}` for {}. Supported {} are: {}",
            target,
            suite_name,
            supported_kind,
            supported.join(", ")
        )
    }
}

fn target_for_arch_checked_impl(arch: &str, component: &str) -> anyhow::Result<&'static str> {
    arch_spec(arch)
        .map(|spec| spec.target)
        .ok_or_else(|| unsupported_arch_error(arch, component))
}

fn arch_for_target_checked_impl(target: &str, component: &str) -> anyhow::Result<&'static str> {
    arch_for_target(target).ok_or_else(|| {
        anyhow!(
            "unsupported {component} target `{target}`; expected one of {SUPPORTED_TARGET_VALUES}"
        )
    })
}

fn unsupported_arch_error(arch: &str, component: &str) -> anyhow::Error {
    anyhow!(
        "unsupported {component} architecture `{arch}`; expected one of {SUPPORTED_ARCH_VALUES}"
    )
}

fn resolve_arch_and_target(
    arch: Option<String>,
    target: Option<String>,
    default_arch: &str,
    default_target: &str,
    component: &str,
) -> anyhow::Result<(String, String)> {
    match (arch, target) {
        (Some(arch), Some(target)) => {
            let expected_target = target_for_arch_checked_impl(&arch, component)?;
            if target != expected_target {
                anyhow::bail!(
                    "{component} arch `{arch}` maps to target `{expected_target}`, but got \
                     `{target}`"
                );
            }
            Ok((arch, target))
        }
        (Some(arch), None) => Ok((
            arch.clone(),
            target_for_arch_checked_impl(&arch, component)?.to_string(),
        )),
        (None, Some(target)) => Ok((
            arch_for_target_checked_impl(&target, component)?.to_string(),
            target,
        )),
        (None, None) => Ok((default_arch.to_string(), default_target.to_string())),
    }
}