cpu-local 0.2.0

Architecture CPU-local register capability for TGOSKits
Documentation
use std::{fs, path::Path};

const LINUX_CURRENT_IMAGES: &[&str] = &[
    "os/StarryOS/kernel/Cargo.toml",
    "os/StarryOS/starryos/Cargo.toml",
    "os/StarryOS/lkm/hello/Cargo.toml",
    "os/StarryOS/lkm/kprobe_test/Cargo.toml",
];

const UNIKERNEL_TLS_IMAGES: &[&str] = &["os/axvisor/Cargo.toml"];
const REGISTER_MODE_NEUTRAL_LIBRARIES: &[&str] = &["virtualization/axvm/Cargo.toml"];

const ARCEOS_UNIKERNEL_DEFAULT_CONSUMERS: &[&str] = &[
    "apps/arceos/arce_agent/Cargo.toml",
    "apps/arceos/helloworld/Cargo.toml",
    "apps/arceos/httpclient/Cargo.toml",
    "apps/arceos/httpserver/Cargo.toml",
    "apps/arceos/io_test/Cargo.toml",
    "apps/arceos/shell/Cargo.toml",
    "apps/arceos/thread_test/Cargo.toml",
    "apps/arceos/tokio_test/Cargo.toml",
    "test-suit/arceos/axtest/sg2002-usb-msc/Cargo.toml",
    "test-suit/arceos/rust/Cargo.toml",
];

#[test]
fn workspace_dependency_does_not_impose_an_image_register_mode() {
    let workspace = workspace_root();
    let manifest = read_manifest(&workspace, "Cargo.toml");
    let dependency = inline_dependencies(&manifest, "ax-std")
        .into_iter()
        .next()
        .expect("workspace must declare ax-std");

    assert!(dependency.contains("default-features = false"));
}

#[test]
fn arceos_unikernels_explicitly_retain_the_compatibility_default() {
    let workspace = workspace_root();

    for relative_path in ARCEOS_UNIKERNEL_DEFAULT_CONSUMERS {
        let manifest = read_manifest(&workspace, relative_path);
        let dependencies = inline_dependencies(&manifest, "ax-std");
        assert!(
            !dependencies.is_empty(),
            "{relative_path} must declare ax-std"
        );
        for dependency in dependencies {
            assert!(
                dependency.contains("\"default\""),
                "{relative_path} must explicitly select ax-std/default"
            );
        }
    }
}

#[test]
fn linux_current_images_do_not_select_unikernel_tls_defaults() {
    let workspace = workspace_root();

    for relative_path in LINUX_CURRENT_IMAGES {
        let manifest = read_manifest(&workspace, relative_path);
        let dependencies = inline_dependencies(&manifest, "ax-std");
        assert!(
            !dependencies.is_empty(),
            "{relative_path} must declare ax-std"
        );

        for dependency in dependencies {
            assert!(
                dependency.contains("default-features = false")
                    && !dependency.contains("\"default\"")
                    && !dependency.contains("\"tls\""),
                "{relative_path} must leave ax-std/default and tls disabled"
            );
        }
    }
}

#[test]
fn axvisor_explicitly_selects_unikernel_tls_without_defaults() {
    let workspace = workspace_root();

    for relative_path in UNIKERNEL_TLS_IMAGES {
        let manifest = read_manifest(&workspace, relative_path);
        let dependencies = inline_dependencies(&manifest, "ax-std");
        assert!(
            !dependencies.is_empty(),
            "{relative_path} must declare ax-std"
        );

        for dependency in dependencies {
            assert!(
                dependency.contains("default-features = false")
                    && !dependency.contains("\"default\"")
                    && dependency.contains("\"tls\""),
                "{relative_path} must explicitly select ax-std/tls"
            );
        }
    }
}

#[test]
fn reusable_virtualization_libraries_do_not_select_the_host_register_mode() {
    let workspace = workspace_root();

    for relative_path in REGISTER_MODE_NEUTRAL_LIBRARIES {
        let manifest = read_manifest(&workspace, relative_path);
        for dependency in inline_dependencies(&manifest, "ax-std") {
            assert!(
                dependency.contains("default-features = false")
                    && !dependency.contains("\"default\"")
                    && !dependency.contains("\"tls\""),
                "{relative_path} must leave TLS selection to the final image"
            );
        }
        for dependency in inline_dependencies(&manifest, "ax-percpu") {
            assert!(!dependency.contains("arm-el2"));
        }
    }
}

fn workspace_root() -> std::path::PathBuf {
    Path::new(env!("CARGO_MANIFEST_DIR"))
        .ancestors()
        .nth(2)
        .expect("cpu-local must be nested under the workspace components directory")
        .to_path_buf()
}

fn read_manifest(workspace: &Path, relative_path: &str) -> String {
    fs::read_to_string(workspace.join(relative_path))
        .unwrap_or_else(|error| panic!("failed to read {relative_path}: {error}"))
}

fn inline_dependencies<'manifest>(
    manifest: &'manifest str,
    dependency: &str,
) -> Vec<&'manifest str> {
    let declaration = format!("{dependency} = {{");
    let mut remaining = manifest;
    let mut dependencies = Vec::new();

    while let Some(start) = remaining.find(&declaration) {
        let dependency = &remaining[start..];
        let end = dependency
            .find('}')
            .expect("inline Cargo dependency must have a closing brace");
        dependencies.push(&dependency[..=end]);
        remaining = &dependency[end + 1..];
    }

    dependencies
}