diff --git a/.claude/skills/arch-platform-porting/SKILL.md b/.claude/skills/arch-platform-porting/SKILL.md index e69911d09f..0152b62edd 100644 --- a/.claude/skills/arch-platform-porting/SKILL.md +++ b/.claude/skills/arch-platform-porting/SKILL.md @@ -25,6 +25,12 @@ Current Axvisor LoongArch QEMU bring-up uses the dynamic UEFI platform path. The - **RISC-V per-CPU register contract**: `ax-percpu` reserves `x3`/`gp` as the per-CPU base, so every RISC-V kernel target spec must pass `--no-relax` to the linker. Do not enable global-pointer relaxation or define `__global_pointer$` unless the per-CPU register design changes at the same time. - **Build system**: wire arch/target mapping in `scripts/axbuild`, dynamic platform defaults, feature propagation, kernel format conversion, UEFI/to-bin behavior, rootfs handling, and per-OS test discovery. - **QEMU and firmware**: verify QEMU binary, machine type, CPU, SMP count, pflash/OVMF files, serial console, disk/rootfs device, `-snapshot`, debug flags, timeout, and success/fail regexes. + QEMU `uefi`, `to_bin`, acceleration, CPU feature, and device choices are part of each + `qemu-*.toml` contract; axbuild must not infer or overwrite them from the target architecture + or host `/dev/kvm` availability. + Axvisor x86_64 CI build groups select exactly one virtualization backend (`vmx` or `svm`); + the generic QEMU board remains backend-neutral while axbuild temporarily defaults an omitted + backend to `vmx`. AMD/SVM CI configurations must keep their explicit `svm` feature. - **someboot arch layer**: implement or audit entry, relocation, BSS clearing, stack setup, memory map parsing, paging, trap vectors, timer, IRQ, power, SMP, and address translation. - **CPU runtime**: update `components/axcpu/src/` for trap entry, context switch, user/kernel context, syscall return path, FP/SIMD state, and per-CPU assumptions. - **Platform bridge**: update `platforms/axplat-dyn`, `platforms/somehal`, platform config, memory regions, IRQ routing, timer source, power operations, and CPU boot operations. diff --git a/Cargo.lock b/Cargo.lock index 1088ae0bbb..01192d5fdb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7605,9 +7605,9 @@ dependencies = [ [[package]] name = "spin" -version = "0.12.0" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1527984ca054dfca79333baec451042863f485fbee01b7bf6d911de915cac865" +checksum = "8abadc99fd9c7bbb7d0ca2b31d72a067d0c0dcd7aad25ab8cac71ba91417694b" dependencies = [ "lock_api", ] diff --git a/Cargo.toml b/Cargo.toml index 043e22ac10..94a7fc0bae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -312,7 +312,7 @@ mmio-api = { version = "0.2.2", path = "memory/mmio-api" } cvi-vdec-uapi = { version = "0.1.0", path = "drivers/interface/cvi-vdec-uapi" } lock_api = { version = "0.4", default-features = false } log = "0.4" -spin = { version = "=0.12.0", default-features = false, features = ["lock_api", "once", "lazylock"] } +spin = { version = "=0.12.2", default-features = false, features = ["lock_api", "once", "lazylock"] } atomic-waker = { version = "1.1", default-features = false } ostool = { version = "0.24.0" } riscv = { version = "0.16.1", default-features = false } diff --git a/README.md b/README.md index 34341b5211..8910e44454 100644 --- a/README.md +++ b/README.md @@ -82,8 +82,8 @@ qemu-system-loongarch64 --version Then use the unified `cargo xtask` entry point to run the three system paths: ```bash -# ArceOS: run Hello World -cargo xtask arceos qemu --package arceos-helloworld --arch aarch64 +# ArceOS: run the default Hello World +cargo xtask arceos qemu --arch aarch64 # StarryOS: prepare rootfs before the first run cargo xtask starry rootfs --arch aarch64 @@ -93,7 +93,7 @@ cargo xtask starry qemu --arch aarch64 cargo xtask axvisor qemu --arch aarch64 ``` -If you only want the shortest path to a successful run, start with ArceOS Hello World. For more systems, architecture combinations, and QEMU options, see the [quick start overview](https://rcore-os.cn/tgoskits/docs/quickstart/overview) and [run and QEMU](https://rcore-os.cn/tgoskits/docs/build/run). +If you only want the shortest path to a successful run, start with the default ArceOS Hello World app. Pass `--package arceos-shell` when you specifically need the interactive Shell. For more systems, architecture combinations, and QEMU options, see the [quick start overview](https://rcore-os.cn/tgoskits/docs/quickstart/overview) and [run and QEMU](https://rcore-os.cn/tgoskits/docs/build/run). ## 4. Contributing diff --git a/apps/arceos/arce_agent/Cargo.toml b/apps/arceos/arce_agent/Cargo.toml index 9b375e0f89..7793b443eb 100644 --- a/apps/arceos/arce_agent/Cargo.toml +++ b/apps/arceos/arce_agent/Cargo.toml @@ -5,8 +5,8 @@ edition.workspace = true publish = false [features] -default = [] -arceos = ["dep:ax-std"] +default = ["arceos"] +arceos = ["dep:ax-std", "ax-std/arceos"] [dependencies] ax-std = { workspace = true, optional = true } @@ -20,6 +20,3 @@ serde_json = "1" log = "0.4" env_logger = "0.11" - -[package.metadata.axstd] -features = ["log-level-warn"] diff --git a/apps/arceos/helloworld/Cargo.toml b/apps/arceos/helloworld/Cargo.toml index 51e1c609a8..803729ccaf 100644 --- a/apps/arceos/helloworld/Cargo.toml +++ b/apps/arceos/helloworld/Cargo.toml @@ -6,11 +6,8 @@ authors = ["eternalcomet "] publish = false [features] -default = [] -arceos = ["dep:ax-std"] +default = ["arceos"] +arceos = ["dep:ax-std", "ax-std/arceos"] [dependencies] ax-std = { workspace = true, optional = true } - -[package.metadata.axstd] -features = ["log-level-debug"] diff --git a/apps/arceos/httpclient/Cargo.toml b/apps/arceos/httpclient/Cargo.toml index 77f5d38a73..f9b36c9aa3 100644 --- a/apps/arceos/httpclient/Cargo.toml +++ b/apps/arceos/httpclient/Cargo.toml @@ -8,12 +8,9 @@ publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] -default = [] -arceos = ["dep:ax-std", "ax-std/net"] +default = ["arceos"] +arceos = ["dep:ax-std", "ax-std/arceos", "ax-std/net"] dns = [] [dependencies] ax-std = { workspace = true, optional = true } - -[package.metadata.axstd] -features = ["log-level-debug"] diff --git a/apps/arceos/httpserver/Cargo.toml b/apps/arceos/httpserver/Cargo.toml index c38bcd29c6..fe7ab978a4 100644 --- a/apps/arceos/httpserver/Cargo.toml +++ b/apps/arceos/httpserver/Cargo.toml @@ -6,11 +6,8 @@ authors = ["Yuekai Jia "] publish = false [features] -default = [] -arceos = ["dep:ax-std"] +default = ["arceos"] +arceos = ["dep:ax-std", "ax-std/arceos"] [dependencies] ax-std = { workspace = true, optional = true } - -[package.metadata.axstd] -features = ["log-level-debug"] diff --git a/apps/arceos/io_test/Cargo.toml b/apps/arceos/io_test/Cargo.toml index 3d9579b1c3..1549b23f24 100644 --- a/apps/arceos/io_test/Cargo.toml +++ b/apps/arceos/io_test/Cargo.toml @@ -6,11 +6,8 @@ authors = ["eternalcomet "] publish = false [features] -default = [] -arceos = ["dep:ax-std", "ax-std/fs"] +default = ["arceos"] +arceos = ["dep:ax-std", "ax-std/arceos", "ax-std/fs"] [dependencies] ax-std = { workspace = true, optional = true } - -[package.metadata.axstd] -features = ["log-level-off"] diff --git a/apps/arceos/shell/Cargo.toml b/apps/arceos/shell/Cargo.toml index 2640aa6984..3edf63ba70 100644 --- a/apps/arceos/shell/Cargo.toml +++ b/apps/arceos/shell/Cargo.toml @@ -10,11 +10,10 @@ publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] -default = [] -arceos = ["dep:ax-std", "ax-std/fs"] +default = ["arceos"] +# The QEMU disk image is FAT32; selecting the backend here keeps filesystem +# capability in the application manifest instead of relying on axbuild fixes. +arceos = ["dep:ax-std", "ax-std/arceos", "ax-std/fatfs"] [dependencies] ax-std = { workspace = true, optional = true } - -[package.metadata.axstd] -features = ["log-level-debug"] diff --git a/apps/arceos/thread_test/Cargo.toml b/apps/arceos/thread_test/Cargo.toml index f7a8edd73b..a3b48b9b68 100644 --- a/apps/arceos/thread_test/Cargo.toml +++ b/apps/arceos/thread_test/Cargo.toml @@ -6,11 +6,8 @@ authors = ["eternalcomet "] publish = false [features] -default = [] -arceos = ["dep:ax-std", "ax-std/multitask", "ax-std/irq"] +default = ["arceos"] +arceos = ["dep:ax-std", "ax-std/arceos", "ax-std/multitask", "ax-std/irq"] [dependencies] ax-std = { workspace = true, optional = true } - -[package.metadata.axstd] -features = ["log-level-off"] diff --git a/apps/arceos/tokio_test/Cargo.toml b/apps/arceos/tokio_test/Cargo.toml index 527694c405..dba90bbb33 100644 --- a/apps/arceos/tokio_test/Cargo.toml +++ b/apps/arceos/tokio_test/Cargo.toml @@ -7,12 +7,9 @@ publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] -default = [] -arceos = ["dep:ax-std", "ax-std/multitask", "ax-std/irq"] +default = ["arceos"] +arceos = ["dep:ax-std", "ax-std/arceos", "ax-std/multitask", "ax-std/irq"] [dependencies] ax-std = { workspace = true, optional = true } tokio = { version = "1", features = ["rt", "macros", "time", "sync"] } - -[package.metadata.axstd] -features = ["log-level-off"] diff --git a/components/aic8800/Cargo.toml b/components/aic8800/Cargo.toml index 4540a471b6..dcc0dd5027 100644 --- a/components/aic8800/Cargo.toml +++ b/components/aic8800/Cargo.toml @@ -8,8 +8,8 @@ readme = "README.md" description = "AIC8800 Wi-Fi driver core (SDIO, firmware, WPA2, SoftAP). OS-independent; runtime capabilities are injected via aic8800::WifiRuntime." # Vendor firmware blobs are not vendored into git or the package tarball; the # build script provisions them into OUT_DIR (from $AIC8800_FIRMWARE_DIR, the -# in-tree `firmware/` dir populated by `cargo xtask`, or the pinned upstream -# source) and verifies each against its SHA-256. Only the top-level blob dir is +# optional in-tree `firmware/` cache, or the pinned upstream source) and +# verifies each against its SHA-256. Only the top-level blob dir is # excluded — the leading slash anchors the glob to the package root so it does # NOT also match the `src/fw/firmware/` source module (required to build). # Without the anchor, `firmware/` matches at any depth and the tarball drops diff --git a/components/aic8800/README.md b/components/aic8800/README.md index 7f2c828a06..d270bce1e5 100644 --- a/components/aic8800/README.md +++ b/components/aic8800/README.md @@ -70,16 +70,15 @@ tarball。`build.rs` 在编译时把它们准备到 `OUT_DIR/firmware/`,`src/f `build.rs` 的固件来源优先级(命中即止): 1. `$AIC8800_FIRMWARE_DIR/` — 显式本地缓存 / 离线镜像目录。 -2. 仓库内 `components/aic8800/firmware/` — 由 `cargo xtask` 预下载填充; - 在本仓库内(含离线)编译时走这条,**不联网**。 -3. 从上游 pin 的 commit 下载 — 用于脱离本仓库的独立构建(如 crates.io)。 +2. 仓库内 `components/aic8800/firmware/` — 可选的本地缓存;手动放入并通过 + SHA-256 校验后,可在离线构建时使用。 +3. 从上游 pin 的 commit 下载 — 任一构建在前两项均不可用时使用。 -清单、摘要与上游 pin 见 [`build.rs`](build.rs) 与 -[`scripts/axbuild/src/firmware.rs`](../../scripts/axbuild/src/firmware.rs)(两份保持同步), -来源与文件列表见 [`firmware/README.md`](firmware/README.md)。 +清单、摘要与上游 pin 见 [`build.rs`](build.rs),来源与文件列表见 +[`firmware/README.md`](firmware/README.md)。 > 因此发布包可独立构建:`cargo publish` 校验 tarball 时会执行本 crate 的 -> `build.rs` 自行准备固件,不依赖仓库根目录 `cargo xtask` 的预下载副作用。 +> `build.rs` 自行准备固件,不依赖仓库根目录的全局预下载副作用。 ## 依赖 diff --git a/components/aic8800/build.rs b/components/aic8800/build.rs index 414c6874b4..e62c804218 100644 --- a/components/aic8800/build.rs +++ b/components/aic8800/build.rs @@ -8,8 +8,8 @@ //! //! Resolution order for each blob (first hit wins): //! 1. `$AIC8800_FIRMWARE_DIR/` — explicit local cache / offline mirror. -//! 2. `components/aic8800/firmware/` — the in-tree dir that -//! `cargo xtask` populates; used by normal in-repo (incl. offline) builds. +//! 2. `components/aic8800/firmware/` — optional in-tree cache for +//! offline builds. //! 3. download from the pinned upstream commit over HTTPS. //! //! Every blob is verified byte-for-byte against its pinned SHA-256 before being @@ -19,7 +19,6 @@ use std::path::{Path, PathBuf}; /// Upstream firmware source: the repo referenced by the LicheeRV Nano /// buildroot package `aic8800-sdio-firmware`, pinned to a fixed commit. -/// Keep this manifest in sync with `scripts/axbuild/src/firmware.rs`. const FIRMWARE_REPO: &str = "lxowalle/aic8800-sdio-firmware"; const FIRMWARE_COMMIT: &str = "c56f910044cc854d6c553bcb9a644f3bca5a4c38"; @@ -165,7 +164,7 @@ fn main() { let env_dir = std::env::var("AIC8800_FIRMWARE_DIR") .ok() .map(PathBuf::from); - // In-tree dir that `cargo xtask` populates (present for in-repo builds). + // Optional in-tree cache for offline builds. let in_tree = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("firmware"); for file in FIRMWARE_FILES { diff --git a/components/aic8800/firmware/README.md b/components/aic8800/firmware/README.md index 0bc43377e9..7dc79da2ea 100644 --- a/components/aic8800/firmware/README.md +++ b/components/aic8800/firmware/README.md @@ -6,12 +6,11 @@ repository. ## Provisioning -The blobs are fetched on demand by the build tooling and verified byte-for-byte -against pinned SHA-256 digests before use. Any `cargo xtask starry ...` or -`cargo xtask clippy ...` invocation that compiles the `aic8800` crate downloads -them into this directory automatically. See -[`scripts/axbuild/src/firmware.rs`](../../../scripts/axbuild/src/firmware.rs) -for the file manifest, digests, and source pin. +The crate build script resolves the blobs on demand and verifies them +byte-for-byte against pinned SHA-256 digests before writing them to Cargo's +`OUT_DIR`. This directory is an optional local cache for offline builds; put +verified blobs here, or set `AIC8800_FIRMWARE_DIR` to an external cache. See +[`../build.rs`](../build.rs) for the file manifest, digests, and source pin. ## Source diff --git a/components/aic8800/src/fw/firmware/data.rs b/components/aic8800/src/fw/firmware/data.rs index ce3b39e69e..0bd2ade551 100644 --- a/components/aic8800/src/fw/firmware/data.rs +++ b/components/aic8800/src/fw/firmware/data.rs @@ -2,7 +2,7 @@ //! //! 所有固件文件通过 include_bytes!() 在编译时嵌入内核镜像。 //! 固件 blob 由 `build.rs` 准备到 `OUT_DIR/firmware/`(来源优先级: -//! `$AIC8800_FIRMWARE_DIR` → 仓库内 `firmware/`(xtask 预下载)→ 上游下载), +//! `$AIC8800_FIRMWARE_DIR` → 仓库内 `firmware/`(可选本地缓存)→ 上游下载), //! 每个文件均按 SHA-256 校验,因此 blob 不必随源码/发布包分发。 //! //! 运行时通过 get_firmware_set() 根据芯片型号和版本选择正确的固件组合。 diff --git a/docs/docs/build/arceos/overview.md b/docs/docs/build/arceos/overview.md index f5ba628efe..d157eef196 100644 --- a/docs/docs/build/arceos/overview.md +++ b/docs/docs/build/arceos/overview.md @@ -53,9 +53,9 @@ cargo xtask arceos [options] ## 特有行为 -### `--package` 必需 +### QEMU 默认 Hello World -ArceOS 把每个可运行的应用建模为 workspace 内的独立 crate(如 `apps/arceos/arceos-httpserver`)。与 StarryOS 的"一次编译整个内核"不同,ArceOS 的构建目标必须由 `--package` 显式锁定,否则报错。`--package` 会被写入 Snapshot,后续短命令(如 `cargo arceos qemu`)会自动复用。 +ArceOS 把每个可运行的应用建模为 workspace 内的独立 crate(如 `apps/arceos/arceos-httpserver`)。`build`、`uboot` 和 `board` 仍需通过 `--package` 或已有 Snapshot/配置选择应用;仅 `qemu` 在没有 `--package` 与 `--config` 时读取 `board/qemu-.toml`,默认运行 `arceos-helloworld`。`--package` 和 `--config` 始终优先于这个 QEMU 默认值。 ### 动态平台固定启用 diff --git a/docs/docs/build/configuration.md b/docs/docs/build/configuration.md index a3a3bd0fbf..9eaf15563f 100644 --- a/docs/docs/build/configuration.md +++ b/docs/docs/build/configuration.md @@ -207,7 +207,7 @@ pub struct BuildInfo { | 字段 | 默认值 | 说明 | |------|--------|------| | `env` | `{}` | 默认不注入额外环境变量;网络地址等需要由具体 build config 或子系统配置显式提供 | -| `features` | `["ax-std"]` | 最小 feature 集 | +| `features` | `[]` | 不补充应用 feature;应用和 build config 自行声明所需能力 | | `log` | `Warn` | 默认日志级别 | | `max_cpu_num` | `None` | 不限制(单核) | @@ -216,15 +216,9 @@ pub struct BuildInfo { - `max_cpu_num`:值为 0 时报错(必须大于 0) - 旧 `plat_dyn` 字段:已移除,配置中出现该字段会报错 -### Axvisor x86 虚拟化后端检测 +### Axvisor x86 虚拟化后端 -Axvisor 在 x86_64 架构上需要虚拟化后端 support(Intel VMX 或 AMD SVM)。`axvisor/build/x86.rs` 中的 `normalize_backend_features()` 负责自动检测或验证: - -1. **已显式指定**:若 features 中包含 `vmx` 或 `svm`,直接使用(两者同时存在则报错) -2. **未指定时自动检测**:通过 CPUID 读取宿主 CPU 厂商信息: - - `GenuineIntel` → `vmx` - - `AuthenticAMD` → `svm` -3. **环境变量覆盖**:设置 `AXVISOR_X86_BACKEND=vmx|intel|svm|amd` 跳过 CPUID 检测 +Axvisor 的 x86_64 build config 应在 `features` 中选择后端:Intel 使用 `vmx`,AMD 使用 `svm`。当前 axbuild 为兼容未迁移的通用 QEMU 配置,缺少后端时临时补充 `vmx`;显式配置 `svm` 时保持不变,同时配置两个后端仍会报错。待 Axvisor 不再依赖这两个 Cargo feature 后,应删除该兼容逻辑。 ### 加载流程 @@ -301,7 +295,6 @@ axbuild 在编译期和运行时使用多个环境变量,分布在配置、运 | `STARRY_APK_REGION` | `china` | StarryOS APK 镜像源区域:`china`/`cn`(`mirrors.cernet.edu.cn`)或 `us`/`usa`(`dl-cdn.alpinelinux.org`) | | `TGOS_IMAGE_LOCAL_STORAGE` | `/tmp/axbuild/rootfs` | TGOS 镜像本地存储路径(覆盖 `ImageConfig.local_storage`,影响 `cargo xtask image` 与所有子系统的 rootfs 拉取) | | `TGOS_IMAGE_REGISTRY_FALLBACK_URL` | `.../rcore-os/tgosimages/.../v0.0.6.toml` | TGOS 镜像注册表的 fallback URL(当 `default.toml` 拉取失败时使用) | -| `AXVISOR_X86_BACKEND` | — | Axvisor x86_64 虚拟化后端强制选择:`vmx`/`intel` 或 `svm`/`amd`(跳过 CPUID 自动检测) | | `AXLOADER_X86_64_UEFI_FIRMWARE` | — | axloader HTTP smoke test 优先使用的 OVMF 固件路径(仅 `cargo xtask axloader test qemu`) | | `AXVISOR_X86_64_UEFI_FIRMWARE` | — | axloader HTTP smoke test 的兼容旧变量;Axvisor UEFI CI 也会用它向 `setup_qemu.sh` 传递 OVMF 路径 | | `AXBUILD_KEEP_QEMU_LOG` | — | 设为非空保留 QEMU 运行日志(用于 backtrace 符号化后的事后分析) | diff --git a/docs/docs/development/arceos.md b/docs/docs/development/arceos.md index 11c79c8227..f31c868196 100644 --- a/docs/docs/development/arceos.md +++ b/docs/docs/development/arceos.md @@ -287,14 +287,12 @@ version = "0.1.0" edition.workspace = true [features] -default = [] -arceos = ["dep:ax-std"] +default = ["arceos"] +arceos = ["dep:ax-std", "ax-std/arceos"] [dependencies] ax-std = { workspace = true, optional = true } -[package.metadata.axstd] -features = ["log-level-debug"] ``` **3) `src/main.rs`** diff --git a/docs/docs/quickstart/arceos.md b/docs/docs/quickstart/arceos.md index abca7ecc7a..f04bb47d0c 100644 --- a/docs/docs/quickstart/arceos.md +++ b/docs/docs/quickstart/arceos.md @@ -6,28 +6,31 @@ title: "ArceOS 快速上手" # ArceOS 快速上手 -ArceOS 的最短路径通常是选择一个示例包,通过 `cargo xtask arceos qemu` 直接构建并启动。 +ArceOS 的最短路径是直接运行 `cargo xtask arceos qemu`。该 QEMU 子命令会选择对应架构的 board 模板,默认启动 Hello World。 ```mermaid flowchart LR - A[选择包] --> B[选择架构] - B --> C[cargo xtask arceos qemu] - C --> D{验证通过?} - D -- 是 --> E[测试套件] - D -- 否 --> F[检查环境] - F --> B + A[选择架构] --> B[cargo xtask arceos qemu] + B --> C{需要其他应用?} + C -- 否 --> D[Hello World] + C -- 是 --> E[--package 指定应用] + D --> F{验证通过?} + E --> F + F -- 是 --> G[测试套件] + F -- 否 --> H[检查环境] + H --> A ``` ## 1. 快速启动 -本节给出 ArceOS 在不同架构上的最短启动命令。推荐优先选择 `arceos-helloworld`,因为它依赖最少、输出最直接,适合确认基础构建链路和 QEMU 路径是否正常。 +不带 `--package` 时,QEMU 子命令从 `os/arceos/configs/board/qemu-.toml` 读取默认应用与 feature;当前模板使用最小的 `arceos-helloworld`。需要运行其他应用时,再通过 `--package` 显式覆盖。 ### 1.1 RISC-V 64 `riscv64` 是当前最适合作为第一条验证路径的架构之一。命令短、反馈明确,也最便于和测试套件中的主流验证路径对应起来。 ```bash -cargo xtask arceos qemu --package arceos-helloworld --target riscv64gc-unknown-none-elf +cargo xtask arceos qemu --target riscv64gc-unknown-none-elf ``` ### 1.2 AArch64 @@ -35,7 +38,7 @@ cargo xtask arceos qemu --package arceos-helloworld --target riscv64gc-unknown-n 如果后续工作会涉及 StarryOS 或 Axvisor,AArch64 路径会更容易和其他系统对齐。它适合在完成第一条最小运行路径后继续验证。 ```bash -cargo xtask arceos qemu --package arceos-helloworld --target aarch64-unknown-none-softfloat +cargo xtask arceos qemu --target aarch64-unknown-none-softfloat ``` ### 1.3 x86_64 @@ -43,7 +46,7 @@ cargo xtask arceos qemu --package arceos-helloworld --target aarch64-unknown-non `x86_64` 更适合本地 x86 平台适配或与 PC 类平台环境对照时使用。启动方式与其他架构一致,主要差别在目标 triple 和底层平台配置。 ```bash -cargo xtask arceos qemu --package arceos-helloworld --target x86_64-unknown-none +cargo xtask arceos qemu --target x86_64-unknown-none ``` ### 1.4 LoongArch64 @@ -51,7 +54,7 @@ cargo xtask arceos qemu --package arceos-helloworld --target x86_64-unknown-none LoongArch64 路径适合作为补充验证,而不是第一次上手的默认首选。使用前建议先确认本地环境或容器环境中对应 QEMU 已可用。 ```bash -cargo xtask arceos qemu --package arceos-helloworld --target loongarch64-unknown-none-softfloat +cargo xtask arceos qemu --target loongarch64-unknown-none-softfloat ``` ## 2. 常用包 diff --git a/os/StarryOS/configs/qemu/qemu-loongarch64.toml b/os/StarryOS/configs/qemu/qemu-loongarch64.toml index 8e206d002e..1f8953b527 100644 --- a/os/StarryOS/configs/qemu/qemu-loongarch64.toml +++ b/os/StarryOS/configs/qemu/qemu-loongarch64.toml @@ -15,7 +15,8 @@ args = [ "-netdev", "user,id=net0", ] -uefi = false +# The LoongArch Starry kernel uses the dynamic UEFI platform on the default QEMU path. +uefi = true to_bin = true success_regex = [] fail_regex = ["(?i)\\bpanic(?:ked)?\\b"] diff --git a/os/StarryOS/starryos/Cargo.toml b/os/StarryOS/starryos/Cargo.toml index ac897b1662..bc0214658a 100644 --- a/os/StarryOS/starryos/Cargo.toml +++ b/os/StarryOS/starryos/Cargo.toml @@ -22,6 +22,10 @@ qemu = [ "starry-kernel/input", "starry-kernel/vsock", # auxiliary features ] +# The profiling command selects this package-level preset; axbuild must not assemble the +# individual driver feature list at runtime. +qperf = ["ax-driver/virtio-blk", "ax-driver/virtio-net", "ax-driver/virtio-socket"] +qperf-metrics = [] smp = ["ax-runtime/smp", "axplat-dyn/smp", "ax-hal/smp", "starry-kernel/ipi"] k230 = [ @@ -44,7 +48,9 @@ name = "xtask" path = "xtask/main.rs" [dependencies] -ax-std = { workspace = true, features = ["ext-ld", "fs", "multitask", "irq"] } +# Starry links Rust `std` dynamically on the musl target; keep the libc +# compatibility surface in the package manifest rather than axbuild. +ax-std = { workspace = true, features = ["ext-ld", "std-compat", "fs", "multitask", "irq"] } ax-driver.workspace = true ax-hal.workspace = true ax-runtime = { workspace = true, features = ["ext-ld", "irq"] } diff --git a/os/arceos/configs/README.md b/os/arceos/configs/README.md index 8d18555ce3..2fd52cb0cc 100644 --- a/os/arceos/configs/README.md +++ b/os/arceos/configs/README.md @@ -4,8 +4,9 @@ This directory follows the same split as the StarryOS and Axvisor configuration trees. - `board/` contains build templates consumed by `cargo xtask arceos defconfig`. - The `qemu-.toml` entries select the default dynamic-platform build for - each supported QEMU architecture. + The `qemu-.toml` entries select the default dynamic-platform Hello World build + for each supported QEMU architecture when `arceos qemu` has no package or + build-config selector. - `qemu/` contains reusable, architecture-specific QEMU runtime templates. They are intended for explicit `--qemu-config` use and deliberately do not declare application success markers. diff --git a/os/arceos/doc/std_support_readme.md b/os/arceos/doc/std_support_readme.md index 656ff9ba8f..4ae4c34485 100644 --- a/os/arceos/doc/std_support_readme.md +++ b/os/arceos/doc/std_support_readme.md @@ -44,28 +44,24 @@ An app declares the ArceOS-side features it needs behind its app-local ```toml [features] -default = [] -arceos = ["dep:ax-std", "ax-std/fs", "ax-std/net", "ax-std/multitask", "ax-std/irq"] +default = ["arceos"] +arceos = ["dep:ax-std", "ax-std/arceos", "ax-std/fs", "ax-std/net", "ax-std/multitask", "ax-std/irq"] [dependencies] ax-std = { workspace = true, optional = true } ``` -Keep logging and normal Cargo features for app-local choices. For example: +Keep normal Cargo features for app-local choices. For example: ```toml [features] -default = [] +default = ["arceos"] dns = [] - -[package.metadata.axstd] -features = ["log-level-debug"] ``` -`axbuild` automatically enables `arceos` for ArceOS std builds when the app -declares it. It combines app features and `package.metadata.axstd.features`, -maps ArceOS backend features to `ax-std/*`, and adds the platform feature for -the selected ArceOS target before linking the app. +The application feature declaration is the source of truth. `axbuild` only +translates features explicitly selected by the build configuration into Cargo +arguments; it does not add the application's runtime baseline. ## Runtime compatibility diff --git a/os/arceos/ulib/axlibc/Cargo.toml b/os/arceos/ulib/axlibc/Cargo.toml index 0adb213640..cce6095a8c 100644 --- a/os/arceos/ulib/axlibc/Cargo.toml +++ b/os/arceos/ulib/axlibc/Cargo.toml @@ -50,10 +50,10 @@ net = ["ax-posix-api/net", "ax-runtime/net", "fd"] # Libc features fd = ["ax-posix-api/fd"] -pipe = ["ax-posix-api/pipe"] -select = ["ax-posix-api/select"] -poll = ["ax-posix-api/poll"] -epoll = ["ax-posix-api/epoll"] +pipe = ["ax-posix-api/pipe", "fd"] +select = ["ax-posix-api/select", "fd"] +poll = ["ax-posix-api/poll", "fd"] +epoll = ["ax-posix-api/epoll", "fd"] [dependencies] ax-driver.workspace = true diff --git a/os/arceos/ulib/axstd/Cargo.toml b/os/arceos/ulib/axstd/Cargo.toml index 2c70d18446..0647a58a44 100644 --- a/os/arceos/ulib/axstd/Cargo.toml +++ b/os/arceos/ulib/axstd/Cargo.toml @@ -21,6 +21,10 @@ crate-type = ["rlib", "staticlib"] [features] default = ["alloc", "tls", "fd"] +# Baseline required by every ArceOS Rust application. Applications enable this +# through their own `arceos` feature instead of axbuild completing it at runtime. +arceos = ["irq", "paging", "std-compat", "smp"] + # libc compatibility surface used when ax-std is linked as fake libc for Rust std. std-compat = ["ax-alloc/global-allocator", "ax-runtime/std-compat"] diff --git a/os/axvisor/Cargo.toml b/os/axvisor/Cargo.toml index 9e3c6f5b53..47664eb21f 100644 --- a/os/axvisor/Cargo.toml +++ b/os/axvisor/Cargo.toml @@ -67,7 +67,7 @@ anyhow.workspace = true log = "0.4" # System dependent modules provided by ArceOS. -ax-std = { workspace = true, features = ["ext-ld", "paging", "irq", "multitask", "task-ext", "smp", "hv"] } +ax-std = { workspace = true, features = ["ext-ld", "paging", "irq", "multitask", "task-ext", "smp", "hv", "std-compat"] } ax-hal = { workspace = true, features = ["paging", "irq", "smp", "hv"] } # System dependent modules provided by ArceOS-Hypervisor (bare-metal only) # ax-runtime = { version = "=0.3.0-preview.1", features = ["alloc", "irq", "paging", "smp", "multitask"] } diff --git a/os/axvisor/configs/board/asus-nuc15crh-x86_64.toml b/os/axvisor/configs/board/asus-nuc15crh-x86_64.toml index 2c9409c2fa..ba5172037e 100644 --- a/os/axvisor/configs/board/asus-nuc15crh-x86_64.toml +++ b/os/axvisor/configs/board/asus-nuc15crh-x86_64.toml @@ -1,5 +1,5 @@ env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2", RDRIVE_ACPI_LOAD_AML = "0" } -features = [] +features = ["vmx"] log = "Info" target = "x86_64-unknown-none" vm_configs = [] diff --git a/os/axvisor/configs/board/qemu-x86_64-linux.toml b/os/axvisor/configs/board/qemu-x86_64-linux.toml index 9800bc6fe8..47a5efa799 100644 --- a/os/axvisor/configs/board/qemu-x86_64-linux.toml +++ b/os/axvisor/configs/board/qemu-x86_64-linux.toml @@ -1,5 +1,6 @@ features = [ "fs", + "vmx", ] log = "Info" target = "x86_64-unknown-none" diff --git a/os/axvisor/configs/board/qemu-x86_64.toml b/os/axvisor/configs/board/qemu-x86_64.toml index 766dad8552..09e044e78a 100644 --- a/os/axvisor/configs/board/qemu-x86_64.toml +++ b/os/axvisor/configs/board/qemu-x86_64.toml @@ -1,6 +1,7 @@ features = [ "ax-driver/virtio-blk", "fs", + "vmx", ] log = "Info" target = "x86_64-unknown-none" diff --git a/os/axvisor/configs/qemu/qemu-x86_64-linux.toml b/os/axvisor/configs/qemu/qemu-x86_64-linux.toml index 93dd3de1e9..725dd29e96 100644 --- a/os/axvisor/configs/qemu/qemu-x86_64-linux.toml +++ b/os/axvisor/configs/qemu/qemu-x86_64-linux.toml @@ -22,5 +22,5 @@ args = [ ] fail_regex = [] success_regex = [] -to_bin = false -uefi = false +to_bin = true +uefi = true diff --git a/os/axvisor/configs/qemu/qemu-x86_64.toml b/os/axvisor/configs/qemu/qemu-x86_64.toml index 34d8b99472..6d4323c357 100644 --- a/os/axvisor/configs/qemu/qemu-x86_64.toml +++ b/os/axvisor/configs/qemu/qemu-x86_64.toml @@ -17,5 +17,5 @@ args = [ ] fail_regex = [] success_regex = [] -to_bin = false -uefi = false +to_bin = true +uefi = true diff --git a/os/axvisor/configs/vms/qemu/loongarch64/linux-rootfs-smp1.toml b/os/axvisor/configs/vms/qemu/loongarch64/linux-rootfs-smp1.toml index c2c86ed236..0a8a91d4cc 100644 --- a/os/axvisor/configs/vms/qemu/loongarch64/linux-rootfs-smp1.toml +++ b/os/axvisor/configs/vms/qemu/loongarch64/linux-rootfs-smp1.toml @@ -27,6 +27,7 @@ kernel_load_addr = 0x0020_0000 # Boot Linux through guest UEFI/ACPI, matching the LoongArch QEMU virt platform. enable_bios = true boot_protocol = "uefi" +# The firmware path is part of this VM contract; axbuild must not rewrite it at runtime. uefi_firmware_path = "/tmp/ostool/ovmf/loongarch64/code.fd" bios_load_addr = 0x1c00_0000 # Kernel command line. Rootfs mode intentionally uses virtio-blk /dev/vda. diff --git a/scripts/axbuild/src/arceos/board.rs b/scripts/axbuild/src/arceos/board.rs index 1b6a743712..a06eb7da66 100644 --- a/scripts/axbuild/src/arceos/board.rs +++ b/scripts/axbuild/src/arceos/board.rs @@ -101,15 +101,17 @@ pub(crate) fn board_names(workspace_root: &Path) -> anyhow::Result> .collect()) } -pub(crate) fn default_qemu_board_for_target( +pub(crate) fn default_qemu_board( workspace_root: &Path, - package: &str, target: &str, + package: Option<&str>, ) -> anyhow::Result> { Ok(board_default_list(workspace_root)? .into_iter() .find(|board| { - board.name.starts_with("qemu-") && board.package == package && board.target == target + board.name.starts_with("qemu-") + && board.target == target + && package.is_none_or(|package| board.package == package) })) } @@ -203,18 +205,25 @@ log = "Info" "#, ); - let board = default_qemu_board_for_target( + let board = default_qemu_board( root.path(), - "arceos-helloworld", "aarch64-unknown-none-softfloat", + Some("arceos-helloworld"), ) .unwrap(); assert_eq!(board.unwrap().name, "qemu-aarch64"); + assert_eq!( + default_qemu_board(root.path(), "aarch64-unknown-none-softfloat", None) + .unwrap() + .unwrap() + .name, + "qemu-aarch64" + ); assert!( - default_qemu_board_for_target( + default_qemu_board( root.path(), - "another-package", "aarch64-unknown-none-softfloat", + Some("another-package"), ) .unwrap() .is_none() diff --git a/scripts/axbuild/src/arceos/build/cargo_config.rs b/scripts/axbuild/src/arceos/build/cargo_config.rs index 91b76bf241..1b86bc9b3c 100644 --- a/scripts/axbuild/src/arceos/build/cargo_config.rs +++ b/scripts/axbuild/src/arceos/build/cargo_config.rs @@ -1,18 +1,14 @@ use anyhow::{Context, bail}; use ostool::build::config::Cargo; -use super::{ArceosBuildInfo, info::load_build_config_with_makefile_features_and_metadata}; +use super::{ArceosBuildInfo, info::load_build_config_with_makefile_features}; use crate::{build, context::ResolvedBuildRequest}; pub(crate) fn load_cargo_config(request: &ResolvedBuildRequest) -> anyhow::Result { let metadata = build::cached_workspace_metadata().context("failed to load workspace metadata")?; let makefile_features = build::makefile_features_from_env(); - let config = load_build_config_with_makefile_features_and_metadata( - request, - &makefile_features, - Some(metadata), - )?; + let config = load_build_config_with_makefile_features(request, &makefile_features)?; if config.app_c.is_some() { bail!( "ArceOS build config {} uses `app-c`; use the C app build path", @@ -29,17 +25,11 @@ pub(crate) fn load_cargo_config(request: &ResolvedBuildRequest) -> anyhow::Resul } pub(crate) fn load_c_app_cargo_config(request: &ResolvedBuildRequest) -> anyhow::Result { - let metadata = - build::cached_workspace_metadata().context("failed to load workspace metadata")?; let makefile_features = build::makefile_features_from_env(); - let mut build_info = load_build_config_with_makefile_features_and_metadata( - request, - &makefile_features, - Some(metadata), - )? - .build_info; + let mut build_info = + load_build_config_with_makefile_features(request, &makefile_features)?.build_info; build_info.validated_max_cpu_num()?; - build_info.resolve_features_with_metadata(&request.package, &request.target, metadata); + build_info.resolve_c_app_features()?; let rustflags = build::toolchain_rustflags_for_features(&build_info.env, &build_info.features); let args = ArceosBuildInfo::build_cargo_args(&request.target, &rustflags); diff --git a/scripts/axbuild/src/arceos/build/info.rs b/scripts/axbuild/src/arceos/build/info.rs index daf62d469c..ee6b15ecf1 100644 --- a/scripts/axbuild/src/arceos/build/info.rs +++ b/scripts/axbuild/src/arceos/build/info.rs @@ -1,8 +1,6 @@ use std::{fs, path::PathBuf}; use anyhow::Context; -use cargo_metadata::Metadata; -use log::warn; use super::ArceosBuildConfig; #[cfg(test)] @@ -32,38 +30,12 @@ fn load_build_info_with_makefile_features( request: &ResolvedBuildRequest, makefile_features: &[String], ) -> anyhow::Result { - let metadata = if makefile_features.is_empty() { - None - } else { - Some(build::workspace_metadata().context("failed to load workspace metadata")?) - }; - load_build_info_with_makefile_features_and_metadata( - request, - makefile_features, - metadata.as_ref(), - ) + Ok(load_build_config_with_makefile_features(request, makefile_features)?.build_info) } -#[cfg(test)] -fn load_build_info_with_makefile_features_and_metadata( +pub(super) fn load_build_config_with_makefile_features( request: &ResolvedBuildRequest, makefile_features: &[String], - metadata: Option<&Metadata>, -) -> anyhow::Result { - Ok( - load_build_config_with_makefile_features_and_metadata( - request, - makefile_features, - metadata, - )? - .build_info, - ) -} - -pub(super) fn load_build_config_with_makefile_features_and_metadata( - request: &ResolvedBuildRequest, - makefile_features: &[String], - metadata: Option<&Metadata>, ) -> anyhow::Result { build::ensure_build_info(&request.build_info_path, ArceosBuildConfig::default_config)?; let content = fs::read_to_string(&request.build_info_path)?; @@ -74,38 +46,14 @@ pub(super) fn load_build_config_with_makefile_features_and_metadata( request.build_info_path.display() ) })?; - if config.build_info.normalize_legacy_feature_aliases() { - warn!( - "normalizing legacy feature aliases in build config {}", - request.build_info_path.display() - ); - fs::write(&request.build_info_path, toml::to_string_pretty(&config)?).with_context( - || { - format!( - "failed to rewrite normalized build info {}", - request.build_info_path.display() - ) - }, - )?; - } + config.build_info.validate_features()?; - match metadata { - Some(metadata) => build::apply_makefile_features_with_metadata( - &mut config.build_info, - &request.package, - makefile_features, - metadata, - ), - None => build::apply_makefile_features( - &mut config.build_info, - &request.package, - makefile_features, - ), - } + build::apply_makefile_features(&mut config.build_info, makefile_features)?; if let Some(smp) = request.smp { config.build_info.max_cpu_num = Some(smp); } + config.build_info.validate_features()?; Ok(config) } diff --git a/scripts/axbuild/src/arceos/build/tests.rs b/scripts/axbuild/src/arceos/build/tests.rs index 5eaf1063a1..249599cc91 100644 --- a/scripts/axbuild/src/arceos/build/tests.rs +++ b/scripts/axbuild/src/arceos/build/tests.rs @@ -11,7 +11,10 @@ use super::{ load_arceos_build_mode, load_c_app_cargo_config, resolve_app_c_dir, resolve_app_c_mode, resolve_build_info_path, }; -use crate::{build, context::ResolvedBuildRequest}; +use crate::{ + build, + context::{ResolvedBuildRequest, find_workspace_root}, +}; fn repo_metadata() -> cargo_metadata::Metadata { build::workspace_metadata().unwrap() @@ -41,13 +44,7 @@ fn request(package: &str, target: &str, build_info_path: PathBuf) -> ResolvedBui } #[test] -fn resolves_dynamic_platform_features_and_args() { - let mut build_info = ArceosBuildInfo::default(); - build_info.resolve_features("arceos-helloworld", "aarch64-unknown-none-softfloat"); - - assert!(!build_info.features.contains(&"ax-std/plat-dyn".to_string())); - assert!(!build_info.features.contains(&"ax-hal/plat-dyn".to_string())); - +fn build_cargo_args_use_builtin_target_and_build_std() { let args = ArceosBuildInfo::build_cargo_args("aarch64-unknown-none-softfloat", &[]); assert!( args.windows(2) @@ -58,22 +55,25 @@ fn resolves_dynamic_platform_features_and_args() { #[test] fn max_cpu_num_adds_smp_feature_for_std_build() { - let metadata = repo_metadata(); let mut build_info = ArceosBuildInfo { features: vec!["ax-api/net".to_string()], max_cpu_num: Some(4), ..ArceosBuildInfo::default() }; - build_info.resolve_features_with_metadata( - "starryos", - "aarch64-unknown-none-softfloat", - &metadata, - ); + build_info.resolve_c_app_features().unwrap(); assert!(build_info.features.contains(&"ax-std/smp".to_string())); } +#[test] +fn arceos_shell_declares_the_filesystem_backend_used_by_its_qemu_disk() { + let manifest = + fs::read_to_string(find_workspace_root().join("apps/arceos/shell/Cargo.toml")).unwrap(); + + assert!(manifest.contains("ax-std/fatfs")); +} + #[test] fn resolve_build_info_path_uses_package_directory() { let path = resolve_build_info_path("arceos-helloworld", "aarch64-unknown-none-softfloat", None) @@ -126,11 +126,7 @@ fn load_build_info_creates_missing_default_file() { assert_eq!(build_info, ArceosBuildInfo::default()); assert!(path.exists()); - assert!( - fs::read_to_string(path) - .unwrap() - .contains("features = [\"ax-std\"]") - ); + assert!(fs::read_to_string(path).unwrap().contains("features = []")); } #[test] @@ -225,7 +221,7 @@ fn app_c_build_config_rejects_source_dir_without_c_files() { } #[test] -fn load_build_info_normalizes_legacy_feature_aliases() { +fn load_build_info_rejects_legacy_feature_aliases() { let root = tempdir().unwrap(); let path = root.path().join(".build-target.toml"); fs::write( @@ -239,16 +235,9 @@ log = "Warn" .unwrap(); let request = request("arceos-helloworld", "target", path.clone()); - let build_info = load_build_info(&request).unwrap(); + let err = load_build_info(&request).unwrap_err(); - assert!(build_info.features.contains(&"ax-std".to_string())); - assert!(build_info.features.contains(&"ax-std/smp".to_string())); - assert!(build_info.features.contains(&"ax-runtime/net".to_string())); - assert!(!build_info.features.contains(&"axstd".to_string())); - - let rewritten = fs::read_to_string(path).unwrap(); - assert!(rewritten.contains("ax-std")); - assert!(!rewritten.contains("axstd")); + assert!(err.to_string().contains("removed `axstd` alias")); } #[test] @@ -377,18 +366,12 @@ fn parse_makefile_features_splits_commas_whitespace_and_dedups() { #[test] fn apply_makefile_features_uses_ax_std_prefix_for_unified_std_build() { - let metadata = repo_metadata(); let mut build_info = ArceosBuildInfo { features: Vec::new(), ..ArceosBuildInfo::default() }; - build::apply_makefile_features_with_metadata( - &mut build_info, - "starryos", - &[String::from("lockdep")], - &metadata, - ); + build::apply_makefile_features(&mut build_info, &[String::from("lockdep")]).unwrap(); assert!(build_info.features.contains(&"lockdep".to_string())); assert!(!build_info.features.contains(&"ax-api/lockdep".to_string())); @@ -477,7 +460,7 @@ fn prepared_cargo_config_defaults_x86_64_to_dynamic_platform() { ) .unwrap(); - assert!(cargo.to_bin); + assert!(!cargo.to_bin); assert!( cargo .target diff --git a/scripts/axbuild/src/arceos/cbuild/features.rs b/scripts/axbuild/src/arceos/cbuild/features.rs index 2df32b742f..f064d5dcb2 100644 --- a/scripts/axbuild/src/arceos/cbuild/features.rs +++ b/scripts/axbuild/src/arceos/cbuild/features.rs @@ -1,7 +1,8 @@ use std::collections::BTreeSet; +use anyhow::bail; + const C_DEFINE_FEATURE_PREFIX: &str = "c-define:"; -const REMOVED_AX_DRIVER_PLAT_STATIC_FEATURE: &str = concat!("ax-driver/", "plat", "-static"); pub(super) fn dynamic_pie_for_c_app(features: &[String]) -> bool { let _ = features; @@ -71,7 +72,7 @@ pub(super) fn c_define_name(feature: &str) -> String { pub(super) fn map_c_app_features( case_features: &[String], base_features: &[String], -) -> Vec { +) -> anyhow::Result> { const LIB_FEATURES: &[&str] = &[ "fp-simd", "irq", @@ -91,9 +92,7 @@ pub(super) fn map_c_app_features( let mut features = BTreeSet::new(); for feature in base_features { - if removed_cargo_feature(feature) { - continue; - } + reject_removed_c_app_feature(feature)?; let normalized = feature .strip_prefix("ax-std/") .or_else(|| feature.strip_prefix("ax-libc/")) @@ -107,7 +106,10 @@ pub(super) fn map_c_app_features( } match normalized { "ax-std" | "ax-libc" => {} - "plat-dyn" => {} + "plat-dyn" => bail!( + "C app feature `plat-dyn` is no longer supported; dynamic platform selection is \ + automatic" + ), "smp" => { features.insert("smp".to_string()); } @@ -120,9 +122,10 @@ pub(super) fn map_c_app_features( } } for feature in case_features { - if feature.starts_with(C_DEFINE_FEATURE_PREFIX) || removed_cargo_feature(feature) { + if feature.starts_with(C_DEFINE_FEATURE_PREFIX) { continue; } + reject_removed_c_app_feature(feature)?; let normalized = feature .strip_prefix("ax-std/") .or_else(|| feature.strip_prefix("ax-libc/")) @@ -135,19 +138,19 @@ pub(super) fn map_c_app_features( continue; } if normalized == "plat-dyn" { - continue; + bail!( + "C app feature `plat-dyn` is no longer supported; dynamic platform selection is \ + automatic" + ); } features.insert(normalized.to_string()); } - if features - .iter() - .any(|feature| matches!(feature.as_str(), "fs" | "net" | "pipe" | "select" | "epoll")) - { - features.insert("fd".to_string()); - } - features.into_iter().collect() + Ok(features.into_iter().collect()) } -fn removed_cargo_feature(feature: &str) -> bool { - matches!(feature, REMOVED_AX_DRIVER_PLAT_STATIC_FEATURE) +fn reject_removed_c_app_feature(feature: &str) -> anyhow::Result<()> { + if feature == concat!("ax-driver/", "plat", "-static") { + bail!("C app feature `{feature}` is no longer supported; remove it from the configuration"); + } + Ok(()) } diff --git a/scripts/axbuild/src/arceos/cbuild/libc.rs b/scripts/axbuild/src/arceos/cbuild/libc.rs index ce60855b79..37bda24f26 100644 --- a/scripts/axbuild/src/arceos/cbuild/libc.rs +++ b/scripts/axbuild/src/arceos/cbuild/libc.rs @@ -5,7 +5,7 @@ use ostool::build::config::Cargo; use crate::support::process::ProcessExt; -pub(super) const AX_LIBC_PACKAGE: &str = "ax-libc"; +pub(crate) const AX_LIBC_PACKAGE: &str = "ax-libc"; pub(super) const PIC_RUSTFLAG: &str = "-Crelocation-model=pic"; pub(super) fn build_axlibc_staticlib( diff --git a/scripts/axbuild/src/arceos/cbuild/mod.rs b/scripts/axbuild/src/arceos/cbuild/mod.rs index 6f8a311b3e..2c9f0a44f3 100644 --- a/scripts/axbuild/src/arceos/cbuild/mod.rs +++ b/scripts/axbuild/src/arceos/cbuild/mod.rs @@ -17,7 +17,8 @@ mod types; use compile::{archive_static_lib, compile_dir_c_sources}; use features::{c_compiler_features, dynamic_pie_for_c_app, map_c_app_features}; use flags::{CFlagsInput, cflags, write_pthread_mutex_header}; -use libc::{AX_LIBC_PACKAGE, build_axlibc_staticlib}; +pub(crate) use libc::AX_LIBC_PACKAGE; +use libc::build_axlibc_staticlib; use link::{find_link_scripts, libgcc, link_c_app, platform_name}; use types::sanitize_name; pub(crate) use types::{ArceosCBuildInput, ArceosCBuildOutput}; @@ -40,7 +41,7 @@ pub(crate) fn build_c_app( cargo.package = AX_LIBC_PACKAGE.to_string(); cargo.target = request.target.clone(); cargo.to_bin = false; - cargo.features = map_c_app_features(&input.features, &cargo.features); + cargo.features = map_c_app_features(&input.features, &cargo.features)?; let c_features = c_compiler_features(&cargo.features, &input.features); let dynamic_pie = dynamic_pie_for_c_app(&cargo.features); diff --git a/scripts/axbuild/src/arceos/cbuild/tests.rs b/scripts/axbuild/src/arceos/cbuild/tests.rs index 2eba26d041..c28ee4946a 100644 --- a/scripts/axbuild/src/arceos/cbuild/tests.rs +++ b/scripts/axbuild/src/arceos/cbuild/tests.rs @@ -92,32 +92,25 @@ fn c_compiler_features_keep_case_defines_for_cflags() { #[test] fn map_c_app_features_preserves_driver_features() { - let removed_static_driver_feature = concat!("ax-driver/", "plat", "-static"); - let features = map_c_app_features( - &strings(&["net", removed_static_driver_feature, "ax-driver/virtio-net"]), - &[], - ); + let features = map_c_app_features(&strings(&["net", "ax-driver/virtio-net"]), &[]).unwrap(); assert!(features.contains(&"net".to_string())); - assert!(features.contains(&"fd".to_string())); - assert!(!features.contains(&removed_static_driver_feature.to_string())); assert!(features.contains(&"ax-driver/virtio-net".to_string())); } #[test] fn map_c_app_features_does_not_forward_case_define_features_to_cargo() { - let features = map_c_app_features(&strings(&["alloc", "c-define:ARCEOS_C_TEST_CASE_MEM"]), &[]); + let features = + map_c_app_features(&strings(&["alloc", "c-define:ARCEOS_C_TEST_CASE_MEM"]), &[]).unwrap(); assert_eq!(features, vec!["alloc".to_string()]); } #[test] -fn map_c_app_features_ignores_removed_dynamic_platform_feature() { - let features = map_c_app_features(&strings(&["alloc"]), &strings(&["plat-dyn"])); +fn map_c_app_features_rejects_removed_platform_feature() { + let err = map_c_app_features(&strings(&["alloc"]), &strings(&["plat-dyn"])).unwrap_err(); - assert!(features.contains(&"alloc".to_string())); - assert!(!features.contains(&"plat-dyn".to_string())); - assert!(!features.contains(&"smp".to_string())); + assert!(err.to_string().contains("no longer supported")); } #[test] @@ -160,18 +153,25 @@ fn pic_rustflag_is_appended_to_axlibc_cargo_env() { #[test] fn map_c_app_features_forwards_multitask_to_runtime_features() { - let features = map_c_app_features(&strings(&["multitask"]), &[]); + let features = map_c_app_features(&strings(&["multitask"]), &[]).unwrap(); assert!(features.contains(&"multitask".to_string())); } #[test] fn map_c_app_features_preserves_paging_facade_feature() { - let features = map_c_app_features(&strings(&["paging"]), &[]); + let features = map_c_app_features(&strings(&["paging"]), &[]).unwrap(); assert_eq!(features, vec!["paging".to_string()]); } +#[test] +fn map_c_app_features_does_not_add_fd_for_higher_level_features() { + let features = map_c_app_features(&strings(&["fs"]), &[]).unwrap(); + + assert_eq!(features, strings(&["fs"])); +} + #[test] fn pthread_mutex_header_matches_lockdep_smp_layout() { let header = pthread_mutex_header_contents(&strings(&["multitask", "lockdep", "smp"])); diff --git a/scripts/axbuild/src/arceos/config.rs b/scripts/axbuild/src/arceos/config.rs index 5a0a3a13de..369510c020 100644 --- a/scripts/axbuild/src/arceos/config.rs +++ b/scripts/axbuild/src/arceos/config.rs @@ -69,11 +69,12 @@ pub(crate) fn ensure_default_build_config_for_target( return Ok(None); } - let Some(board) = board::default_qemu_board_for_target(workspace_root, package, target)? else { + let Some(board) = board::default_qemu_board(workspace_root, target, Some(package))? else { return Ok(None); }; + // This only materializes a missing build config. The command dispatcher owns + // snapshot persistence, so implicit config creation cannot alter it here. write_board_to_default_build_config_at(build_config_path, &board)?; - update_snapshot_for_board(workspace_root, &board, build_config_path)?; Ok(Some(board)) } @@ -212,7 +213,7 @@ log = "Info" } #[test] - fn ensure_default_build_config_uses_matching_qemu_board_and_resets_runtime_config() { + fn ensure_default_build_config_uses_matching_qemu_board_without_changing_snapshot() { let root = tempdir().unwrap(); write_workspace(root.path()); let source = r#" @@ -248,8 +249,9 @@ log = "Warn" assert_eq!(board.unwrap().name, "qemu-aarch64"); assert_eq!(fs::read_to_string(&output).unwrap(), source); - let snapshot = ArceosCommandSnapshot::load(root.path()).unwrap(); - assert_eq!(snapshot.qemu.qemu_config, None); - assert_eq!(snapshot.uboot.uboot_config, None); + assert_eq!( + ArceosCommandSnapshot::load(root.path()).unwrap(), + existing_snapshot + ); } } diff --git a/scripts/axbuild/src/arceos/mod.rs b/scripts/axbuild/src/arceos/mod.rs index 61f5e9bc3d..8feb332c6c 100644 --- a/scripts/axbuild/src/arceos/mod.rs +++ b/scripts/axbuild/src/arceos/mod.rs @@ -1,15 +1,17 @@ use std::path::{Path, PathBuf}; -use anyhow::{Context, bail}; +use anyhow::{Context, anyhow, bail}; use clap::{Args, Subcommand}; -use log::warn; use ostool::{ board::{RunBoardOptions, config::BoardRunConfig}, build::config::Cargo, }; use crate::{ - context::{AppContext, BuildCliArgs, ResolvedBuildRequest, SnapshotPersistence}, + context::{ + AppContext, BuildCliArgs, ResolvedBuildRequest, SnapshotPersistence, + resolve_arceos_arch_and_target, + }, test::host_http::HostHttpServerGuard, }; @@ -178,8 +180,26 @@ impl ArceOS { } async fn qemu(&mut self, args: ArgsQemu) -> anyhow::Result<()> { + let mut build_args = BuildCliArgs::from(&args.build); + if build_args.package.is_none() && build_args.config.is_none() { + // Bare `arceos qemu` is a command-level default: select the matching + // board template, whose package and features define the runnable app. + // Explicit package/config selectors remain fully authoritative. + let (_, target) = + resolve_arceos_arch_and_target(build_args.arch.clone(), build_args.target.clone())?; + let board = board::default_qemu_board(self.app.workspace_root(), &target, None)? + .ok_or_else(|| { + anyhow!( + "missing ArceOS QEMU default config for target `{target}` under {}", + board::board_dir(self.app.workspace_root()) + .map(|path| path.display().to_string()) + .unwrap_or_else(|_| "os/arceos/configs/board".to_string()) + ) + })?; + build_args.config = Some(board.path); + } let request = self.prepare_request( - (&args.build).into(), + build_args, args.qemu_config, None, SnapshotPersistence::Store, @@ -286,7 +306,7 @@ impl ArceOS { request: &ResolvedBuildRequest, cargo: &Cargo, ) -> anyhow::Result> { - let mut qemu = match request.qemu_config.as_deref() { + let qemu = match request.qemu_config.as_deref() { Some(path) => self .app .read_qemu_config_from_path_for_cargo(cargo, path) @@ -301,9 +321,6 @@ impl ArceOS { .map(Some)? } }; - if let Some(qemu) = qemu.as_mut() { - crate::test::qemu::apply_dynamic_platform_qemu_boot(qemu, cargo); - } Ok(qemu) } @@ -398,7 +415,6 @@ impl ArceOS { if !extra_rustflags.is_empty() { bail!("ArceOS board extra rustflags are only supported for RustStd packages"); } - let request = c_app_internal_request(&request); let cargo = build::load_c_app_cargo_config(&request)?; let board_config = self .load_board_config(&cargo, board_config_path.as_deref()) @@ -456,7 +472,6 @@ impl ArceOS { .map(|_| ()) } build::ArceosBuildMode::AppC { app_dir, app_name } => { - let request = c_app_internal_request(&request); let output = self.build_c_app_request(&request, app_dir, app_name)?; println!("Built ArceOS C app ELF: {}", output.elf_path.display()); Ok(()) @@ -506,7 +521,6 @@ impl ArceOS { app_name: String, ) -> anyhow::Result<()> { self.app.set_debug_mode(request.debug)?; - let request = c_app_internal_request(&request); let cargo = build::load_c_app_cargo_config(&request)?; let mut qemu = self .load_qemu_config(&request, &cargo) @@ -518,7 +532,6 @@ impl ArceOS { ) })?; let output = self.build_c_app_request(&request, app_dir, app_name)?; - crate::test::qemu::apply_dynamic_platform_qemu_boot(&mut qemu, &cargo); // See `run_qemu_request_with_cargo`: default ArceOS QEMU keeps a FAT32 rootfs. crate::test::qemu::apply_smp_qemu_arg(&mut qemu, request.smp); rootfs::prepare_default_qemu_fat32_rootfs(self.app.workspace_root(), &qemu)?; @@ -536,7 +549,6 @@ impl ArceOS { app_name: String, ) -> anyhow::Result<()> { self.app.set_debug_mode(request.debug)?; - let request = c_app_internal_request(&request); let cargo = build::load_c_app_cargo_config(&request)?; let uboot = self .load_uboot_config(&request, &cargo) @@ -553,22 +565,6 @@ impl ArceOS { } } -fn warn_if_c_app_package_override(request: &ResolvedBuildRequest) { - if request.package != "ax-libc" { - warn!( - "ArceOS C app build ignores --package {}; using ax-libc internally", - request.package - ); - } -} - -fn c_app_internal_request(request: &ResolvedBuildRequest) -> ResolvedBuildRequest { - warn_if_c_app_package_override(request); - let mut request = request.clone(); - request.package = "ax-libc".to_string(); - request -} - pub(crate) fn default_qemu_config_template_path(workspace_root: &Path, arch: &str) -> PathBuf { workspace_root.join(format!("os/arceos/configs/qemu/qemu-{arch}.toml")) } @@ -576,6 +572,7 @@ pub(crate) fn default_qemu_config_template_path(workspace_root: &Path, arch: &st #[cfg(test)] mod tests { use clap::Parser; + use ostool::run::qemu::QemuConfig; use tempfile::tempdir; use super::*; @@ -675,6 +672,7 @@ mod tests { let board = board::load_board_file(&board_path).unwrap(); assert_eq!(board.package, "arceos-helloworld"); assert_eq!(board.target, target); + assert!(board.build_config.build_info.features.is_empty()); } } diff --git a/scripts/axbuild/src/arceos/rootfs.rs b/scripts/axbuild/src/arceos/rootfs.rs index 3e368c44db..3de5c7dedc 100644 --- a/scripts/axbuild/src/arceos/rootfs.rs +++ b/scripts/axbuild/src/arceos/rootfs.rs @@ -135,7 +135,6 @@ pub(super) async fn qemu_with_explicit_rootfs( .load_qemu_config(&request, &cargo) .await? .unwrap_or_default(); - qemu_test::apply_dynamic_platform_qemu_boot(&mut qemu, &cargo); patch_qemu_rootfs(&mut qemu, &rootfs); qemu_test::apply_smp_qemu_arg(&mut qemu, request.smp); arceos diff --git a/scripts/axbuild/src/arceos/test/c_qemu.rs b/scripts/axbuild/src/arceos/test/c_qemu.rs index 87568354e3..ff2c88f355 100644 --- a/scripts/axbuild/src/arceos/test/c_qemu.rs +++ b/scripts/axbuild/src/arceos/test/c_qemu.rs @@ -212,7 +212,6 @@ async fn build_and_run_c_test( None, SnapshotPersistence::Discard, )?; - let cargo = build::load_c_app_cargo_config(&request)?; let input = c_test_build_input( app_dir, app_name, @@ -222,8 +221,7 @@ async fn build_and_run_c_test( build_config.build_info.features.clone(), ); let output = cbuild::build_c_app(&workspace_root, &request, &input)?; - let mut qemu = qemu_config; - qemu_test::apply_dynamic_platform_qemu_boot(&mut qemu, &cargo); + let qemu = qemu_config; rootfs::prepare_default_qemu_fat32_rootfs(arceos.app.workspace_root(), &qemu)?; let _host_http_server = qemu_test::load_qemu_case_host_http_server(&test.qemu_config_path)? .as_ref() diff --git a/scripts/axbuild/src/arceos/test/rust_qemu.rs b/scripts/axbuild/src/arceos/test/rust_qemu.rs index 7aa9bedf07..ac1c3798cc 100644 --- a/scripts/axbuild/src/arceos/test/rust_qemu.rs +++ b/scripts/axbuild/src/arceos/test/rust_qemu.rs @@ -169,6 +169,8 @@ fn is_lockdep_detect_feature(feature: &str) -> bool { } fn add_cargo_feature(cargo: &mut Cargo, feature: &str) { + // The feature comes from the explicitly selected ArceOS test case; the + // normal suite path never adds test-specific capabilities. if !cargo.features.iter().any(|existing| existing == feature) { cargo.features.push(feature.to_string()); cargo.features.sort(); @@ -453,6 +455,19 @@ BT 0 ip=0x1 fp=0x2 ); } + #[test] + fn arceos_rust_aarch64_qemu_config_converts_high_half_kernel_to_bin() { + let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../test-suit/arceos/rust"); + let qemu_path = root.join("qemu-aarch64.toml"); + let config: QemuConfig = + toml::from_str(&std::fs::read_to_string(qemu_path).unwrap()).unwrap(); + + assert!( + config.to_bin, + "the AArch64 kernel is linked at a high-half address and QEMU must load its raw BIN" + ); + } + #[test] fn arceos_rust_panic_path_qemu_uses_panic_backtrace_result_regex() { let mut cargo = rust_test_cargo_for_target("x86_64-unknown-none"); diff --git a/scripts/axbuild/src/axvisor/build/features.rs b/scripts/axbuild/src/axvisor/build/features.rs index b1a85119a1..a68c3ea025 100644 --- a/scripts/axbuild/src/axvisor/build/features.rs +++ b/scripts/axbuild/src/axvisor/build/features.rs @@ -1,26 +1,5 @@ use anyhow::anyhow; -use super::metadata::platform_feature_names; - -pub(super) fn normalize_axvisor_feature_surface( - features: &mut Vec, - target: &str, - metadata: &cargo_metadata::Metadata, -) -> anyhow::Result<()> { - let _ = target; - let known_platforms = platform_feature_names(metadata); - retain_non_platform_features(features, &known_platforms); - Ok(()) -} - -fn retain_non_platform_features(features: &mut Vec, known_platforms: &[String]) { - features.retain(|feature| { - nested_platform_feature_name(feature, known_platforms).is_none() - && ax_hal_platform_feature_name(feature, known_platforms).is_none() - && !known_platforms.iter().any(|platform| platform == feature) - }); -} - pub(super) fn reject_unsupported_nested_platform_features( features: &[String], known_platforms: &[String], @@ -46,10 +25,6 @@ pub(super) fn reject_unsupported_nested_platform_features( Ok(()) } -pub(super) fn remove_dynamic_platform_features(features: &mut Vec) { - features.retain(|feature| !is_removed_dynamic_platform_feature(feature)); -} - pub(super) fn is_removed_dynamic_platform_feature(feature: &str) -> bool { matches!( feature, @@ -72,15 +47,6 @@ fn nested_platform_feature_name<'a>( .filter(|name| is_platform_control_feature(name, known_platforms)) } -fn ax_hal_platform_feature_name<'a>( - feature: &'a str, - known_platforms: &[String], -) -> Option<&'a str> { - feature - .strip_prefix("ax-hal/") - .filter(|name| known_platforms.iter().any(|platform| platform == name)) -} - fn is_platform_control_feature(name: &str, known_platforms: &[String]) -> bool { name == "plat-dyn" || known_platforms.iter().any(|platform| platform == name) } diff --git a/scripts/axbuild/src/axvisor/build/load.rs b/scripts/axbuild/src/axvisor/build/load.rs index 40bca86c4a..a9b084bcf5 100644 --- a/scripts/axbuild/src/axvisor/build/load.rs +++ b/scripts/axbuild/src/axvisor/build/load.rs @@ -127,7 +127,7 @@ fn load_or_create_missing_build_config( return Ok(loaded); } - let default_build_info = super::default_axvisor_build_info(); + let default_build_info = super::config::default_axvisor_build_info(); fs::write( &request.build_info_path, toml::to_string_pretty(&default_build_info)?, diff --git a/scripts/axbuild/src/axvisor/build/mod.rs b/scripts/axbuild/src/axvisor/build/mod.rs index 0499e7015a..f165770364 100644 --- a/scripts/axbuild/src/axvisor/build/mod.rs +++ b/scripts/axbuild/src/axvisor/build/mod.rs @@ -2,7 +2,6 @@ mod config; mod features; mod load; mod metadata; -mod x86; #[cfg(test)] mod tests; @@ -20,21 +19,12 @@ pub(crate) use load::{ use ostool::build::config::Cargo; use self::{ - config::LoadedAxvisorBuildConfig, - features::{ - normalize_axvisor_feature_surface, reject_unsupported_nested_platform_features, - remove_dynamic_platform_features, - }, - load::load_build_config, - metadata::platform_feature_names, + config::LoadedAxvisorBuildConfig, features::reject_unsupported_nested_platform_features, + load::load_build_config, metadata::platform_feature_names, }; pub use crate::build::LogLevel; use crate::context::ResolvedAxvisorRequest; -pub(crate) fn default_axvisor_build_info() -> AxvisorBuildInfo { - config::default_axvisor_build_info() -} - pub(crate) fn workspace_root_from_axvisor_dir(axvisor_dir: &Path) -> PathBuf { load::workspace_root_from_axvisor_dir(axvisor_dir) } @@ -52,15 +42,9 @@ fn to_cargo_config( ) -> anyhow::Result { config.target = request.target.clone(); let makefile_features = crate::build::makefile_features_from_env(); - crate::build::apply_makefile_features_with_metadata( - &mut config.build_info, - &request.package, - &makefile_features, - metadata, - ); + crate::build::apply_makefile_features(&mut config.build_info, &makefile_features)?; let known_platforms = platform_feature_names(metadata); reject_unsupported_nested_platform_features(&config.build_info.features, &known_platforms)?; - normalize_axvisor_feature_surface(&mut config.build_info.features, &config.target, metadata)?; let mut cargo = config .build_info .into_prepared_base_cargo_config_with_metadata( @@ -68,19 +52,16 @@ fn to_cargo_config( &config.target, metadata, )?; - remove_dynamic_platform_features(&mut cargo.features); - patch_axvisor_cargo_config(&mut cargo, request, metadata, &config.vm_configs)?; + patch_axvisor_cargo_config(&mut cargo, request, &config.vm_configs)?; Ok(cargo) } fn patch_axvisor_cargo_config( cargo: &mut Cargo, request: &ResolvedAxvisorRequest, - metadata: &cargo_metadata::Metadata, config_vmconfigs: &[PathBuf], ) -> anyhow::Result<()> { cargo.package = request.package.clone(); - cargo.to_bin = default_axvisor_to_bin(&request.arch); ensure_axvisor_bin_arg(&mut cargo.args); cargo .env @@ -88,9 +69,6 @@ fn patch_axvisor_cargo_config( cargo .env .insert("AX_TARGET".to_string(), request.target.clone()); - normalize_axvisor_feature_surface(&mut cargo.features, &request.target, metadata)?; - remove_dynamic_platform_features(&mut cargo.features); - let vmconfigs = if request.vmconfigs.is_empty() { config_vmconfigs .iter() @@ -108,9 +86,6 @@ fn patch_axvisor_cargo_config( ); } - if request.arch == "x86_64" { - x86::normalize_backend_features(&mut cargo.features)?; - } cargo.features.sort(); cargo.features.dedup(); Ok(()) @@ -128,10 +103,6 @@ fn resolve_build_config_vmconfig_path(request: &ResolvedAxvisorRequest, path: &P workspace_root.join(path) } -fn default_axvisor_to_bin(arch: &str) -> bool { - !matches!(arch, "x86_64" | "loongarch64") -} - fn ensure_axvisor_bin_arg(args: &mut Vec) { if args.iter().any(|arg| arg == "--bin") { return; diff --git a/scripts/axbuild/src/axvisor/build/tests.rs b/scripts/axbuild/src/axvisor/build/tests.rs index a1df1af8c8..75a294af96 100644 --- a/scripts/axbuild/src/axvisor/build/tests.rs +++ b/scripts/axbuild/src/axvisor/build/tests.rs @@ -67,6 +67,29 @@ fn request(path: PathBuf, arch: &str, target: &str) -> ResolvedAxvisorRequest { } } +#[test] +fn axvisor_ax_std_dependency_declares_std_compat() { + let metadata = crate::build::workspace_metadata().unwrap(); + let package = metadata + .packages + .iter() + .find(|package| package.name == AXVISOR_PACKAGE) + .unwrap(); + let ax_std = package + .dependencies + .iter() + .find(|dependency| dependency.name == "ax-std") + .unwrap(); + + assert!( + ax_std + .features + .iter() + .any(|feature| feature == "std-compat"), + "Axvisor must declare ax-std/std-compat in its dependency instead of relying on axbuild" + ); +} + #[test] fn resolve_build_info_path_uses_default_axvisor_location() { let root = tempdir().unwrap(); @@ -211,6 +234,44 @@ log = "Info" ); } +#[test] +fn load_cargo_config_does_not_select_an_x86_backend() { + let root = tempdir().unwrap(); + let config_path = root.path().join("build-x86_64.toml"); + fs::write( + &config_path, + r#" +features = [] +log = "Info" +"#, + ) + .unwrap(); + + let cargo = load_cargo_config(&request(config_path, "x86_64", "x86_64-unknown-none")).unwrap(); + + assert!(!cargo.features.contains(&"vmx".to_string())); + assert!(!cargo.features.contains(&"svm".to_string())); +} + +#[test] +fn load_cargo_config_forwards_explicit_x86_svm_backend() { + let root = tempdir().unwrap(); + let config_path = root.path().join("build-x86_64-svm.toml"); + fs::write( + &config_path, + r#" +features = ["svm"] +log = "Info" +"#, + ) + .unwrap(); + + let cargo = load_cargo_config(&request(config_path, "x86_64", "x86_64-unknown-none")).unwrap(); + + assert!(cargo.features.contains(&"svm".to_string())); + assert!(!cargo.features.contains(&"vmx".to_string())); +} + #[test] fn load_target_from_board_config_reads_target() { let root = tempdir().unwrap(); @@ -518,7 +579,7 @@ log = "Info" } #[test] -fn load_cargo_config_keeps_loongarch_dynamic_axvisor_as_elf() { +fn load_cargo_config_prepares_loongarch_dynamic_axvisor_runtime_artifact() { let root = tempdir().unwrap(); let config_path = root.path().join(".build.toml"); fs::write( diff --git a/scripts/axbuild/src/axvisor/build/x86.rs b/scripts/axbuild/src/axvisor/build/x86.rs deleted file mode 100644 index a5b282526e..0000000000 --- a/scripts/axbuild/src/axvisor/build/x86.rs +++ /dev/null @@ -1,171 +0,0 @@ -use anyhow::anyhow; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum VirtualizationBackend { - Vmx, - Svm, -} - -impl VirtualizationBackend { - fn feature(self) -> &'static str { - match self { - Self::Vmx => "vmx", - Self::Svm => "svm", - } - } - - fn from_feature(feature: &str) -> Option { - match feature { - "vmx" | "axvm/vmx" => Some(Self::Vmx), - "svm" | "axvm/svm" => Some(Self::Svm), - _ => None, - } - } -} - -pub(super) fn normalize_backend_features(features: &mut Vec) -> anyhow::Result<()> { - normalize_backend_features_with(features, detect_host_backend) -} - -fn normalize_backend_features_with( - features: &mut Vec, - detect_backend: impl FnOnce() -> anyhow::Result, -) -> anyhow::Result<()> { - let has_vmx = features.iter().any(|feature| { - VirtualizationBackend::from_feature(feature) == Some(VirtualizationBackend::Vmx) - }); - let has_svm = features.iter().any(|feature| { - VirtualizationBackend::from_feature(feature) == Some(VirtualizationBackend::Svm) - }); - - match (has_vmx, has_svm) { - (true, true) => Err(anyhow!( - "x86_64 Axvisor features `vmx` and `svm` are mutually exclusive" - )), - (true, false) | (false, true) => Ok(()), - (false, false) => { - let backend = detect_backend()?; - println!( - "Auto-selected x86_64 virtualization backend: {}", - backend.feature() - ); - features.push(backend.feature().to_string()); - Ok(()) - } - } -} - -fn detect_host_backend() -> anyhow::Result { - if let Ok(value) = std::env::var("AXVISOR_X86_BACKEND") { - return parse_backend(&value); - } - - detect_host_backend_from_cpuid() -} - -fn parse_backend(value: &str) -> anyhow::Result { - match value.trim().to_ascii_lowercase().as_str() { - "vmx" | "intel" => Ok(VirtualizationBackend::Vmx), - "svm" | "amd" => Ok(VirtualizationBackend::Svm), - other => Err(anyhow!( - "invalid AXVISOR_X86_BACKEND value `{other}`; expected `vmx`/`intel` or `svm`/`amd`" - )), - } -} - -#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -fn detect_host_backend_from_cpuid() -> anyhow::Result { - let cpuid = raw_cpuid::CpuId::new(); - let vendor = cpuid - .get_vendor_info() - .ok_or_else(|| anyhow!("failed to read x86 CPUID vendor information"))?; - - match vendor.as_str() { - "GenuineIntel" => Ok(VirtualizationBackend::Vmx), - "AuthenticAMD" => Ok(VirtualizationBackend::Svm), - _ => Err(anyhow!( - "unsupported x86 CPU vendor `{vendor}` for automatic Axvisor backend selection; set \ - AXVISOR_X86_BACKEND=vmx or AXVISOR_X86_BACKEND=svm to override" - )), - } -} - -#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] -fn detect_host_backend_from_cpuid() -> anyhow::Result { - Err(anyhow!( - "cannot auto-select x86_64 Axvisor virtualization backend on non-x86 host; set \ - AXVISOR_X86_BACKEND=vmx or AXVISOR_X86_BACKEND=svm" - )) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn backend_auto_selects_vmx_when_missing() { - let mut features = vec!["fs".to_string()]; - - normalize_backend_features_with(&mut features, || Ok(VirtualizationBackend::Vmx)).unwrap(); - - assert!(features.contains(&"vmx".to_string())); - assert!(!features.contains(&"svm".to_string())); - } - - #[test] - fn backend_auto_selects_svm_when_missing() { - let mut features = vec!["fs".to_string()]; - - normalize_backend_features_with(&mut features, || Ok(VirtualizationBackend::Svm)).unwrap(); - - assert!(features.contains(&"svm".to_string())); - assert!(!features.contains(&"vmx".to_string())); - } - - #[test] - fn backend_keeps_explicit_choice() { - let mut features = vec!["svm".to_string()]; - - normalize_backend_features_with(&mut features, || Ok(VirtualizationBackend::Vmx)).unwrap(); - - assert!(features.contains(&"svm".to_string())); - assert!(!features.contains(&"vmx".to_string())); - } - - #[test] - fn backend_keeps_nested_axvm_choice() { - let mut features = vec!["axvm/svm".to_string()]; - - normalize_backend_features_with(&mut features, || Ok(VirtualizationBackend::Vmx)).unwrap(); - - assert!(features.contains(&"axvm/svm".to_string())); - assert!(!features.contains(&"vmx".to_string())); - } - - #[test] - fn backend_rejects_conflicting_features() { - let mut features = vec!["vmx".to_string(), "svm".to_string()]; - - let err = normalize_backend_features_with(&mut features, || Ok(VirtualizationBackend::Vmx)) - .unwrap_err(); - - assert!(err.to_string().contains("mutually exclusive")); - } - - #[test] - fn backend_rejects_conflicting_nested_features() { - let mut features = vec!["vmx".to_string(), "axvm/svm".to_string()]; - - let err = normalize_backend_features_with(&mut features, || Ok(VirtualizationBackend::Vmx)) - .unwrap_err(); - - assert!(err.to_string().contains("mutually exclusive")); - } - - #[test] - fn parses_backend_override() { - assert_eq!(parse_backend("intel").unwrap(), VirtualizationBackend::Vmx); - assert_eq!(parse_backend("svm").unwrap(), VirtualizationBackend::Svm); - assert!(parse_backend("unknown").is_err()); - } -} diff --git a/scripts/axbuild/src/axvisor/rootfs.rs b/scripts/axbuild/src/axvisor/rootfs.rs index 92bf782dcf..74ba9eb5b8 100644 --- a/scripts/axbuild/src/axvisor/rootfs.rs +++ b/scripts/axbuild/src/axvisor/rootfs.rs @@ -11,12 +11,12 @@ use std::{ path::{Path, PathBuf}, }; -use anyhow::{Context, anyhow}; +use anyhow::{anyhow, bail}; use ostool::{build::config::Cargo, run::qemu::QemuConfig}; use serde::Deserialize; use super::{Axvisor, build}; -use crate::{context::ResolvedAxvisorRequest, rootfs, test::qemu as qemu_test}; +use crate::{context::ResolvedAxvisorRequest, rootfs}; #[derive(Deserialize)] struct VmRootfsProbe { @@ -29,7 +29,7 @@ struct VmKernelRootfsProbe { } pub(super) async fn qemu(axvisor: &mut Axvisor, args: super::ArgsQemu) -> anyhow::Result<()> { - let mut request = axvisor.prepare_request( + let request = axvisor.prepare_request( (&args.build).into(), args.qemu_config, None, @@ -52,96 +52,24 @@ pub(super) async fn qemu(axvisor: &mut Axvisor, args: super::ArgsQemu) -> anyhow explicit_rootfs.as_deref(), ) .await?; - prepare_loongarch_linux_vmconfigs( - &mut request, - axvisor.app.workspace_root(), - explicit_rootfs.as_deref(), - )?; - let cargo = build::load_cargo_config(&request)?; + let mut cargo = build::load_cargo_config(&request)?; let qemu = load_patched_qemu_config(axvisor, &request, &cargo, explicit_rootfs.as_deref()).await?; + cargo.to_bin = qemu_to_bin_requested(&qemu)?; axvisor .app .qemu(cargo, request.build_info_path, Some(qemu)) .await } -pub(crate) fn prepare_loongarch_linux_vmconfigs( - request: &mut ResolvedAxvisorRequest, - workspace_root: &Path, - _explicit_rootfs: Option<&Path>, -) -> anyhow::Result<()> { - if request.arch != "loongarch64" || request.vmconfigs.is_empty() { - return Ok(()); - } - - let firmware_path = loongarch_uefi_firmware_path(workspace_root).ok_or_else(|| { - anyhow!("LoongArch UEFI firmware image was not found; expected ostool OVMF code.fd") - })?; - let out_dir = workspace_root.join("tmp/axbuild/axvisor/loongarch64"); - let mut prepared_vmconfigs = Vec::with_capacity(request.vmconfigs.len()); - - for vmconfig in &request.vmconfigs { - let content = fs::read_to_string(vmconfig) - .map_err(|e| anyhow!("failed to read vm config {}: {e}", vmconfig.display()))?; - let value: toml::Value = toml::from_str(&content) - .map_err(|e| anyhow!("failed to parse vm config {}: {e}", vmconfig.display()))?; - let guest_kernel = value - .get("kernel") - .and_then(|kernel| kernel.get("kernel_path")) - .and_then(|path| path.as_str()); - - if guest_kernel != Some("/guest/linux/linux-qemu") { - prepared_vmconfigs.push(vmconfig.clone()); - continue; - } - - let prepared_vmconfig = out_dir.join( - vmconfig - .file_name() - .unwrap_or_else(|| std::ffi::OsStr::new("linux-rootfs-smp1.toml")), +fn qemu_to_bin_requested(qemu: &QemuConfig) -> anyhow::Result { + if qemu.uefi && !qemu.to_bin { + bail!( + "QEMU config enables UEFI but does not request `to_bin = true`; set `to_bin = true` \ + explicitly" ); - fs::create_dir_all(&out_dir) - .with_context(|| format!("failed to create {}", out_dir.display()))?; - let patched = replace_toml_string_value( - &content, - "uefi_firmware_path", - &firmware_path.display().to_string(), - ); - fs::write(&prepared_vmconfig, patched) - .with_context(|| format!("failed to write {}", prepared_vmconfig.display()))?; - prepared_vmconfigs.push(prepared_vmconfig); } - - request.vmconfigs = prepared_vmconfigs; - Ok(()) -} - -fn loongarch_uefi_firmware_path(workspace_root: &Path) -> Option { - [ - PathBuf::from("/tmp/ostool/ovmf/loongarch64/code.fd"), - workspace_root.join("tmp/ostool/ovmf/loongarch64/code.fd"), - workspace_root.join("tmp/loongarch-uefi-stage1/assets/qemu-binary/QEMU_EFI.fd"), - ] - .into_iter() - .find(|path| path.exists()) -} - -fn replace_toml_string_value(content: &str, key: &str, value: &str) -> String { - let prefix = format!("{key} = "); - content - .lines() - .map(|line| { - if line.trim_start().starts_with(&prefix) { - let indent_len = line.len() - line.trim_start().len(); - format!("{}{}\"{}\"", &line[..indent_len], prefix, value) - } else { - line.to_string() - } - }) - .collect::>() - .join("\n") - + "\n" + Ok(qemu.to_bin) } pub(super) async fn load_patched_qemu_config( @@ -163,7 +91,6 @@ pub(super) async fn load_patched_qemu_config( axvisor.app.workspace_root(), explicit_rootfs, )?; - qemu_test::apply_dynamic_platform_qemu_boot(&mut qemu, cargo); Ok(qemu) } @@ -516,4 +443,15 @@ kernel_path = "{}" Some(explicit) ); } + + #[test] + fn qemu_uefi_without_to_bin_is_rejected() { + let qemu = QemuConfig { + uefi: true, + to_bin: false, + ..Default::default() + }; + + assert!(qemu_to_bin_requested(&qemu).is_err()); + } } diff --git a/scripts/axbuild/src/axvisor/test/assets.rs b/scripts/axbuild/src/axvisor/test/assets.rs index fbc9aad66c..ecc47554d8 100644 --- a/scripts/axbuild/src/axvisor/test/assets.rs +++ b/scripts/axbuild/src/axvisor/test/assets.rs @@ -1,131 +1,4 @@ -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 { - 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(), - 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") -} +use crate::test::case as test_case; pub(super) fn axvisor_case_asset_config() -> test_case::CaseAssetConfig { test_case::CaseAssetConfig { diff --git a/scripts/axbuild/src/axvisor/test/qemu.rs b/scripts/axbuild/src/axvisor/test/qemu.rs index fc3b24fd63..4bb564efc7 100644 --- a/scripts/axbuild/src/axvisor/test/qemu.rs +++ b/scripts/axbuild/src/axvisor/test/qemu.rs @@ -9,11 +9,7 @@ use ostool::{build::config::Cargo, run::qemu::QemuConfig}; use super::{ AXVISOR_NORMAL_GROUP, AxvisorQemuCase, - assets::{ - arceos_x86_64_guest_elf_path, arceos_x86_64_guest_request, axvisor_case_asset_config, - build_group_needs_arceos_x86_64_guest, case_needs_arceos_x86_64_guest, - inject_arceos_x86_64_guest_image, - }, + assets::axvisor_case_asset_config, discover_qemu_cases, discovery::{ discover_test_group_names, qemu_list_error_is_ignorable, test_suite_dir, test_suite_root, @@ -126,22 +122,7 @@ impl Axvisor { for build_group in &mut build_groups { rootfs::ensure_qemu_rootfs_ready(&build_group.request, self.app.workspace_root(), None) .await?; - rootfs::prepare_loongarch_linux_vmconfigs( - &mut build_group.request, - self.app.workspace_root(), - None, - )?; build_group.cargo = build::load_cargo_config(&build_group.request)?; - if build_group_needs_arceos_x86_64_guest(&build_group.request) { - self.build_arceos_x86_64_guest_image() - .await - .with_context(|| { - format!( - "failed to build ArceOS guest image for Axvisor qemu build group `{}`", - build_group.group.build_group - ) - })?; - } self.app .build( build_group.cargo.clone(), @@ -206,7 +187,7 @@ impl Axvisor { &case.build_config_path, &mut cargo_by_build_config, )?; - let mut qemu = self + let qemu = self .app .read_qemu_config_from_path_for_cargo(&cargo, &case.case.qemu_config_path) .await @@ -216,7 +197,6 @@ impl Axvisor { case.case.display_name ) })?; - test_qemu::apply_dynamic_platform_qemu_boot(&mut qemu, &cargo); test_qemu::validate_grouped_qemu_commands(&qemu, &case.case, "Axvisor")?; prepared.push(PreparedAxvisorQemuCase { case, qemu }); } @@ -273,7 +253,7 @@ impl Axvisor { test_qemu::apply_timeout_scale(&mut qemu); let rootfs_path = rootfs::qemu_rootfs_path(request, self.app.workspace_root(), None)?; - let mut prepared_assets = test_case::prepare_case_assets( + let prepared_assets = test_case::prepare_case_assets( self.app.workspace_root(), &request.arch, &request.target, @@ -282,24 +262,14 @@ impl Axvisor { asset_config.clone(), ) .await?; - if case_needs_arceos_x86_64_guest(request, case) { - inject_arceos_x86_64_guest_image( - self.app.workspace_root(), - request, - case, - &mut prepared_assets, - ) - .with_context(|| { - format!( - "failed to prepare ArceOS guest image for Axvisor qemu case `{}`", - case.case.case.name - ) - })?; - } rootfs::patch_qemu_rootfs_path(&mut qemu, &prepared_assets.rootfs_path); qemu.args.extend(prepared_assets.extra_qemu_args.clone()); - let cargo = build::load_cargo_config(request)?; - test_qemu::apply_dynamic_platform_qemu_boot(&mut qemu, &cargo); + // UEFI needs a writable ESP for firmware variables. Keep the explicit + // snapshot isolation, but apply it per drive so QEMU does not make the + // `fat:rw` ESP read-only through the global `-snapshot` flag. + if qemu.uefi { + test_qemu::apply_drive_snapshot_without_global_snapshot(&mut qemu); + } Ok((qemu, prepared_assets)) } @@ -328,21 +298,6 @@ impl Axvisor { ) .await } - - async fn build_arceos_x86_64_guest_image(&mut self) -> anyhow::Result { - let request = arceos_x86_64_guest_request()?; - let cargo = crate::arceos::build::load_cargo_config(&request)?; - self.app - .build(cargo.clone(), request.build_info_path.clone()) - .await?; - - let elf_path = arceos_x86_64_guest_elf_path(self.app.workspace_root(), request.debug); - self.app - .prepare_elf_artifact(elf_path.clone(), true) - .await?; - - Ok(elf_path.with_extension("bin")) - } } fn qemu_group_vmconfigs( diff --git a/scripts/axbuild/src/axvisor/test/tests.rs b/scripts/axbuild/src/axvisor/test/tests.rs index 508e1b87e3..cb2425ebb0 100644 --- a/scripts/axbuild/src/axvisor/test/tests.rs +++ b/scripts/axbuild/src/axvisor/test/tests.rs @@ -3,6 +3,7 @@ use std::{ path::{Path, PathBuf}, }; +use ostool::run::qemu::QemuConfig; use tempfile::tempdir; use super::*; @@ -161,6 +162,71 @@ fn checked_in_test_build_vmconfigs_exist() { assert!(checked > 0); } +#[test] +fn nimbos_uefi_case_uses_uefi_host_boot() { + let workspace_root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../.."); + let path = workspace_root.join("test-suit/axvisor/uefi/qemu-nimbos/qemu-x86_64.toml"); + let config: QemuConfig = toml::from_str(&fs::read_to_string(path).unwrap()).unwrap(); + + assert!(config.uefi); + assert!(config.to_bin); +} + +#[test] +fn x86_hypervisor_backend_cases_request_raw_bin_artifacts() { + let workspace_root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../.."); + + for (backend, cpu_features) in [ + ( + "vmx", + &["+vmx-ept", "+vmx-unrestricted-guest", "+vmx-flexpriority"], + ), + ("svm", &["+svm", "+npt", "+nrip-save"]), + ] { + let path = workspace_root.join(format!( + "test-suit/axvisor/normal/qemu/smoke/qemu-x86_64-{backend}.toml" + )); + let config: QemuConfig = toml::from_str(&fs::read_to_string(&path).unwrap()).unwrap(); + + assert!( + config.uefi, + "{backend} smoke must boot the dynamic x86 host through UEFI" + ); + assert!( + config.to_bin, + "{backend} smoke must provide a raw BIN for the UEFI ESP" + ); + assert!( + !config.args.iter().any(|arg| arg == "-nodefaults"), + "{backend} UEFI smoke needs QEMU's default firmware devices" + ); + + let machine = qemu_argument_value(&config.args, "-machine"); + assert!( + !machine.contains("sata=off") && !machine.contains("i8042=off"), + "{backend} UEFI smoke must keep the firmware boot bus available" + ); + + let cpu = qemu_argument_value(&config.args, "-cpu"); + assert!(cpu.contains("-la57")); + for feature in cpu_features { + assert!( + cpu.contains(feature), + "{backend} smoke must enable the required CPU feature {feature}" + ); + } + } +} + +fn qemu_argument_value<'a>(args: &'a [String], option: &str) -> &'a str { + let index = args + .iter() + .position(|arg| arg == option) + .unwrap_or_else(|| panic!("missing QEMU option {option}")); + args.get(index + 1) + .unwrap_or_else(|| panic!("missing value for QEMU option {option}")) +} + #[test] fn parses_supported_arch_aliases() { assert_eq!( diff --git a/scripts/axbuild/src/backtrace/mod.rs b/scripts/axbuild/src/backtrace/mod.rs index a73d6b9b08..315ad6a7ca 100644 --- a/scripts/axbuild/src/backtrace/mod.rs +++ b/scripts/axbuild/src/backtrace/mod.rs @@ -15,7 +15,9 @@ pub(crate) use capture::write_raw_blocks_from_output; pub(crate) use capture::{ BacktraceBlockCapture, BacktraceQemuCapture, flush_pending_stream_symbolize, }; -pub(crate) use paths::{arceos_rust_elf_path, std_test_elf_path}; +#[cfg(test)] +pub(crate) use paths::arceos_rust_elf_path; +pub(crate) use paths::std_test_elf_path; pub(crate) use symbolize::{ BacktraceSymbolizeSession, SymbolizeAfterQemuOutcome, keep_qemu_log_from_env, maybe_symbolize_after_qemu, symbolize_captured_blocks_to_string, diff --git a/scripts/axbuild/src/build/info.rs b/scripts/axbuild/src/build/info.rs index 6ad71d1a33..74fb3923ad 100644 --- a/scripts/axbuild/src/build/info.rs +++ b/scripts/axbuild/src/build/info.rs @@ -1,3 +1,5 @@ +use anyhow::bail; + use super::*; pub(crate) fn env_truthy(env: &HashMap, key: &str) -> bool { @@ -95,19 +97,6 @@ pub(crate) const ARCEOS_LINKER_SCRIPT: &str = "linker.x"; pub(super) const STD_TARGET_DIR: &str = "std"; pub(super) const AXSTD_STD_PACKAGE: &str = "ax-std"; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(super) enum StdFeaturePrefixFamily { - AxStd, -} - -impl StdFeaturePrefixFamily { - fn prefix(self) -> &'static str { - match self { - Self::AxStd => "ax-std/", - } - } -} - #[derive(Debug, Clone, JsonSchema, Deserialize, Serialize, PartialEq)] pub struct BuildInfo { /// Environment variables to set during the build. @@ -151,12 +140,9 @@ impl BuildInfo { target: String, args: Vec, ) -> Cargo { - self.into_base_cargo_config_with_to_bin( - package, - target.clone(), - args, - default_to_bin_for_target(&target), - ) + // Keep the Cargo artifact as ELF by default. BIN conversion is an + // explicit runner/config concern and must not be inferred from target. + self.into_base_cargo_config_with_to_bin(package, target, args, false) } pub(crate) fn into_base_cargo_config_with_to_bin( @@ -203,7 +189,15 @@ impl BuildInfo { metadata: &Metadata, ) -> anyhow::Result { self.validated_max_cpu_num()?; - self.resolve_std_features_with_metadata(package, target, metadata); + self.validate_features()?; + self.resolve_std_features(); + // `max_cpu_num` is an explicit build setting. Propagate SMP only when + // the caller requested more than one CPU; package metadata never adds + // features implicitly. + if self.max_cpu_num.is_some_and(|max_cpu_num| max_cpu_num > 1) { + self.features.push("smp".to_string()); + self.resolve_std_features(); + } let std_target = std_build_target_for(target)?; let fake_lib_dir = std_fake_lib_dir(&std_target.target_name)?; let wrapper = std_linker_wrapper_path(&std_target.target_name, &fake_lib_dir)?; @@ -213,22 +207,14 @@ impl BuildInfo { std_target.cargo_args, ); cargo.env.extend(std_target.env); - prepare_std_build_env_for_package( - &mut cargo.env, - package, - target, - &cargo.features, - metadata, - )?; + // The std target wrapper needs the original kernel target. This is + // build context, not a Cargo feature or platform selection. + cargo + .env + .insert("AX_TARGET".to_string(), target.to_string()); let app_features = package_feature_names(package, metadata)?; let axstd_features = package_feature_names(AXSTD_STD_PACKAGE, metadata)?; - inject_arceos_feature_for_std_build(&mut cargo.features, &app_features); - pass_std_build_nested_features( - &mut cargo.env, - &mut cargo.features, - &app_features, - &axstd_features, - ); + pass_std_build_nested_features(&mut cargo.features, &app_features, &axstd_features); cargo.pre_build_cmds.push( std_fake_lib_prebuild_script_path(&std_target.target_name, &fake_lib_dir, &cargo.env)? .display() @@ -240,7 +226,6 @@ impl BuildInfo { .display() .to_string(), ); - cargo.to_bin = true; Ok(cargo) } @@ -249,117 +234,46 @@ impl BuildInfo { .features .iter() .map(|feature| normalize_std_feature(feature)) - .filter(|feature| !is_removed_dynamic_platform_feature(feature)) .collect(); self.features.sort(); self.features.dedup(); } - pub(super) fn resolve_std_features_with_metadata( - &mut self, - package: &str, - target: &str, - metadata: &Metadata, - ) { - let _ = target; - self.features - .extend(std_package_metadata_features(package, metadata)); - self.resolve_std_features(); - - if self.max_cpu_num.is_some_and(|max_cpu_num| max_cpu_num > 1) { - self.features.push("smp".to_string()); - } - self.features.push("smp".to_string()); - - self.resolve_std_features(); - } - - pub(crate) fn resolve_features_with_metadata( - &mut self, - package: &str, - target: &str, - metadata: &Metadata, - ) { - self.resolve_features_with_prefix_family( - package, - target, - detect_std_feature_prefix_family(package, metadata), - Some(metadata), - ); - } - - pub(super) fn resolve_features_with_prefix_family( - &mut self, - package: &str, - target: &str, - prefix_family: anyhow::Result, - metadata: Option<&Metadata>, - ) { - let prefix_family = self.resolve_std_feature_prefix_family(package, prefix_family); - let _ = (target, metadata); - - self.features - .retain(|feature| !matches!(feature.as_str(), "plat-dyn" | "ax-std/plat-dyn")); - + pub(crate) fn resolve_c_app_features(&mut self) -> anyhow::Result<()> { + self.validate_features()?; + // `max_cpu_num` is an explicit C build setting; expose the matching ax-std + // capability only when the caller requested more than one CPU. if self.max_cpu_num.is_some_and(|max_cpu_num| max_cpu_num > 1) { - self.features.push(format!("{}smp", prefix_family.prefix())); + self.features.push("ax-std/smp".to_string()); } - self.features.sort(); self.features.dedup(); + Ok(()) } - fn resolve_std_feature_prefix_family( - &self, - package: &str, - prefix_family: anyhow::Result, - ) -> StdFeaturePrefixFamily { - match prefix_family { - Ok(prefix_family) => prefix_family, - Err(err) => { - if let Some(prefix_family) = feature_family_from_existing_features(&self.features) { - return prefix_family; - } - warn!( - "failed to detect direct ax dependency for package {}: {}, defaulting to \ - ax-std feature prefix", - package, err - ); - StdFeaturePrefixFamily::AxStd - } + /// Reject compatibility aliases and removed platform controls instead of silently changing + /// the build contract selected by the caller. + pub(crate) fn validate_features(&self) -> anyhow::Result<()> { + for feature in &self.features { + self.validate_feature(feature)?; } + Ok(()) } - pub(crate) fn normalize_legacy_feature_aliases(&mut self) -> bool { - let mut changed = false; - - for feature in &mut self.features { - let normalized = normalize_legacy_feature_alias(feature); - if *feature != normalized { - *feature = normalized; - changed = true; - } + pub(crate) fn validate_feature(&self, feature: &str) -> anyhow::Result<()> { + if feature == "axstd" || feature.starts_with("axstd/") { + bail!( + "feature `{feature}` uses the removed `axstd` alias; use the declared Cargo \ + feature name instead" + ); } - - if changed { - self.features.sort(); - self.features.dedup(); - } - - changed - } - - #[cfg(test)] - pub(crate) fn resolve_features(&mut self, package: &str, target: &str) { - match workspace_metadata() { - Ok(metadata) => self.resolve_features_with_metadata(package, target, &metadata), - Err(err) => self.resolve_features_with_prefix_family( - package, - target, - Err(err.context("failed to load workspace metadata")), - None, - ), + if is_removed_dynamic_platform_feature(feature) { + bail!( + "feature `{feature}` is no longer supported; dynamic platform selection is \ + automatic, remove the feature from the selected configuration" + ); } + Ok(()) } pub(crate) fn validated_max_cpu_num(&self) -> anyhow::Result> { @@ -398,7 +312,7 @@ impl Default for BuildInfo { Self { env: HashMap::new(), log: LogLevel::Warn, - features: vec!["ax-std".to_string()], + features: Vec::new(), max_cpu_num: None, } } diff --git a/scripts/axbuild/src/build/mod.rs b/scripts/axbuild/src/build/mod.rs index 594663d1d0..356e387a0b 100644 --- a/scripts/axbuild/src/build/mod.rs +++ b/scripts/axbuild/src/build/mod.rs @@ -10,7 +10,7 @@ use std::{ use anyhow::{Context, bail}; use cargo_metadata::{Metadata, Package}; -use log::{info, warn}; +use log::info; use ostool::build::config::Cargo; pub use ostool::build::config::LogLevel; use schemars::JsonSchema; @@ -32,17 +32,16 @@ pub(crate) use info::{ ARCEOS_LINKER_SCRIPT, BuildInfo, append_encoded_rustflags, build_info_enables_backtrace_path, env_truthy, toolchain_rustflags_for_features, }; -use info::{AXSTD_STD_PACKAGE, PIE_TARGET_DIR, STD_TARGET_DIR, TARGET_JSON_ROOT}; +use info::{PIE_TARGET_DIR, STD_TARGET_DIR, TARGET_JSON_ROOT}; #[cfg(test)] pub(crate) use platform::parse_makefile_features; #[cfg(test)] pub(crate) use platform::workspace_metadata; use platform::*; pub(crate) use platform::{ - apply_makefile_features, apply_makefile_features_with_metadata, cached_workspace_metadata, - default_build_info_path_in_workspace, makefile_features_from_env, + apply_makefile_features, cached_workspace_metadata, default_build_info_path_in_workspace, + makefile_features_from_env, }; -pub(crate) use std_build::prepare_std_build_env; use std_build::*; #[cfg(test)] diff --git a/scripts/axbuild/src/build/platform.rs b/scripts/axbuild/src/build/platform.rs index 34812b35ed..74c06c7305 100644 --- a/scripts/axbuild/src/build/platform.rs +++ b/scripts/axbuild/src/build/platform.rs @@ -1,4 +1,4 @@ -use super::{info::StdFeaturePrefixFamily, *}; +use super::*; #[cfg(test)] pub(super) fn supports_platform_dynamic(target: &str) -> bool { @@ -8,34 +8,19 @@ pub(super) fn supports_platform_dynamic(target: &str) -> bool { || target.starts_with("x86_64-") } -pub(super) fn default_to_bin_for_target(target: &str) -> bool { - !target.starts_with("x86_64-") && !target.starts_with("loongarch64-") -} - -pub(super) fn normalize_legacy_feature_alias(feature: &str) -> String { - if feature == "axstd" { - "ax-std".to_string() - } else if let Some(rest) = feature.strip_prefix("axstd/") { - format!("ax-std/{rest}") - } else { - feature.to_string() - } -} - pub(super) fn normalize_std_feature(feature: &str) -> String { - let normalized = normalize_legacy_feature_alias(feature); - match normalized.as_str() { - "ax-std" => normalized, + match feature { + "ax-std" => feature.to_string(), feature if feature.starts_with("ax-std/") => feature .split_once('/') .map(|(_, feature)| feature.to_string()) - .unwrap_or_else(|| normalized.clone()), + .unwrap_or_else(|| feature.to_string()), feature if feature.starts_with("ax-hal/") || feature.starts_with("ax-driver/") || feature.starts_with("ax-runtime/") => { - normalized + feature.to_string() } feature => feature.to_string(), } @@ -44,7 +29,13 @@ pub(super) fn normalize_std_feature(feature: &str) -> String { pub(super) fn is_removed_dynamic_platform_feature(feature: &str) -> bool { matches!( feature, - "plat-dyn" | "ax-std/plat-dyn" | "ax-driver/plat-dyn" + "dyn-plat" + | "plat-dyn" + | "axplat-dyn" + | "ax-hal/plat-dyn" + | "ax-std/plat-dyn" + | "axvm/plat-dyn" + | "ax-driver/plat-dyn" ) } @@ -138,47 +129,11 @@ pub(crate) fn makefile_features_from_env() -> Vec { pub(crate) fn apply_makefile_features( build_info: &mut BuildInfo, - _package: &str, makefile_features: &[String], -) { - if makefile_features.is_empty() { - return; - } - apply_std_makefile_features(build_info, makefile_features); -} - -pub(crate) fn apply_makefile_features_with_metadata( - build_info: &mut BuildInfo, - _package: &str, - makefile_features: &[String], - _metadata: &Metadata, -) { - apply_std_makefile_features(build_info, makefile_features); -} - -#[cfg(test)] -pub(super) fn apply_makefile_features_with_prefix_family( - build_info: &mut BuildInfo, - _package: &str, - makefile_features: &[String], - _prefix_family: anyhow::Result, -) { - if makefile_features.is_empty() { - return; - } - - apply_std_makefile_features(build_info, makefile_features); -} - -pub(super) fn apply_std_makefile_features( - build_info: &mut BuildInfo, - makefile_features: &[String], -) { +) -> anyhow::Result<()> { for feature in makefile_features { + build_info.validate_feature(feature)?; let mapped = normalize_std_feature(feature); - if is_removed_dynamic_platform_feature(&mapped) { - continue; - } if !build_info .features .iter() @@ -187,6 +142,7 @@ pub(super) fn apply_std_makefile_features( build_info.features.push(mapped); } } + Ok(()) } pub(crate) fn default_build_info_path_in_workspace( @@ -200,18 +156,6 @@ pub(crate) fn default_build_info_path_in_workspace( .join(format!("build-{target}.toml")) } -pub(super) fn feature_family_from_existing_features( - features: &[String], -) -> Option { - if features - .iter() - .any(|feature| feature.starts_with("ax-std/")) - { - return Some(StdFeaturePrefixFamily::AxStd); - } - None -} - pub(crate) fn workspace_metadata() -> anyhow::Result { let manifest_path = workspace_manifest_path()?; workspace_metadata_root_manifest(&manifest_path) @@ -242,30 +186,6 @@ pub(super) fn workspace_package<'a>( .ok_or_else(|| anyhow::anyhow!("workspace package `{package}` not found")) } -pub(super) fn metadata_package<'a>(metadata: &'a Metadata, package: &str) -> Option<&'a Package> { - metadata.packages.iter().find(|pkg| pkg.name == package) -} - -pub(super) fn detect_std_feature_prefix_family( - package: &str, - metadata: &Metadata, -) -> anyhow::Result { - let package_info = workspace_package(metadata, package)?; - - let has_axstd = package_info - .dependencies - .iter() - .any(|dep| dep.name == "ax-std" || dep.rename.as_deref() == Some("ax-std")); - - if has_axstd { - Ok(StdFeaturePrefixFamily::AxStd) - } else { - Err(anyhow::anyhow!( - "package `{package}` must directly depend on `ax-std`" - )) - } -} - #[cfg(test)] pub(super) fn ax_hal_platform_feature_name<'a>( feature: &'a str, @@ -299,13 +219,6 @@ pub(super) struct AxplatMetadata { dynamic: bool, } -#[derive(Debug, Clone, Default, Deserialize)] -#[serde(default)] -#[serde(rename_all = "kebab-case")] -pub(super) struct AxstdMetadata { - features: Vec, -} - #[cfg(test)] #[derive(Debug, Clone)] pub(super) struct PlatformPackage { @@ -322,21 +235,6 @@ pub(super) fn platform_metadata(package: &Package) -> Option { .and_then(|metadata| serde_json::from_value(metadata).ok()) } -pub(super) fn axstd_metadata(package: &Package) -> Option { - package - .metadata - .get("axstd") - .cloned() - .and_then(|metadata| serde_json::from_value(metadata).ok()) -} - -pub(super) fn std_package_metadata_features(package: &str, metadata: &Metadata) -> Vec { - metadata_package(metadata, package) - .and_then(axstd_metadata) - .map(|metadata| metadata.features) - .unwrap_or_default() -} - #[cfg(test)] pub(super) fn platform_packages(metadata: &Metadata) -> Vec { metadata diff --git a/scripts/axbuild/src/build/std_build.rs b/scripts/axbuild/src/build/std_build.rs index f01c897df9..ff80fe544e 100644 --- a/scripts/axbuild/src/build/std_build.rs +++ b/scripts/axbuild/src/build/std_build.rs @@ -233,29 +233,7 @@ pub(super) fn std_target_json_path(target: &str) -> PathBuf { path.join(PIE_TARGET_DIR).join(format!("{target}.json")) } -pub(crate) fn prepare_std_build_env( - envs: &mut HashMap, - target: &str, - metadata: &Metadata, -) -> anyhow::Result<()> { - prepare_std_build_env_for_package(envs, AXSTD_STD_PACKAGE, target, &[], metadata) -} - -pub(super) fn prepare_std_build_env_for_package( - envs: &mut HashMap, - package: &str, - target: &str, - features: &[String], - metadata: &Metadata, -) -> anyhow::Result<()> { - envs.insert("AX_TARGET".to_string(), target.to_string()); - - let _ = (package, features, metadata); - Ok(()) -} - pub(super) fn pass_std_build_nested_features( - _envs: &mut HashMap, features: &mut Vec, app_features: &[String], axstd_features: &[String], @@ -264,9 +242,6 @@ pub(super) fn pass_std_build_nested_features( for feature in features.drain(..) { let feature = normalize_std_feature(&feature); - if is_removed_dynamic_platform_feature(&feature) { - continue; - } if matches!(feature.as_str(), "ax-std") { continue; } @@ -288,33 +263,12 @@ pub(super) fn pass_std_build_nested_features( } } - // Runtime-discovered platforms must map MMIO regions and initialize their - // interrupt controller before an application-specific feature can do so. - // Keep this baseline in every std-aware build so minimal applications can - // boot on QEMU without supplying platform plumbing themselves. - for feature in ["irq", "paging", "std-compat"] { - if axstd_feature_is_available(feature, axstd_features) { - cargo_features.push(format!("ax-std/{feature}")); - } - } - cargo_features.sort(); cargo_features.dedup(); *features = cargo_features; } -pub(super) fn inject_arceos_feature_for_std_build( - features: &mut Vec, - app_features: &[String], -) { - if app_features.iter().any(|feature| feature == "arceos") - && !features.iter().any(|feature| feature == "arceos") - { - features.push("arceos".to_string()); - } -} - pub(super) fn axstd_feature_name(feature: &str) -> &str { feature .strip_prefix("ax-hal/") diff --git a/scripts/axbuild/src/build/tests/info.rs b/scripts/axbuild/src/build/tests/info.rs index 569f057233..9d7b2c6153 100644 --- a/scripts/axbuild/src/build/tests/info.rs +++ b/scripts/axbuild/src/build/tests/info.rs @@ -20,6 +20,7 @@ fn build_info_enables_backtrace_matches_env_flags() { fn build_info_defaults_to_empty_env() { let info = BuildInfo::default(); assert!(info.env.is_empty()); + assert!(info.features.is_empty()); } #[test] diff --git a/scripts/axbuild/src/build/tests/metadata.rs b/scripts/axbuild/src/build/tests/metadata.rs index 9631b095ed..c511a3e6f3 100644 --- a/scripts/axbuild/src/build/tests/metadata.rs +++ b/scripts/axbuild/src/build/tests/metadata.rs @@ -1,14 +1,18 @@ use super::*; -use crate::build::info::StdFeaturePrefixFamily; #[test] -fn rejects_packages_without_ax_std_dependency() { - let workspace = temp_workspace("plain-app", "ax-api = \"0.1.0\"\n").unwrap(); +fn rejects_legacy_and_removed_platform_features() { + for feature in ["axstd", "axstd/net", "plat-dyn", "ax-std/plat-dyn"] { + let info = BuildInfo { + features: vec![feature.to_string()], + ..BuildInfo::default() + }; - let metadata = metadata_for_manifest(&workspace.join("Cargo.toml")); - let err = detect_std_feature_prefix_family("plain-app", &metadata).unwrap_err(); - - assert!(err.to_string().contains("must directly depend on `ax-std`")); + assert!( + info.validate_features().is_err(), + "{feature} must be rejected" + ); + } } #[test] @@ -23,9 +27,7 @@ fn std_build_maps_arceos_features_to_ax_std_dependency() { }; info.resolve_std_features(); - let mut envs = HashMap::new(); pass_std_build_nested_features( - &mut envs, &mut info.features, &[], &[ @@ -37,14 +39,8 @@ fn std_build_maps_arceos_features_to_ax_std_dependency() { assert_eq!( info.features, - vec![ - "ax-std/lockdep".to_string(), - "ax-std/smp".to_string(), - "ax-std/std-compat".to_string() - ] + vec!["ax-std/lockdep".to_string(), "ax-std/smp".to_string()] ); - assert!(envs.is_empty()); - assert!(!envs.values().any(|value| value.contains("arceos"))); assert!(!info.features.contains(&"lockdep".to_string())); } @@ -55,30 +51,16 @@ fn makefile_features_use_ax_std_dependency_for_std_build() { ..BuildInfo::default() }; - apply_makefile_features_with_prefix_family( - &mut info, - "arceos-app", - &[String::from("lockdep")], - Err(anyhow::anyhow!("std test packages do not depend on ax-std")), - ); + apply_makefile_features(&mut info, &[String::from("lockdep")]).unwrap(); info.resolve_std_features(); - let mut envs = HashMap::new(); pass_std_build_nested_features( - &mut envs, &mut info.features, &[], &["lockdep".to_string(), "std-compat".to_string()], ); - assert_eq!( - info.features, - vec![ - "ax-std/lockdep".to_string(), - "ax-std/std-compat".to_string() - ] - ); - assert!(envs.is_empty()); + assert_eq!(info.features, vec!["ax-std/lockdep".to_string()]); } #[test] @@ -89,17 +71,3 @@ fn unknown_ax_hal_features_are_not_platforms() { assert_eq!(ax_hal_platform_feature_name(feature, Some(&metadata)), None); } } - -#[test] -fn default_platform_feature_uses_dynamic_platform() { - let mut info = BuildInfo::default(); - - info.resolve_features_with_prefix_family( - "arceos-helloworld", - "loongarch64-unknown-none-softfloat", - Ok(StdFeaturePrefixFamily::AxStd), - None, - ); - - assert!(!info.features.contains(&"ax-std/plat-dyn".to_string())); -} diff --git a/scripts/axbuild/src/build/tests/mod.rs b/scripts/axbuild/src/build/tests/mod.rs index b377c0211e..ae55159c80 100644 --- a/scripts/axbuild/src/build/tests/mod.rs +++ b/scripts/axbuild/src/build/tests/mod.rs @@ -7,10 +7,6 @@ use walkdir::WalkDir; use super::*; -fn metadata_for_manifest(manifest_path: &Path) -> cargo_metadata::Metadata { - workspace_metadata_root_manifest(manifest_path).unwrap() -} - fn repo_metadata() -> cargo_metadata::Metadata { workspace_metadata().unwrap() } diff --git a/scripts/axbuild/src/build/tests/platform.rs b/scripts/axbuild/src/build/tests/platform.rs index a22f87cb4a..632090af07 100644 --- a/scripts/axbuild/src/build/tests/platform.rs +++ b/scripts/axbuild/src/build/tests/platform.rs @@ -1,24 +1,18 @@ use super::*; #[test] -fn std_build_platform_feature_stays_on_arceos_rust_dependency() { - let mut info = BuildInfo { +fn std_build_rejects_removed_platform_feature() { + let info = BuildInfo { features: vec!["ax-std/plat-dyn".to_string(), "alloc".to_string()], ..BuildInfo::default() }; - info.resolve_std_features(); - let mut envs = HashMap::new(); - pass_std_build_nested_features(&mut envs, &mut info.features, &[], &["alloc".to_string()]); - - assert_eq!(info.features, vec!["ax-std/alloc".to_string()]); - assert!(envs.is_empty()); + assert!(info.validate_features().is_err()); } #[test] fn x86_64_defaults_to_dynamic_platform() { assert!(supports_platform_dynamic("x86_64-unknown-none")); - assert!(!default_to_bin_for_target("x86_64-unknown-none")); } #[test] @@ -26,9 +20,6 @@ fn loongarch64_defaults_to_dynamic_platform_when_supported() { assert!(supports_platform_dynamic( "loongarch64-unknown-none-softfloat" )); - assert!(!default_to_bin_for_target( - "loongarch64-unknown-none-softfloat" - )); } #[test] diff --git a/scripts/axbuild/src/build/tests/std_features.rs b/scripts/axbuild/src/build/tests/std_features.rs index 7f6b8b7261..dcc86ff859 100644 --- a/scripts/axbuild/src/build/tests/std_features.rs +++ b/scripts/axbuild/src/build/tests/std_features.rs @@ -2,17 +2,13 @@ use super::*; #[test] fn std_build_nested_features_are_passed_through_not_enabled_on_app() { - let mut envs = HashMap::new(); let mut features = vec![ - "plat-dyn".to_string(), - "ax-driver/plat-dyn".to_string(), "ax-driver/virtio-blk".to_string(), "ax-driver/virtio-net".to_string(), "dns".to_string(), ]; pass_std_build_nested_features( - &mut envs, &mut features, &["dns".to_string()], &[ @@ -28,26 +24,22 @@ fn std_build_nested_features_are_passed_through_not_enabled_on_app() { features, vec![ "ax-std/dns".to_string(), - "ax-std/std-compat".to_string(), "ax-std/virtio-blk".to_string(), "ax-std/virtio-net".to_string(), "dns".to_string(), ] ); - assert!(envs.is_empty()); } #[test] fn std_build_runtime_features_are_passed_through_after_normalization() { let mut info = BuildInfo { - features: vec!["plat-dyn".to_string(), "dns".to_string()], + features: vec!["dns".to_string()], ..BuildInfo::default() }; info.resolve_std_features(); - let mut envs = HashMap::new(); pass_std_build_nested_features( - &mut envs, &mut info.features, &["dns".to_string()], &[ @@ -59,13 +51,8 @@ fn std_build_runtime_features_are_passed_through_after_normalization() { assert_eq!( info.features, - vec![ - "ax-std/dns".to_string(), - "ax-std/std-compat".to_string(), - "dns".to_string() - ] + vec!["ax-std/dns".to_string(), "dns".to_string()] ); - assert!(envs.is_empty()); } #[test] @@ -95,17 +82,9 @@ fn std_build_cargo_config_builds_fake_lib_before_app() { ); assert_eq!( cargo.features, - vec![ - "arceos".to_string(), - "ax-std/dns".to_string(), - "ax-std/fs".to_string(), - "ax-std/irq".to_string(), - "ax-std/paging".to_string(), - "ax-std/smp".to_string(), - "ax-std/std-compat".to_string(), - ] + vec!["ax-std/dns".to_string(), "ax-std/fs".to_string(),] ); - assert!(cargo.to_bin); + assert!(!cargo.to_bin); assert_eq!( cargo.env.get("CARGO_UNSTABLE_JSON_TARGET_SPEC"), Some(&"true".to_string()) diff --git a/scripts/axbuild/src/build/tests/std_linker.rs b/scripts/axbuild/src/build/tests/std_linker.rs index eb9af8bfab..a2b2923f3b 100644 --- a/scripts/axbuild/src/build/tests/std_linker.rs +++ b/scripts/axbuild/src/build/tests/std_linker.rs @@ -216,9 +216,9 @@ fn std_build_dynamic_x86_64_prepares_binary_artifact() { .target .ends_with("scripts/targets/std/pie/x86_64-unknown-linux-musl.json") ); - assert!(cargo.to_bin); + assert!(!cargo.to_bin); assert!(!cargo.features.contains(&"ax-std/plat-dyn".to_string())); - assert!(cargo.features.contains(&"ax-std/smp".to_string())); + assert!(!cargo.features.contains(&"ax-std/smp".to_string())); assert_eq!( cargo.env.get("AX_TARGET"), Some(&"x86_64-unknown-none".to_string()) diff --git a/scripts/axbuild/src/build/tests/std_metadata.rs b/scripts/axbuild/src/build/tests/std_metadata.rs index 7526c55cd9..336a800cc0 100644 --- a/scripts/axbuild/src/build/tests/std_metadata.rs +++ b/scripts/axbuild/src/build/tests/std_metadata.rs @@ -1,7 +1,7 @@ use super::*; #[test] -fn std_build_uses_package_axstd_metadata_for_ax_std_features() { +fn std_build_only_propagates_selected_features() { let workspace = temp_workspace("std-app", "").unwrap(); let app_manifest = workspace.join("app/Cargo.toml"); fs::write( @@ -12,16 +12,13 @@ fn std_build_uses_package_axstd_metadata_for_ax_std_features() { ) .unwrap(); - let metadata = metadata_for_manifest(&workspace.join("Cargo.toml")); let mut info = BuildInfo { features: vec!["dns".to_string()], ..BuildInfo::default() }; - info.resolve_std_features_with_metadata("std-app", "x86_64-unknown-none", &metadata); - let mut envs = HashMap::new(); + info.resolve_std_features(); pass_std_build_nested_features( - &mut envs, &mut info.features, &[], &[ @@ -32,20 +29,11 @@ fn std_build_uses_package_axstd_metadata_for_ax_std_features() { ], ); - assert_eq!( - info.features, - vec![ - "ax-std/dns".to_string(), - "ax-std/multitask".to_string(), - "ax-std/net".to_string(), - "ax-std/std-compat".to_string(), - ] - ); - assert!(envs.is_empty()); + assert_eq!(info.features, vec!["ax-std/dns".to_string()]); } #[test] -fn std_build_auto_enables_app_arceos_feature_when_declared() { +fn std_build_does_not_auto_enable_app_arceos_feature() { let metadata = repo_metadata(); let cargo = BuildInfo { features: Vec::new(), @@ -58,16 +46,20 @@ fn std_build_auto_enables_app_arceos_feature_when_declared() { ) .unwrap(); - assert!(cargo.features.contains(&"arceos".to_string())); + assert!(!cargo.features.contains(&"arceos".to_string())); } #[test] -fn std_build_does_not_inject_arceos_feature_when_app_lacks_it() { - let mut features = vec!["dns".to_string()]; - - inject_arceos_feature_for_std_build(&mut features, &["dns".to_string()]); +fn arceos_test_suite_declares_its_arceos_baseline() { + let metadata = repo_metadata(); + let package = workspace_package(&metadata, "arceos-test-suit").unwrap(); + let ax_std = package.features.get("ax-std").unwrap(); - assert_eq!(features, vec!["dns".to_string()]); + assert!( + ax_std.iter().any(|feature| feature == "ax-std/arceos"), + "arceos-test-suit must enable the ax-std/arceos baseline itself instead of relying on \ + axbuild" + ); } #[test] @@ -94,11 +86,11 @@ fn std_build_uses_dynamic_platform_features_without_static_hal_platform() { .ends_with("scripts/targets/std/pie/aarch64-unknown-linux-musl.json") ); assert!(!cargo.features.contains(&"ax-std/plat-dyn".to_string())); - assert!(cargo.features.contains(&"ax-std/smp".to_string())); - assert!(cargo.features.contains(&"ax-std/std-compat".to_string())); + assert!(!cargo.features.contains(&"ax-std/smp".to_string())); + assert!(!cargo.features.contains(&"ax-std/std-compat".to_string())); assert!(cargo.features.contains(&"ax-std/virtio-net".to_string())); assert!(cargo.features.contains(&"ax-std/net".to_string())); - assert!(cargo.to_bin); + assert!(!cargo.to_bin); assert_eq!( cargo.env.get("AX_TARGET"), Some(&"aarch64-unknown-none-softfloat".to_string()) @@ -131,8 +123,8 @@ fn std_build_aarch64_defaults_to_dynamic_platform() { ); assert!(!cargo.env.contains_key("AX_CONFIG_PATH")); assert!(!cargo.features.contains(&"ax-std/plat-dyn".to_string())); - assert!(cargo.features.contains(&"ax-std/smp".to_string())); - assert!(cargo.features.contains(&"ax-std/std-compat".to_string())); + assert!(!cargo.features.contains(&"ax-std/smp".to_string())); + assert!(!cargo.features.contains(&"ax-std/std-compat".to_string())); assert!( cargo .features diff --git a/scripts/axbuild/src/clippy/env.rs b/scripts/axbuild/src/clippy/env.rs index 3fd8187f7d..b5ef7ea113 100644 --- a/scripts/axbuild/src/clippy/env.rs +++ b/scripts/axbuild/src/clippy/env.rs @@ -1,6 +1,3 @@ -use std::collections::HashMap; - -use anyhow::Context; use cargo_metadata::{Metadata, Package}; use super::{AXSTD_STD_CLIPPY_TARGET, AXSTD_STD_DEFAULT_FEATURE, AXSTD_STD_PACKAGE}; @@ -9,21 +6,19 @@ pub(super) fn clippy_env(_package: &Package) -> Vec<(String, String)> { Vec::new() } -fn axstd_std_clippy_env(metadata: &Metadata) -> anyhow::Result> { - let mut envs = HashMap::new(); - crate::build::prepare_std_build_env(&mut envs, AXSTD_STD_CLIPPY_TARGET, metadata) - .context("failed to prepare ax-std std clippy config")?; - Ok(envs.into_iter().collect()) -} - pub(super) fn feature_clippy_env( package: &Package, feature: &str, base_env: Vec<(String, String)>, - metadata: &Metadata, + _metadata: &Metadata, ) -> anyhow::Result> { if package.name == AXSTD_STD_PACKAGE && feature == AXSTD_STD_DEFAULT_FEATURE { - return axstd_std_clippy_env(metadata); + // Clippy for the std-only ax-std target needs the original target + // name; no feature or platform is inferred here. + return Ok(vec![( + "AX_TARGET".to_string(), + AXSTD_STD_CLIPPY_TARGET.to_string(), + )]); } Ok(base_env) diff --git a/scripts/axbuild/src/context/mod.rs b/scripts/axbuild/src/context/mod.rs index c4a912c6ab..8bc2822ac5 100644 --- a/scripts/axbuild/src/context/mod.rs +++ b/scripts/axbuild/src/context/mod.rs @@ -158,13 +158,10 @@ impl AppContext { &mut self, cargo: Cargo, build_config_path: PathBuf, - mut qemu: Option, + qemu: Option, ) -> anyhow::Result<()> { let _env_guard = EnvRestoreGuard::set(&cargo.env); let _path_guard = self.scoped_qemu_path(&cargo)?; - if let Some(qemu) = qemu.as_mut() { - crate::test::qemu::apply_dynamic_platform_qemu_boot(qemu, &cargo); - } self.set_build_config_path(build_config_path); let build_config_path = self.build_config_path.clone(); let stage = StageLog::start(format!( diff --git a/scripts/axbuild/src/context/resolve.rs b/scripts/axbuild/src/context/resolve.rs index 59b4cb37a3..6cf7644641 100644 --- a/scripts/axbuild/src/context/resolve.rs +++ b/scripts/axbuild/src/context/resolve.rs @@ -1,6 +1,6 @@ use std::path::{Path, PathBuf}; -use anyhow::anyhow; +use anyhow::{anyhow, bail}; use super::{ ARCEOS_SNAPSHOT_FILE, AppContext, ArceosCommandSnapshot, ArceosQemuSnapshot, @@ -10,6 +10,7 @@ use super::{ StarryQemuSnapshot, StarryUbootSnapshot, resolve_arceos_arch_and_target, resolve_axvisor_arch_and_target, resolve_starry_arch_and_target, }; +use crate::arceos::cbuild::AX_LIBC_PACKAGE; #[derive(Debug, Clone, Default, PartialEq, Eq)] struct ResolvedCommandPaths { @@ -62,23 +63,34 @@ impl AppContext { resolved_config.filter(|path| path.exists()) }; - let package = cli - .package - .clone() - .or(config_selectors.package) - .or_else(|| { - if config_selectors.uses_app_c { - Some("ax-libc".to_string()) - } else { - snapshot.package.clone() + // A C source app is linked against ax-libc, not an arbitrary workspace + // package. Reject conflicts here so command execution never rewrites the + // request selected by the user or build configuration. + let package = if config_selectors.uses_app_c { + for package in [&cli.package, &config_selectors.package] + .into_iter() + .flatten() + { + if package != AX_LIBC_PACKAGE { + bail!( + "ArceOS build config with `app-c` must use package `{AX_LIBC_PACKAGE}`; \ + remove the conflicting package selector `{package}`" + ); } - }) - .ok_or_else(|| { - anyhow!( - "missing ArceOS package; pass `--package` or set `package` in {}", - ARCEOS_SNAPSHOT_FILE - ) - })?; + } + AX_LIBC_PACKAGE.to_string() + } else { + cli.package + .clone() + .or(config_selectors.package) + .or(snapshot.package.clone()) + .ok_or_else(|| { + anyhow!( + "missing ArceOS package; pass `--package` or set `package` in {}", + ARCEOS_SNAPSHOT_FILE + ) + })? + }; let config_target = config_selectors.target; let effective_arch = cli.arch.clone().or_else(|| { if cli.target.is_some() || config_target.is_some() { diff --git a/scripts/axbuild/src/context/tests/arceos.rs b/scripts/axbuild/src/context/tests/arceos.rs index f0d1c9b80f..e3e38f35b2 100644 --- a/scripts/axbuild/src/context/tests/arceos.rs +++ b/scripts/axbuild/src/context/tests/arceos.rs @@ -294,6 +294,40 @@ fn prepare_request_requires_package() { assert!(err.to_string().contains("missing ArceOS package")); } +#[test] +fn prepare_request_rejects_package_override_for_c_app_config() { + let root = tempdir().unwrap(); + let config_path = root.path().join("configs/c-app.toml"); + fs::create_dir_all(config_path.parent().unwrap()).unwrap(); + fs::write( + &config_path, + r#" +package = "arceos-helloworld" +target = "aarch64-unknown-none-softfloat" +app-c = "c" +features = [] +log = "Info" +"#, + ) + .unwrap(); + let app = test_app_context(root.path()); + + let err = prepare_arceos_request( + &app, + BuildCliArgs { + config: Some(config_path), + package: Some("arceos-helloworld".into()), + ..Default::default() + }, + None, + None, + ) + .unwrap_err(); + + assert!(err.to_string().contains("app-c")); + assert!(err.to_string().contains("ax-libc")); +} + #[test] fn prepare_request_resolves_arceos_target_from_arch() { let root = tempdir().unwrap(); diff --git a/scripts/axbuild/src/firmware.rs b/scripts/axbuild/src/firmware.rs deleted file mode 100644 index 9046af21c5..0000000000 --- a/scripts/axbuild/src/firmware.rs +++ /dev/null @@ -1,145 +0,0 @@ -//! AIC8800 Wi-Fi firmware provisioning. -//! -//! The AIC8800 firmware blobs are vendor binaries that we do not vendor into -//! the git tree. Instead they are fetched on demand (and integrity-checked -//! against pinned SHA-256 digests) from the upstream LicheeRV Nano firmware -//! package, pinned to a specific commit. -//! -//! This is a workspace-level *cache warmer*: any build/lint/test command that -//! compiles the `aic8800` crate calls [`ensure_aic8800_firmware`] first to -//! populate `components/aic8800/firmware/`. The `aic8800` crate's own -//! `build.rs` then prefers that in-tree dir (so in-repo, incl. offline, builds -//! never hit the network), falling back to its own download only when the dir -//! is absent (e.g. a standalone crates.io build). Keep this manifest in sync -//! with `components/aic8800/build.rs`. - -use std::path::{Path, PathBuf}; - -use anyhow::{Context, Result}; -use sha2::{Digest, Sha256}; - -use crate::support::download::{download_file_verified_sha256, http_client}; - -/// Upstream firmware source: the repo referenced by the LicheeRV Nano -/// buildroot package `aic8800-sdio-firmware`, pinned to a fixed commit. -const FIRMWARE_REPO: &str = "lxowalle/aic8800-sdio-firmware"; -const FIRMWARE_COMMIT: &str = "c56f910044cc854d6c553bcb9a644f3bca5a4c38"; - -/// One firmware blob: the local file name under `components/aic8800/firmware/`, -/// its path within the upstream repo, and the expected SHA-256 of its contents. -struct FirmwareFile { - name: &'static str, - remote_path: &'static str, - sha256: &'static str, -} - -/// The exact set of blobs referenced by `aic8800`'s `include_bytes!` calls. -/// Digests verified byte-for-byte against the pinned upstream commit. -const FIRMWARE_FILES: &[FirmwareFile] = &[ - FirmwareFile { - name: "fmacfw.bin", - remote_path: "aic8800_and_aic8800D80/fmacfw.bin", - sha256: "2c6e70726df10ef74d9b1a657c74fdcfaeb88855b96b2c9bc8e0e603ac7c4cc3", - }, - FirmwareFile { - name: "fmacfw_patch.bin", - remote_path: "aic8800_and_aic8800D80/fmacfw_patch.bin", - sha256: "6c8126ad655e9971f05ca03dc60fa82cb6d48c3b02cf3ba960137566ce2e28d5", - }, - FirmwareFile { - name: "fmacfw_patch_8800dc_u02.bin", - remote_path: "aic8800DC/fmacfw_patch_8800dc_u02.bin", - sha256: "69d3ac2038da3b8e652ed1ec5079598ceb6df51db7b87b1d33f6d3c820c86a6f", - }, - FirmwareFile { - name: "fw_patch_8800dc_u02.bin", - remote_path: "aic8800DC/fw_patch_8800dc_u02.bin", - sha256: "c4087b95e788785df0fc55aa92152d214323ee028c70ba0ebb23944d4070340b", - }, - FirmwareFile { - name: "fw_patch_table_8800dc_u02.bin", - remote_path: "aic8800DC/fw_patch_table_8800dc_u02.bin", - sha256: "e7eea12cc85fca5d8667182b4520b6a0929044c70c6d9e9a3d7ece8b16169688", - }, - FirmwareFile { - name: "fmacfw_8800d80_u02.bin", - remote_path: "aic8800_and_aic8800D80/fmacfw_8800d80_u02.bin", - sha256: "ffb49ede6004e58453f01489edf28b888b509529c3173554c98aa94fbb33507d", - }, - FirmwareFile { - name: "fw_patch_8800d80_u02.bin", - remote_path: "aic8800_and_aic8800D80/fw_patch_8800d80_u02.bin", - sha256: "f0e2f5bbc17bc327ca7f1574ff55370dfd863d931514347bb4abc18a74f6218f", - }, - FirmwareFile { - name: "fw_patch_table_8800d80_u02.bin", - remote_path: "aic8800_and_aic8800D80/fw_patch_table_8800d80_u02.bin", - sha256: "9decb77435b7e9713e33e32da483d683b7329ed93b672b2d1b134031d7da5f67", - }, -]; - -fn firmware_dir(workspace_root: &Path) -> PathBuf { - workspace_root.join("components/aic8800/firmware") -} - -fn sha256_hex(bytes: &[u8]) -> String { - let mut hasher = Sha256::new(); - hasher.update(bytes); - hasher - .finalize() - .iter() - .map(|b| format!("{b:02x}")) - .collect() -} - -/// Returns `true` if `path` exists and its contents match `expected_sha256`. -fn file_matches(path: &Path, expected_sha256: &str) -> bool { - match std::fs::read(path) { - Ok(bytes) => sha256_hex(&bytes) == expected_sha256, - Err(_) => false, - } -} - -/// Ensures every AIC8800 firmware blob is present under -/// `components/aic8800/firmware/` with the expected contents, downloading any -/// missing or mismatched file from the pinned upstream commit. -/// -/// Idempotent and cheap when blobs are already in place (only hashing, no -/// network). Safe to call before any command that compiles `aic8800`. -pub async fn ensure_aic8800_firmware(workspace_root: &Path) -> Result<()> { - let dir = firmware_dir(workspace_root); - - let missing: Vec<&FirmwareFile> = FIRMWARE_FILES - .iter() - .filter(|f| !file_matches(&dir.join(f.name), f.sha256)) - .collect(); - - if missing.is_empty() { - return Ok(()); - } - - std::fs::create_dir_all(&dir) - .with_context(|| format!("failed to create firmware dir {}", dir.display()))?; - - let client = http_client()?; - log::info!( - "fetching {} AIC8800 firmware blob(s) from {}@{}", - missing.len(), - FIRMWARE_REPO, - &FIRMWARE_COMMIT[..12] - ); - - for file in missing { - let url = format!( - "https://raw.githubusercontent.com/{}/{}/{}", - FIRMWARE_REPO, FIRMWARE_COMMIT, file.remote_path - ); - let dest = dir.join(file.name); - download_file_verified_sha256(&client, &url, &dest, file.sha256) - .await - .with_context(|| format!("failed to fetch firmware {} from {url}", file.name))?; - log::info!(" fetched {}", file.name); - } - - Ok(()) -} diff --git a/scripts/axbuild/src/ktest/mod.rs b/scripts/axbuild/src/ktest/mod.rs index fa694221e7..821bd89127 100644 --- a/scripts/axbuild/src/ktest/mod.rs +++ b/scripts/axbuild/src/ktest/mod.rs @@ -191,7 +191,6 @@ async fn run_qemu(args: ArgsKtestQemu) -> anyhow::Result<()> { crate::rootfs::qemu::RootfsPatchMode::EnsureDiskBootNet, ); } - crate::test::qemu::apply_dynamic_platform_qemu_boot(&mut qemu, &cargo); apply_axtest_qemu_markers(&mut qemu); app.run_qemu_with_axtest_coverage(&cargo, qemu, None).await } @@ -407,6 +406,8 @@ fn prepare_ktest_cargo(cargo: &mut Cargo, target: &KtestTarget, coverage: bool) cargo.test = Some(target.name.clone()); remove_cargo_target_selector_args(&mut cargo.args); cargo.env.remove("AXBUILD_STARRY_BIN"); + // `ktest` is an explicit command: the harness feature and the target's + // declared required features are the only additions made here. ensure_feature(cargo, AXTEST_FEATURE); for feature in &target.required_features { ensure_feature(cargo, feature); diff --git a/scripts/axbuild/src/lib.rs b/scripts/axbuild/src/lib.rs index 73529ae139..ef619a70e6 100644 --- a/scripts/axbuild/src/lib.rs +++ b/scripts/axbuild/src/lib.rs @@ -14,7 +14,6 @@ mod board; mod build; mod clippy; pub mod context; -mod firmware; pub mod image; mod ktest; mod rootfs; @@ -111,10 +110,7 @@ async fn run_root_cli(cli: Cli) -> anyhow::Result<()> { Commands::AgentReviewBench { command } => agent_review_bench::execute(command).await, Commands::Test => test::std::run_std_test_command(), Commands::Ktest(args) => ktest::run(args).await, - Commands::Clippy(args) => { - ensure_aic8800_firmware().await?; - clippy::run_workspace_clippy_command(&args) - } + Commands::Clippy(args) => clippy::run_workspace_clippy_command(&args), Commands::SyncLint(args) => sync_lint::run_sync_lint_command(&args), Commands::SpinLint => spin_lint::run_spin_lint_command(), Commands::Board { command } => board::execute(command).await, @@ -123,10 +119,7 @@ async fn run_root_cli(cli: Cli) -> anyhow::Result<()> { Commands::Axvisor { command } => Axvisor::new()?.execute(command).await, Commands::Axloader { command } => Axloader::new()?.execute(command).await, Commands::Arceos { command } => ArceOS::new()?.execute(command).await, - Commands::Starry { command } => { - ensure_aic8800_firmware().await?; - Starry::new()?.execute(command).await - } + Commands::Starry { command } => Starry::new()?.execute(command).await, } } @@ -333,10 +326,3 @@ mod tests { } } } - -/// Provisions the AIC8800 Wi-Fi firmware blobs (fetched + integrity-checked, -/// never committed) before any command that may compile the `aic8800` crate. -async fn ensure_aic8800_firmware() -> anyhow::Result<()> { - let workspace_root = context::workspace_root_path()?; - firmware::ensure_aic8800_firmware(&workspace_root).await -} diff --git a/scripts/axbuild/src/spin_lint.rs b/scripts/axbuild/src/spin_lint.rs index fa90c2a5c0..9ff39f9f74 100644 --- a/scripts/axbuild/src/spin_lint.rs +++ b/scripts/axbuild/src/spin_lint.rs @@ -8,8 +8,8 @@ use anyhow::{Context, bail}; use toml::Value; use walkdir::{DirEntry, WalkDir}; -const SPIN_VERSION_REQ: &str = "=0.12.0"; -const SPIN_LOCKFILE_VERSION: &str = "0.12.0"; +const SPIN_VERSION_REQ: &str = "=0.12.2"; +const SPIN_LOCKFILE_VERSION: &str = "0.12.2"; const CRATES_IO_SOURCE: &str = "registry+https://github.com/rust-lang/crates.io-index"; const ALLOWED_SPIN_FEATURES: &[&str] = &["lock_api", "once", "lazylock"]; const FORBIDDEN_SPIN_RWLOCK_PATTERNS: &[&str] = @@ -647,7 +647,7 @@ mod tests { members = ["crate"] [workspace.dependencies] -spin = { version = "=0.12.0", default-features = false, features = ["lock_api", "once", "lazylock"] } +spin = { version = "=0.12.2", default-features = false, features = ["lock_api", "once", "lazylock"] } "#, ); write_file( @@ -669,13 +669,19 @@ spin = { workspace = true } r#" [[package]] name = "spin" -version = "0.12.0" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abc" "#, ); } + #[test] + fn pins_spin_to_the_dev_baseline() { + assert_eq!(SPIN_VERSION_REQ, "=0.12.2"); + assert_eq!(SPIN_LOCKFILE_VERSION, "0.12.2"); + } + #[test] fn accepts_workspace_registry_spin_dependency_without_crates_io_patch() { let root = tempfile::tempdir().unwrap(); @@ -698,7 +704,7 @@ checksum = "abc" members = ["crate"] [workspace.dependencies] -spin = { version = "=0.12.0", default-features = false, features = ["lock_api", "once", "lazylock"] } +spin = { version = "=0.12.2", default-features = false, features = ["lock_api", "once", "lazylock"] } [patch.crates-io] spin = { path = "components/spin" } @@ -724,7 +730,7 @@ spin = { path = "components/spin" } r#" [package] name = "spin" -version = "0.12.0" +version = "0.12.2" "#, ); @@ -749,7 +755,7 @@ version = "0.12.0" members = ["crate"] [workspace.dependencies] -spin = { version = "=0.12.0", path = "components/spin", default-features = false, features = ["lock_api", "once", "lazylock"] } +spin = { version = "=0.12.2", path = "components/spin", default-features = false, features = ["lock_api", "once", "lazylock"] } "#, ); @@ -774,7 +780,7 @@ spin = { version = "=0.12.0", path = "components/spin", default-features = false members = ["crate"] [workspace.dependencies] -spin = { version = "=0.12.0", default-features = true, features = ["lock_api", "once", "lazylock"] } +spin = { version = "=0.12.2", default-features = true, features = ["lock_api", "once", "lazylock"] } "#, ); @@ -799,7 +805,7 @@ spin = { version = "=0.12.0", default-features = true, features = ["lock_api", " members = ["crate"] [workspace.dependencies] -spin = { version = "=0.12.0", default-features = false, features = ["lock_api", "once", "lazylock", "rwlock"] } +spin = { version = "=0.12.2", default-features = false, features = ["lock_api", "once", "lazylock", "rwlock"] } "#, ); @@ -826,7 +832,7 @@ version = "0.1.0" edition = "2021" [dependencies] -spin = { version = "=0.12.0", default-features = false, features = ["once"] } +spin = { version = "=0.12.2", default-features = false, features = ["once"] } "#, ); @@ -876,7 +882,7 @@ version = "0.1.0" edition = "2021" [dependencies] -spin = { version = "=0.12.0", features = ["once"] } +spin = { version = "=0.12.2", features = ["once"] } "#, ); @@ -903,7 +909,7 @@ version = "0.1.0" edition = "2021" [dependencies] -spin = { version = "=0.12.0", default-features = false, features = ["once", "rwlock"] } +spin = { version = "=0.12.2", default-features = false, features = ["once", "rwlock"] } "#, ); @@ -930,7 +936,7 @@ version = "0.1.0" edition = "2021" [dependencies] -spin_compat = { package = "spin", version = "=0.12.0", default-features = false, features = ["once"] } +spin_compat = { package = "spin", version = "=0.12.2", default-features = false, features = ["once"] } "#, ); @@ -980,7 +986,7 @@ spin = { workspace = true, features = ["rwlock"] } r#" [[package]] name = "spin" -version = "0.12.0" +version = "0.12.2" "#, ); @@ -1003,7 +1009,7 @@ version = "0.12.0" r#" [[package]] name = "spin" -version = "0.12.0" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" "#, ); @@ -1038,7 +1044,7 @@ checksum = "abc" assert!( findings .iter() - .any(|finding| finding.message.contains("must stay at 0.12.0")) + .any(|finding| finding.message.contains("must stay at 0.12.2")) ); } diff --git a/scripts/axbuild/src/starry/app/qemu.rs b/scripts/axbuild/src/starry/app/qemu.rs index df816f830f..8a1a3f76cb 100644 --- a/scripts/axbuild/src/starry/app/qemu.rs +++ b/scripts/axbuild/src/starry/app/qemu.rs @@ -13,7 +13,7 @@ use super::{ types::{StarryAppCase, StarryAppKind}, }; use crate::{ - context::starry_target_for_arch_checked, + context::{DEFAULT_STARRY_ARCH, starry_target_for_arch_checked}, test::{ case::TestQemuCase, qemu::{self as qemu_test}, @@ -47,7 +47,7 @@ pub(crate) async fn prepare_qemu_app_case( .and_then(arch_from_qemu_config_path) .map(str::to_string) }) - .unwrap_or_else(|| "x86_64".to_string()); + .unwrap_or_else(|| DEFAULT_STARRY_ARCH.to_string()); let target = starry_target_for_arch_checked(&arch)?.to_string(); let build_config_path = discover_optional_build_config(&app.case_dir, &target)?; let fields = qemu_config_path @@ -171,7 +171,7 @@ pub(super) fn resolve_qemu_config( return Ok(Some(resolve_case_relative_path(&app.case_dir, path))); } - let arch = arch.unwrap_or("x86_64"); + let arch = arch.unwrap_or(DEFAULT_STARRY_ARCH); let path = app.case_dir.join(qemu_config_name(arch)); if path.is_file() { return Ok(Some(path)); diff --git a/scripts/axbuild/src/starry/app/selection.rs b/scripts/axbuild/src/starry/app/selection.rs index bcb07ba186..e3e832456f 100644 --- a/scripts/axbuild/src/starry/app/selection.rs +++ b/scripts/axbuild/src/starry/app/selection.rs @@ -9,6 +9,7 @@ use super::{ qemu::qemu_app_supports_arch, types::{StarryAppCase, StarryAppKind}, }; +use crate::context::DEFAULT_STARRY_ARCH; pub(crate) fn print_apps(workspace_root: &Path, kind: Option) -> anyhow::Result<()> { for app in filtered_apps(workspace_root, kind)? { @@ -43,7 +44,8 @@ pub(crate) fn selected_apps( }; apps.retain(|app| app.kind == kind); if args.all && args.qemu_config.is_none() { - let arch = args.arch.as_deref().unwrap_or("x86_64"); + // Keep app selection aligned with the generic Starry command default. + let arch = args.arch.as_deref().unwrap_or(DEFAULT_STARRY_ARCH); apps.retain(|app| app.kind != StarryAppKind::Qemu || qemu_app_supports_arch(app, arch)); } if let Some(case_name) = args.test_case.as_deref() { diff --git a/scripts/axbuild/src/starry/app/tests/qemu.rs b/scripts/axbuild/src/starry/app/tests/qemu.rs index 3d1a0f2769..dcbfd1a7d4 100644 --- a/scripts/axbuild/src/starry/app/tests/qemu.rs +++ b/scripts/axbuild/src/starry/app/tests/qemu.rs @@ -7,7 +7,9 @@ use std::{ use tempfile::tempdir; -use super::{app_qemu_test_case, load_qemu_app_case_fields, resolve_qemu_config}; +use super::{ + app_qemu_test_case, load_qemu_app_case_fields, prepare_qemu_app_case, resolve_qemu_config, +}; use crate::{ starry::app::{ StarryAppQemuCase, discover_apps, @@ -39,6 +41,28 @@ fn qemu_config_selection_prefers_exact_arch_config() { assert_eq!(selected, exact); } +#[tokio::test] +async fn qemu_case_uses_starry_default_arch_without_an_arch_argument() { + let root = tempdir().unwrap(); + write_case_file( + root.path(), + "qemu/apt", + "qemu-riscv64.toml", + "args = []\nuefi = false\nto_bin = true\nsuccess_regex = []\nfail_regex = []\n", + ); + let app = discover_apps(root.path()) + .unwrap() + .into_iter() + .find(|app| app.name == "qemu/apt") + .unwrap(); + + let case = prepare_qemu_app_case(root.path(), &app, None, None) + .await + .unwrap(); + + assert_eq!(case.arch, crate::context::DEFAULT_STARRY_ARCH); +} + #[test] fn qemu_config_selection_rejects_variant_only_default() { let root = tempdir().unwrap(); diff --git a/scripts/axbuild/src/starry/app/tests/selection.rs b/scripts/axbuild/src/starry/app/tests/selection.rs index 659c09be13..ccb6694392 100644 --- a/scripts/axbuild/src/starry/app/tests/selection.rs +++ b/scripts/axbuild/src/starry/app/tests/selection.rs @@ -30,6 +30,31 @@ fn all_qemu_selection_skips_apps_without_matching_arch_config() { assert_eq!(names, vec!["qemu/apk-curl"]); } +#[test] +fn all_qemu_selection_uses_starry_default_arch_without_an_arch_argument() { + let root = tempdir().unwrap(); + write_case_file( + root.path(), + "qemu/apk-curl", + "qemu-x86_64.toml", + "args = []\n", + ); + write_case_file(root.path(), "qemu/apt", "qemu-riscv64.toml", "args = []\n"); + let args = ArgsAppQemu { + all: true, + test_case: None, + caps: Vec::new(), + arch: None, + qemu_config: None, + debug: false, + }; + + let apps = selected_apps(root.path(), &args, StarryAppKind::Qemu).unwrap(); + let names = apps.into_iter().map(|app| app.name).collect::>(); + + assert_eq!(names, vec!["qemu/apt"]); +} + #[test] fn all_qemu_selection_skips_ignored_nested_app() { let root = tempdir().unwrap(); diff --git a/scripts/axbuild/src/starry/build.rs b/scripts/axbuild/src/starry/build.rs index 43f8007daf..3d3294e0ad 100644 --- a/scripts/axbuild/src/starry/build.rs +++ b/scripts/axbuild/src/starry/build.rs @@ -19,8 +19,9 @@ use crate::{ support::process::ProcessExt, }; -pub(crate) fn default_starry_build_info_for_target(target: &str) -> StarryBuildInfo { - let _ = target; +pub(crate) fn default_starry_build_info() -> StarryBuildInfo { + // The package and board configuration own feature selection; a generated + // default must remain an empty capability set. StarryBuildInfo { features: Vec::new(), ..StarryBuildInfo::default() @@ -73,9 +74,7 @@ pub(crate) fn load_build_info(request: &ResolvedStarryRequest) -> anyhow::Result let mut build_info = if let Some(build_info) = &request.build_info_override { build_info.clone() } else { - crate::build::ensure_build_info(&request.build_info_path, || { - default_starry_build_info_for_target(&request.target) - })?; + crate::build::ensure_build_info(&request.build_info_path, default_starry_build_info)?; crate::build::load_toml_with_rejector( &request.build_info_path, "build info", @@ -83,7 +82,7 @@ pub(crate) fn load_build_info(request: &ResolvedStarryRequest) -> anyhow::Result )? }; - crate::build::apply_makefile_features(&mut build_info, &request.package, &makefile_features); + crate::build::apply_makefile_features(&mut build_info, &makefile_features)?; if let Some(smp) = request.smp { build_info.max_cpu_num = Some(smp); @@ -99,22 +98,16 @@ pub(crate) fn load_cargo_config(request: &ResolvedStarryRequest) -> anyhow::Resu let mut build_info = if let Some(build_info) = &request.build_info_override { build_info.clone() } else { - crate::build::ensure_build_info(&request.build_info_path, || { - default_starry_build_info_for_target(&request.target) - })?; + crate::build::ensure_build_info(&request.build_info_path, default_starry_build_info)?; crate::build::load_toml_with_rejector( &request.build_info_path, "build info", crate::build::reject_arceos_app_c_field, )? }; - crate::build::apply_makefile_features_with_metadata( - &mut build_info, - &request.package, - &makefile_features, - metadata, - ); - normalize_starry_platform_features(&mut build_info.features); + crate::build::apply_makefile_features(&mut build_info, &makefile_features)?; + build_info.features.sort(); + build_info.features.dedup(); if let Some(smp) = request.smp { build_info.max_cpu_num = Some(smp); } @@ -123,18 +116,10 @@ pub(crate) fn load_cargo_config(request: &ResolvedStarryRequest) -> anyhow::Resu &request.target, metadata, )?; - cargo - .features - .retain(|feature| !is_removed_dynamic_platform_feature(feature)); patch_starry_cargo_config(&mut cargo, request, metadata)?; Ok(cargo) } -fn normalize_starry_platform_features(features: &mut Vec) { - features.sort(); - features.dedup(); -} - fn patch_starry_cargo_config( cargo: &mut Cargo, request: &ResolvedStarryRequest, @@ -143,10 +128,6 @@ fn patch_starry_cargo_config( cargo.package = request.package.clone(); ensure_starry_bin_arg(&mut cargo.args, &request.package, metadata)?; apply_starry_bin_override(cargo)?; - cargo - .features - .retain(|feature| !is_removed_dynamic_platform_feature(feature)); - cargo .env .insert("AX_ARCH".to_string(), request.arch.clone()); @@ -553,13 +534,6 @@ fn temp_file_path(path: &Path, suffix: &str) -> anyhow::Result { Ok(parent.join(format!(".{name}.{suffix}.{}.tmp", std::process::id()))) } -fn is_removed_dynamic_platform_feature(feature: &str) -> bool { - matches!( - feature, - "plat-dyn" | "ax-std/plat-dyn" | "starry-kernel/plat-dyn" | "ax-hal/plat-dyn" - ) -} - fn apply_starry_bin_override(cargo: &mut Cargo) -> anyhow::Result<()> { let Some(bin) = cargo.env.get("AXBUILD_STARRY_BIN").cloned() else { return Ok(()); diff --git a/scripts/axbuild/src/starry/build/tests.rs b/scripts/axbuild/src/starry/build/tests.rs index 20b3d3763a..ce21d7b626 100644 --- a/scripts/axbuild/src/starry/build/tests.rs +++ b/scripts/axbuild/src/starry/build/tests.rs @@ -8,7 +8,7 @@ use tempfile::tempdir; use super::*; use crate::{ - context::{ResolvedStarryRequest, STARRY_PACKAGE}, + context::{ResolvedStarryRequest, STARRY_PACKAGE, find_workspace_root}, starry::build::LogLevel, }; @@ -58,6 +58,14 @@ fn resolve_build_info_path_uses_default_starry_location() { ); } +#[test] +fn starry_manifest_declares_std_compat_for_dynamic_std_linking() { + let manifest = + fs::read_to_string(find_workspace_root().join("os/StarryOS/starryos/Cargo.toml")).unwrap(); + + assert!(manifest.contains("\"std-compat\"")); +} + #[test] fn resolve_build_info_path_ignores_source_tree_defaults() { let root = tempdir().unwrap(); @@ -110,30 +118,15 @@ fn load_build_info_writes_default_template_when_missing() { let build_info = load_build_info(&request).unwrap(); - assert_eq!( - build_info, - default_starry_build_info_for_target("aarch64-unknown-none-softfloat") - ); + assert_eq!(build_info, default_starry_build_info()); assert!(path.exists()); let persisted: StarryBuildInfo = toml::from_str(&fs::read_to_string(path).unwrap()).unwrap(); assert_eq!(persisted, build_info); } #[test] -fn default_aarch64_starry_build_info_uses_dynamic_platform() { - let build_info = default_starry_build_info_for_target("aarch64-unknown-none-softfloat"); - assert!(!build_info.features.contains(&"qemu".to_string())); -} - -#[test] -fn default_riscv64_starry_build_info_uses_dynamic_platform() { - let build_info = default_starry_build_info_for_target("riscv64gc-unknown-none-elf"); - assert!(!build_info.features.contains(&"qemu".to_string())); -} - -#[test] -fn default_x86_starry_build_info_uses_dynamic_platform() { - let build_info = default_starry_build_info_for_target("x86_64-unknown-none"); +fn default_starry_build_info_does_not_inject_features() { + let build_info = default_starry_build_info(); assert!(build_info.features.is_empty()); } @@ -234,7 +227,7 @@ fn load_build_info_prefers_request_override_without_writing_file() { request.build_info_override = Some(StarryBuildInfo { log: LogLevel::Info, features: vec!["net".to_string()], - ..default_starry_build_info_for_target("aarch64-unknown-none-softfloat") + ..default_starry_build_info() }); let build_info = load_build_info(&request).unwrap(); @@ -279,7 +272,7 @@ fn patch_starry_cargo_config_injects_required_features_and_env() { assert_eq!(cargo.env.get("AX_PLATFORM").map(String::as_str), None); assert_eq!(cargo.env.get("AX_LOG").map(String::as_str), Some("info")); assert_eq!(cargo.env.get("CUSTOM").map(String::as_str), Some("1")); - assert!(cargo.to_bin); + assert!(!cargo.to_bin); assert!(cargo.post_build_cmds.is_empty()); } @@ -296,7 +289,7 @@ fn patch_starry_cargo_config_preserves_request_package() { qemu_config: None, uboot_config: None, }; - let build_info = default_starry_build_info_for_target("x86_64-unknown-none"); + let build_info = default_starry_build_info(); let mut cargo = build_info.into_base_cargo_config_with_log( "placeholder".to_string(), request.target.clone(), @@ -314,13 +307,13 @@ fn patch_starry_cargo_config_preserves_request_package() { } #[test] -fn patch_starry_cargo_config_keeps_dynamic_platform_without_qemu() { - let request = request( +fn load_cargo_config_rejects_removed_dynamic_platform_feature() { + let mut request = request( PathBuf::from("/tmp/.build.toml"), "aarch64", "aarch64-unknown-none-softfloat", ); - let build_info = StarryBuildInfo { + request.build_info_override = Some(StarryBuildInfo { env: HashMap::new(), features: vec![ "common".to_string(), @@ -330,32 +323,14 @@ fn patch_starry_cargo_config_keeps_dynamic_platform_without_qemu() { ], log: LogLevel::Info, max_cpu_num: Some(8), - }; - let mut cargo = build_info.into_base_cargo_config_with_log( - STARRY_PACKAGE.to_string(), - "scripts/targets/std/pie/aarch64-unknown-linux-musl.json".to_string(), - Vec::new(), - ); + }); - let metadata = crate::build::workspace_metadata().unwrap(); - patch_starry_cargo_config(&mut cargo, &request, &metadata).unwrap(); + let err = load_cargo_config(&request).unwrap_err(); assert!( - cargo - .features - .contains(&"ax-driver/rockchip-soc".to_string()) - ); - assert!( - cargo - .features - .contains(&"ax-driver/rockchip-sdhci".to_string()) - ); - assert!(!cargo.features.contains(&"plat-dyn".to_string())); - assert!(!cargo.features.contains(&"qemu".to_string())); - assert!(!cargo.env.contains_key("AX_PLATFORM")); - assert_eq!( - cargo.target, - "scripts/targets/std/pie/aarch64-unknown-linux-musl.json" + err.to_string() + .contains("feature `plat-dyn` is no longer supported"), + "{err:#}" ); } @@ -368,7 +343,7 @@ fn patch_starry_cargo_config_keeps_qemu_as_capability_feature() { ); let build_info = StarryBuildInfo { env: HashMap::new(), - features: vec!["qemu".to_string(), "plat-dyn".to_string()], + features: vec!["qemu".to_string()], log: LogLevel::Info, max_cpu_num: None, }; @@ -381,7 +356,6 @@ fn patch_starry_cargo_config_keeps_qemu_as_capability_feature() { let metadata = crate::build::workspace_metadata().unwrap(); patch_starry_cargo_config(&mut cargo, &request, &metadata).unwrap(); - assert!(!cargo.features.contains(&"plat-dyn".to_string())); assert!(cargo.features.contains(&"qemu".to_string())); assert!(!cargo.env.contains_key("AX_PLATFORM")); } @@ -395,7 +369,7 @@ fn patch_starry_cargo_config_keeps_loongarch64_dynamic_platform_dynamic() { ); let build_info = StarryBuildInfo { env: HashMap::new(), - features: vec!["ax-hal/plat-dyn".to_string(), "axplat-dyn/efi".to_string()], + features: vec!["axplat-dyn/efi".to_string()], log: LogLevel::Info, max_cpu_num: None, }; @@ -408,7 +382,6 @@ fn patch_starry_cargo_config_keeps_loongarch64_dynamic_platform_dynamic() { patch_starry_cargo_config(&mut cargo, &request, &metadata).unwrap(); assert!(!cargo.features.contains(&"qemu".to_string())); - assert!(!cargo.features.contains(&"ax-hal/plat-dyn".to_string())); assert!(cargo.features.contains(&"axplat-dyn/efi".to_string())); assert!(!cargo.env.contains_key("AX_PLATFORM")); } @@ -509,17 +482,15 @@ fn load_cargo_config_keeps_sg2002_as_device_feature_without_static_platform_alia ); request.build_info_override = Some(StarryBuildInfo { features: vec![ - "plat-dyn".to_string(), "starry-kernel/sg2002".to_string(), "axplat-dyn/thead-mae".to_string(), ], - ..default_starry_build_info_for_target("riscv64gc-unknown-none-elf") + ..default_starry_build_info() }); let cargo = load_cargo_config(&request).unwrap(); let removed_sg2002_platform = concat!("ax-hal/", "riscv64", "-sg2002"); - assert!(!cargo.features.contains(&"plat-dyn".to_string())); assert!(cargo.features.contains(&"starry-kernel/sg2002".to_string())); assert!( cargo @@ -546,7 +517,7 @@ fn load_cargo_config_keeps_pie_target_for_non_kmod_dynamic_platform_request() { ); request.build_info_override = Some(StarryBuildInfo { features: vec!["ax-driver/virtio-blk".to_string()], - ..default_starry_build_info_for_target("aarch64-unknown-none-softfloat") + ..default_starry_build_info() }); let cargo = load_cargo_config(&request).unwrap(); @@ -597,7 +568,7 @@ fn patch_starry_cargo_config_preserves_json_target() { qemu_config: None, uboot_config: None, }; - let build_info = default_starry_build_info_for_target(&request.target); + let build_info = default_starry_build_info(); let mut cargo = build_info.into_base_cargo_config_with_log( request.package.clone(), "scripts/targets/std/aarch64-unknown-linux-musl.json".to_string(), diff --git a/scripts/axbuild/src/starry/config.rs b/scripts/axbuild/src/starry/config.rs index 822adcd3b3..d49cf15099 100644 --- a/scripts/axbuild/src/starry/config.rs +++ b/scripts/axbuild/src/starry/config.rs @@ -101,8 +101,9 @@ pub(crate) fn ensure_default_build_config_for_target( .unwrap_or_else(|_| "os/StarryOS/configs/board".to_string()) ) })?; + // This only materializes a missing build config. The command dispatcher owns + // snapshot persistence, so implicit config creation cannot alter it here. write_board_to_build_config(build_config_path, &board)?; - update_snapshot_for_board(workspace_root, &board, build_config_path)?; Ok(Some(board)) } @@ -261,7 +262,7 @@ log = "Info" } #[test] - fn ensure_default_build_config_for_target_generates_missing_file_and_updates_snapshot() { + fn ensure_default_build_config_for_target_generates_missing_file_without_changing_snapshot() { let root = tempdir().unwrap(); write_workspace(root.path()); let source = write_board( @@ -298,15 +299,9 @@ log = "Warn" fs::read_to_string(source).unwrap() ); - let snapshot = StarryCommandSnapshot::load(root.path()).unwrap(); - assert_eq!(snapshot.arch.as_deref(), Some("riscv64")); assert_eq!( - snapshot.target.as_deref(), - Some("riscv64gc-unknown-none-elf") - ); - assert_eq!( - snapshot.config, - Some(PathBuf::from("tmp/custom-starry.toml")) + StarryCommandSnapshot::load(root.path()).unwrap(), + existing_snapshot ); } diff --git a/scripts/axbuild/src/starry/mod.rs b/scripts/axbuild/src/starry/mod.rs index d5389ef438..48f1dea413 100644 --- a/scripts/axbuild/src/starry/mod.rs +++ b/scripts/axbuild/src/starry/mod.rs @@ -266,7 +266,6 @@ impl Starry { if case.snapshot && !qemu.args.iter().any(|arg| arg == "-snapshot") { qemu.args.push("-snapshot".to_string()); } - qemu::apply_dynamic_platform_qemu_boot(&mut qemu, &cargo); if qemu.uefi { qemu::apply_drive_snapshot_without_global_snapshot(&mut qemu); } @@ -320,7 +319,6 @@ impl Starry { rootfs::RootfsPatchMode::EnsureDiskBootNet, ); qemu.args.extend(prepared_assets.extra_qemu_args.clone()); - qemu::apply_dynamic_platform_qemu_boot(&mut qemu, &cargo); println!( " prepare assets: {:.2?} (pipeline={}, cache={})", prepare_started.elapsed(), diff --git a/scripts/axbuild/src/starry/perf/args.rs b/scripts/axbuild/src/starry/perf/args.rs index cd0f8d77af..342ef2f629 100644 --- a/scripts/axbuild/src/starry/perf/args.rs +++ b/scripts/axbuild/src/starry/perf/args.rs @@ -4,11 +4,9 @@ use ostool::build::config::Cargo; use super::super::{ArgsPerf, PerfCallchain, PerfFormat}; pub(super) fn apply_perf_cargo_features(cargo: &mut Cargo, args: &ArgsPerf) { - cargo.features.extend([ - "ax-driver/virtio-blk".to_string(), - "ax-driver/virtio-net".to_string(), - "ax-driver/virtio-socket".to_string(), - ]); + // `starry perf` is an explicit command selection. The package owns the complete feature + // preset so axbuild only selects that declared command capability. + cargo.features.push("qperf".to_string()); if args.qperf_metrics { cargo.features.push("qperf-metrics".to_string()); } diff --git a/scripts/axbuild/src/starry/rootfs.rs b/scripts/axbuild/src/starry/rootfs.rs index 5cae8ca0c7..c1286b8cfd 100644 --- a/scripts/axbuild/src/starry/rootfs.rs +++ b/scripts/axbuild/src/starry/rootfs.rs @@ -105,7 +105,6 @@ pub(super) async fn load_patched_qemu_config( } else if apply_default_args { patch_qemu_rootfs(&mut qemu, request, starry.app.workspace_root(), None, mode)?; } - qemu_test::apply_dynamic_platform_qemu_boot(&mut qemu, cargo); qemu_test::apply_smp_qemu_arg(&mut qemu, request.smp); Ok(qemu) diff --git a/scripts/axbuild/src/starry/test/qemu_run.rs b/scripts/axbuild/src/starry/test/qemu_run.rs index dde5a72267..7e602f2dfd 100644 --- a/scripts/axbuild/src/starry/test/qemu_run.rs +++ b/scripts/axbuild/src/starry/test/qemu_run.rs @@ -226,7 +226,6 @@ impl Starry { ("phase", "prepare-qemu-config".to_string()), ], ); - qemu_test::apply_dynamic_platform_qemu_boot(&mut qemu, cargo); Self::rewrite_qemu_case_managed_rootfs_paths(self.app.workspace_root(), &mut qemu)?; let rootfs_path = Self::qemu_case_rootfs_path(self.app.workspace_root(), &qemu, default_rootfs_path)?; @@ -499,6 +498,12 @@ impl Starry { ); timing_stage.finish(); qemu.args.extend(prepared_assets.extra_qemu_args.clone()); + // UEFI uses a writable ESP for the kernel image. A global `-snapshot` + // makes QEMU treat the VVFAT drive as read-only, so keep snapshot + // isolation on each ordinary disk instead. + if qemu.uefi { + qemu_test::apply_drive_snapshot_without_global_snapshot(&mut qemu); + } let timing_stage = timing::TimingStage::new( "qemu-case", [ @@ -506,7 +511,6 @@ impl Starry { ("phase", "apply-dynamic-boot".to_string()), ], ); - qemu_test::apply_dynamic_platform_qemu_boot(&mut qemu, cargo); timing_stage.finish(); let timing_stage = timing::TimingStage::new( "qemu-case", diff --git a/scripts/axbuild/src/starry/test/tests/qemu_run_tests.rs b/scripts/axbuild/src/starry/test/tests/qemu_run_tests.rs index ecb5103957..097db6ff05 100644 --- a/scripts/axbuild/src/starry/test/tests/qemu_run_tests.rs +++ b/scripts/axbuild/src/starry/test/tests/qemu_run_tests.rs @@ -25,6 +25,27 @@ fn qemu_case_requirements_default_to_single_cpu() { assert_eq!(requirements, StarryQemuCaseRequirements { smp: 1 }); } +#[test] +fn uefi_qemu_snapshot_keeps_esp_writable() { + let mut qemu = QemuConfig { + args: vec![ + "-snapshot".to_string(), + "-drive".to_string(), + "id=disk0,if=none,format=raw,file=/tmp/rootfs.img".to_string(), + ], + uefi: true, + ..Default::default() + }; + + qemu_test::apply_drive_snapshot_without_global_snapshot(&mut qemu); + + assert!(!qemu.args.iter().any(|arg| arg == "-snapshot")); + assert_eq!( + qemu.args[1], + "id=disk0,if=none,format=raw,file=/tmp/rootfs.img,snapshot=on" + ); +} + #[test] fn qemu_case_rootfs_uses_drive_file_arg() { let root = tempdir().unwrap(); @@ -244,7 +265,7 @@ fn qemu_group_build_context_uses_group_build_config_over_default_override() { ); request.build_info_override = Some(crate::starry::build::StarryBuildInfo { max_cpu_num: Some(1), - ..crate::starry::build::default_starry_build_info_for_target("x86_64-unknown-none") + ..crate::starry::build::default_starry_build_info() }); let (_group_request, cargo) = @@ -274,9 +295,7 @@ fn qemu_group_build_context_uses_dynamic_group_platform_over_default_request() { ); request.build_info_override = Some(crate::starry::build::StarryBuildInfo { features: vec!["qemu".to_string()], - ..crate::starry::build::default_starry_build_info_for_target( - "aarch64-unknown-none-softfloat", - ) + ..crate::starry::build::default_starry_build_info() }); let (_group_request, cargo) = diff --git a/scripts/axbuild/src/starry/tests.rs b/scripts/axbuild/src/starry/tests.rs index a1e54ef416..6b56993d1e 100644 --- a/scripts/axbuild/src/starry/tests.rs +++ b/scripts/axbuild/src/starry/tests.rs @@ -1,6 +1,7 @@ -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use clap::Parser; +use ostool::run::qemu::QemuConfig; use super::*; use crate::starry::test::TestCommand; @@ -28,6 +29,22 @@ fn command_parses_test_qemu() { } } +#[test] +fn standard_x86_64_and_loongarch64_qemu_configs_use_uefi_boot() { + let workspace = Path::new(env!("CARGO_MANIFEST_DIR")).join("../.."); + + for arch in ["x86_64", "loongarch64"] { + let path = workspace.join(format!("os/StarryOS/configs/qemu/qemu-{arch}.toml")); + let config: QemuConfig = toml::from_str(&std::fs::read_to_string(path).unwrap()).unwrap(); + + assert!(config.uefi, "Starry {arch} default QEMU path must use UEFI"); + assert!( + config.to_bin, + "Starry {arch} default QEMU path must prepare a BIN" + ); + } +} + #[test] fn command_parses_defconfig() { match parse(["starry", "defconfig", "qemu-aarch64"]) { diff --git a/scripts/axbuild/src/support/axtest_coverage.rs b/scripts/axbuild/src/support/axtest_coverage.rs index a761477148..7163859638 100644 --- a/scripts/axbuild/src/support/axtest_coverage.rs +++ b/scripts/axbuild/src/support/axtest_coverage.rs @@ -26,6 +26,8 @@ pub(crate) fn enabled(cargo: &Cargo) -> bool { } pub(crate) fn prepare_cargo(cargo: &mut Cargo) { + // Coverage is enabled only after the caller explicitly selected coverage + // mode; do not alter ordinary test builds. if !cargo .features .iter() diff --git a/scripts/axbuild/src/test/qemu/boot.rs b/scripts/axbuild/src/test/qemu/boot.rs index 8999dd8832..cd5d5c3355 100644 --- a/scripts/axbuild/src/test/qemu/boot.rs +++ b/scripts/axbuild/src/test/qemu/boot.rs @@ -1,5 +1,8 @@ use super::*; +// QEMU platform, firmware, CPU, and acceleration flags come from the selected TOML file. The +// helpers retained here only apply explicit test controls such as `--smp`, snapshot persistence, +// and timeout scaling. pub(super) struct QemuArgs<'a> { args: &'a [String], } @@ -45,215 +48,6 @@ pub(crate) fn apply_smp_qemu_arg(qemu: &mut QemuConfig, smp: Option) { QemuArgsMut::new(&mut qemu.args).set_option_value("-smp", cpu_num.to_string()); } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(super) enum DynamicPlatformBootArch { - X86_64, - LoongArch64, -} - -pub(crate) fn apply_dynamic_platform_qemu_boot(qemu: &mut QemuConfig, cargo: &Cargo) { - apply_dynamic_platform_qemu_boot_with_kvm_probe(qemu, cargo, host_kvm_available); -} - -pub(super) fn apply_dynamic_platform_qemu_boot_with_kvm_probe( - qemu: &mut QemuConfig, - cargo: &Cargo, - kvm_available: impl FnOnce() -> bool, -) { - apply_x86_64_kvm_accel_if_available_with_probe(qemu, cargo, kvm_available); - - let Some(arch) = cargo_dynamic_platform_boot_arch(cargo) else { - return; - }; - - qemu.uefi = true; - qemu.to_bin = true; - apply_drive_snapshot_without_global_snapshot(qemu); - - if arch != DynamicPlatformBootArch::X86_64 { - return; - } - - ensure_uefi_drive_bus(qemu); - keep_qemu_default_devices_for_uefi(qemu); - disable_unneeded_default_x86_64_devices(qemu); - disable_dynamic_x86_64_five_level_paging(qemu); - enable_dynamic_x86_64_nested_virtualization_features(qemu, cargo); - apply_dynamic_x86_64_qemu_debug_args(qemu); -} - -pub(super) fn apply_x86_64_kvm_accel_if_available_with_probe( - qemu: &mut QemuConfig, - cargo: &Cargo, - kvm_available: impl FnOnce() -> bool, -) { - if !cargo_target_is_x86_64(&cargo.target) { - return; - } - if qemu.args.iter().any(|arg| arg == "-accel") { - return; - } - if !kvm_available() { - return; - } - - qemu.args.push("-accel".to_string()); - qemu.args.push("kvm".to_string()); -} - -#[cfg(unix)] -pub(super) fn host_kvm_available() -> bool { - use std::fs::OpenOptions; - - OpenOptions::new() - .read(true) - .write(true) - .open("/dev/kvm") - .is_ok() -} - -#[cfg(not(unix))] -pub(super) fn host_kvm_available() -> bool { - false -} - -pub(super) fn apply_dynamic_x86_64_qemu_debug_args(qemu: &mut QemuConfig) { - let Ok(value) = std::env::var(DYNAMIC_X86_64_QEMU_DEBUG_ENV) else { - return; - }; - if !env_flag_enabled(&value) { - return; - } - - push_unique_arg(&mut qemu.args, "-no-reboot"); - push_unique_arg(&mut qemu.args, "-S"); - push_unique_arg(&mut qemu.args, "-s"); -} - -pub(super) fn env_flag_enabled(value: &str) -> bool { - !matches!( - value.trim().to_ascii_lowercase().as_str(), - "" | "0" | "false" | "no" | "off" - ) -} - -pub(super) fn push_unique_arg(args: &mut Vec, arg: &str) { - if !args.iter().any(|existing| existing == arg) { - args.push(arg.to_string()); - } -} - -pub(super) fn ensure_uefi_drive_bus(qemu: &mut QemuConfig) { - for index in 0..qemu.args.len() { - if qemu.args.get(index).is_some_and(|arg| arg == "-machine") - && let Some(machine) = qemu.args.get_mut(index + 1) - { - remove_machine_option(machine, "sata=off"); - remove_machine_option(machine, "i8042=off"); - } - } -} - -pub(super) fn remove_machine_option(machine: &mut String, option: &str) { - let parts = machine - .split(',') - .filter(|part| *part != option) - .collect::>(); - *machine = parts.join(","); -} - -pub(super) fn keep_qemu_default_devices_for_uefi(qemu: &mut QemuConfig) { - qemu.args.retain(|arg| arg != "-nodefaults"); -} - -pub(super) fn disable_unneeded_default_x86_64_devices(qemu: &mut QemuConfig) { - let has_network_arg = qemu - .args - .iter() - .any(|arg| matches!(arg.as_str(), "-net" | "-netdev" | "-nic")); - if !has_network_arg { - qemu.args.push("-net".to_string()); - qemu.args.push("none".to_string()); - } - - let has_vga_arg = qemu.args.iter().any(|arg| arg == "-vga"); - if !has_vga_arg { - qemu.args.push("-vga".to_string()); - qemu.args.push("none".to_string()); - } -} - -pub(super) fn disable_dynamic_x86_64_five_level_paging(qemu: &mut QemuConfig) { - for index in 0..qemu.args.len() { - if qemu.args.get(index).is_some_and(|arg| arg == "-cpu") - && let Some(cpu) = qemu.args.get_mut(index + 1) - { - disable_qemu_cpu_feature(cpu, "la57"); - } - } -} - -pub(super) fn enable_dynamic_x86_64_nested_virtualization_features( - qemu: &mut QemuConfig, - cargo: &Cargo, -) { - for index in 0..qemu.args.len() { - if qemu.args.get(index).is_some_and(|arg| arg == "-cpu") - && let Some(cpu) = qemu.args.get_mut(index + 1) - { - for feature in dynamic_x86_64_nested_virtualization_features(cargo) { - enable_qemu_cpu_feature(cpu, feature); - } - } - } -} - -pub(super) fn dynamic_x86_64_nested_virtualization_features( - cargo: &Cargo, -) -> &'static [&'static str] { - if cargo.features.iter().any(|feature| { - matches!( - feature.as_str(), - "svm" | "axvm/svm" | "x86_vcpu/svm" | "x86-vcpu/svm" - ) - }) { - &["svm", "npt", "nrip-save"] - } else if cargo.features.iter().any(|feature| { - matches!( - feature.as_str(), - "vmx" | "axvm/vmx" | "x86_vcpu/vmx" | "x86-vcpu/vmx" - ) - }) { - &["vmx-ept", "vmx-unrestricted-guest", "vmx-flexpriority"] - } else { - &[] - } -} - -pub(super) fn enable_qemu_cpu_feature(cpu: &mut String, feature: &str) { - let enabled_feature = format!("+{feature}"); - if cpu.split(',').any(|part| part.trim() == enabled_feature) { - return; - } - - if !cpu.is_empty() { - cpu.push(','); - } - cpu.push_str(&enabled_feature); -} - -pub(super) fn disable_qemu_cpu_feature(cpu: &mut String, feature: &str) { - let disabled_feature = format!("-{feature}"); - if cpu.split(',').any(|part| part.trim() == disabled_feature) { - return; - } - - if !cpu.is_empty() { - cpu.push(','); - } - cpu.push_str(&disabled_feature); -} - pub(crate) fn apply_drive_snapshot_without_global_snapshot(qemu: &mut QemuConfig) { let mut global_snapshot = false; qemu.args.retain(|arg| { @@ -296,31 +90,6 @@ pub(super) fn ensure_drive_snapshot_on(drive: &mut String) { } } -pub(super) fn cargo_dynamic_platform_boot_arch(cargo: &Cargo) -> Option { - if cargo_target_is_dynamic_x86_64(&cargo.target) { - Some(DynamicPlatformBootArch::X86_64) - } else if cargo_target_is_dynamic_loongarch64(&cargo.target) { - Some(DynamicPlatformBootArch::LoongArch64) - } else { - None - } -} - -pub(super) fn cargo_target_is_dynamic_x86_64(target: &str) -> bool { - cargo_target_is_x86_64(target) -} - -pub(super) fn cargo_target_is_x86_64(target: &str) -> bool { - let target = target.strip_suffix(".json").unwrap_or(target); - target.ends_with("x86_64-unknown-none") || target.ends_with("x86_64-unknown-linux-musl") -} - -pub(super) fn cargo_target_is_dynamic_loongarch64(target: &str) -> bool { - let target = target.strip_suffix(".json").unwrap_or(target); - target.ends_with("loongarch64-unknown-none-softfloat") - || target.ends_with("loongarch64-unknown-linux-musl") -} - pub(crate) fn smp_from_qemu_arg(qemu: &QemuConfig) -> Option { let args = QemuArgs::new(&qemu.args); let value = args.option_value("-smp")?; diff --git a/scripts/axbuild/src/test/qemu/mod.rs b/scripts/axbuild/src/test/qemu/mod.rs index d90df68832..be4bf7dda1 100644 --- a/scripts/axbuild/src/test/qemu/mod.rs +++ b/scripts/axbuild/src/test/qemu/mod.rs @@ -13,7 +13,6 @@ use crate::{ test::case::{HostHttpServerConfig, TestQemuCase, TestQemuSubcase, TestQemuSubcaseKind}, }; -const DYNAMIC_X86_64_QEMU_DEBUG_ENV: &str = "AXBUILD_X86_64_DYN_QEMU_DEBUG"; const TIMEOUT_SCALE_ENV: &str = "AXBUILD_TEST_TIMEOUT_SCALE"; mod boot; @@ -26,8 +25,8 @@ mod tree; mod types; pub(crate) use boot::{ - apply_drive_snapshot_without_global_snapshot, apply_dynamic_platform_qemu_boot, - apply_smp_qemu_arg, apply_timeout_scale, qemu_timeout_summary, smp_from_qemu_arg, + apply_drive_snapshot_without_global_snapshot, apply_smp_qemu_arg, apply_timeout_scale, + qemu_timeout_summary, smp_from_qemu_arg, }; pub(crate) use config::{ load_qemu_case_extra_config, load_qemu_case_host_http_server, load_test_qemu_case_fields, diff --git a/scripts/axbuild/src/test/qemu/tests/boot.rs b/scripts/axbuild/src/test/qemu/tests/boot.rs deleted file mode 100644 index 535816821f..0000000000 --- a/scripts/axbuild/src/test/qemu/tests/boot.rs +++ /dev/null @@ -1,566 +0,0 @@ -use ostool::{build::config::Cargo, run::qemu::QemuConfig}; - -use super::{ENV_LOCK, TempEnvVar}; -use crate::test::qemu::{DYNAMIC_X86_64_QEMU_DEBUG_ENV, boot::*}; - -#[test] -fn dynamic_x86_64_cargo_uses_uefi_bin_qemu_boot() { - let _guard = ENV_LOCK.lock().unwrap(); - let _debug = TempEnvVar::unset(DYNAMIC_X86_64_QEMU_DEBUG_ENV); - let cargo = Cargo { - target: "scripts/targets/std/pie/x86_64-unknown-linux-musl.json".to_string(), - to_bin: true, - ..Default::default() - }; - let mut qemu = QemuConfig { - uefi: false, - to_bin: false, - ..Default::default() - }; - - apply_dynamic_platform_qemu_boot_with_kvm_probe(&mut qemu, &cargo, || false); - - assert!(qemu.uefi); - assert!(qemu.to_bin); -} - -#[test] -fn dynamic_x86_64_std_cargo_uses_uefi_bin_qemu_boot() { - let _guard = ENV_LOCK.lock().unwrap(); - let _debug = TempEnvVar::unset(DYNAMIC_X86_64_QEMU_DEBUG_ENV); - let cargo = Cargo { - target: "scripts/targets/std/pie/x86_64-unknown-linux-musl.json".to_string(), - to_bin: true, - ..Default::default() - }; - let mut qemu = QemuConfig { - uefi: false, - to_bin: false, - ..Default::default() - }; - - apply_dynamic_platform_qemu_boot_with_kvm_probe(&mut qemu, &cargo, || false); - - assert!(qemu.uefi); - assert!(qemu.to_bin); -} - -#[test] -fn axvisor_x86_64_uses_dependency_dynamic_platform_boot_without_feature() { - let _guard = ENV_LOCK.lock().unwrap(); - let _debug = TempEnvVar::unset(DYNAMIC_X86_64_QEMU_DEBUG_ENV); - let cargo = Cargo { - package: "axvisor".to_string(), - target: "scripts/targets/std/pie/x86_64-unknown-linux-musl.json".to_string(), - features: vec![], - to_bin: false, - ..Default::default() - }; - let mut qemu = QemuConfig { - uefi: false, - to_bin: false, - ..Default::default() - }; - - apply_dynamic_platform_qemu_boot_with_kvm_probe(&mut qemu, &cargo, || false); - - assert!(qemu.uefi); - assert!(qemu.to_bin); -} - -#[test] -fn axvisor_loongarch64_uses_dependency_dynamic_platform_boot_without_feature() { - let cargo = Cargo { - package: "axvisor".to_string(), - target: "scripts/targets/std/pie/loongarch64-unknown-linux-musl.json".to_string(), - features: vec![], - to_bin: false, - ..Default::default() - }; - let mut qemu = QemuConfig { - uefi: false, - to_bin: false, - args: vec!["-snapshot".to_string()], - ..Default::default() - }; - - apply_dynamic_platform_qemu_boot_with_kvm_probe(&mut qemu, &cargo, || false); - - assert!(qemu.uefi); - assert!(qemu.to_bin); - assert!(qemu.args.is_empty()); -} - -#[test] -fn dynamic_x86_64_qemu_boot_converts_global_snapshot_to_drive_snapshots() { - let _guard = ENV_LOCK.lock().unwrap(); - let _debug = TempEnvVar::unset(DYNAMIC_X86_64_QEMU_DEBUG_ENV); - let cargo = Cargo { - target: "scripts/targets/std/pie/x86_64-unknown-linux-musl.json".to_string(), - to_bin: true, - ..Default::default() - }; - let mut qemu = QemuConfig { - args: vec![ - "-nographic".to_string(), - "-snapshot".to_string(), - "-drive".to_string(), - "id=disk0,format=raw,file=rootfs.img".to_string(), - "-smp".to_string(), - "1".to_string(), - ], - ..Default::default() - }; - - apply_dynamic_platform_qemu_boot_with_kvm_probe(&mut qemu, &cargo, || false); - - assert_eq!( - qemu.args, - [ - "-nographic", - "-drive", - "id=disk0,format=raw,file=rootfs.img,snapshot=on", - "-smp", - "1", - "-net", - "none", - "-vga", - "none" - ] - ); -} - -#[test] -fn dynamic_loongarch64_cargo_uses_uefi_bin_qemu_boot() { - let _guard = ENV_LOCK.lock().unwrap(); - let _debug = TempEnvVar::unset(DYNAMIC_X86_64_QEMU_DEBUG_ENV); - let cargo = Cargo { - target: "loongarch64-unknown-none-softfloat".to_string(), - to_bin: true, - ..Default::default() - }; - let mut qemu = QemuConfig { - uefi: false, - to_bin: false, - ..Default::default() - }; - - apply_dynamic_platform_qemu_boot_with_kvm_probe(&mut qemu, &cargo, || false); - - assert!(qemu.uefi); - assert!(qemu.to_bin); -} - -#[test] -fn dynamic_loongarch64_std_cargo_uses_uefi_bin_qemu_boot() { - let _guard = ENV_LOCK.lock().unwrap(); - let _debug = TempEnvVar::unset(DYNAMIC_X86_64_QEMU_DEBUG_ENV); - let cargo = Cargo { - target: "scripts/targets/std/pie/loongarch64-unknown-linux-musl.json".to_string(), - to_bin: true, - ..Default::default() - }; - let mut qemu = QemuConfig { - uefi: false, - to_bin: false, - ..Default::default() - }; - - apply_dynamic_platform_qemu_boot_with_kvm_probe(&mut qemu, &cargo, || false); - - assert!(qemu.uefi); - assert!(qemu.to_bin); -} - -#[test] -fn dynamic_loongarch64_qemu_boot_converts_global_snapshot_to_drive_snapshots() { - let _guard = ENV_LOCK.lock().unwrap(); - let _debug = TempEnvVar::unset(DYNAMIC_X86_64_QEMU_DEBUG_ENV); - let cargo = Cargo { - target: "scripts/targets/std/pie/loongarch64-unknown-linux-musl.json".to_string(), - to_bin: true, - ..Default::default() - }; - let mut qemu = QemuConfig { - args: vec![ - "-machine".to_string(), - "virt".to_string(), - "-snapshot".to_string(), - "-drive".to_string(), - "id=disk0,format=raw,file=rootfs.img".to_string(), - ], - ..Default::default() - }; - - apply_dynamic_platform_qemu_boot_with_kvm_probe(&mut qemu, &cargo, || false); - - assert_eq!( - qemu.args, - [ - "-machine", - "virt", - "-drive", - "id=disk0,format=raw,file=rootfs.img,snapshot=on" - ] - ); -} - -#[test] -fn dynamic_x86_64_qemu_boot_keeps_uefi_drive_bus_available() { - let _guard = ENV_LOCK.lock().unwrap(); - let _debug = TempEnvVar::unset(DYNAMIC_X86_64_QEMU_DEBUG_ENV); - let cargo = Cargo { - target: "scripts/targets/std/pie/x86_64-unknown-linux-musl.json".to_string(), - to_bin: true, - ..Default::default() - }; - let mut qemu = QemuConfig { - args: vec![ - "-machine".to_string(), - "q35,sata=off,smbus=off,i8042=off".to_string(), - ], - ..Default::default() - }; - - apply_dynamic_platform_qemu_boot_with_kvm_probe(&mut qemu, &cargo, || false); - - assert_eq!( - qemu.args, - ["-machine", "q35,smbus=off", "-net", "none", "-vga", "none"] - ); -} - -#[test] -fn dynamic_x86_64_qemu_boot_keeps_default_uefi_disk_bus_available() { - let _guard = ENV_LOCK.lock().unwrap(); - let _debug = TempEnvVar::unset(DYNAMIC_X86_64_QEMU_DEBUG_ENV); - let cargo = Cargo { - target: "scripts/targets/std/pie/x86_64-unknown-linux-musl.json".to_string(), - to_bin: true, - ..Default::default() - }; - let mut qemu = QemuConfig { - args: vec![ - "-nodefaults".to_string(), - "-machine".to_string(), - "q35,sata=off,smbus=off,i8042=off".to_string(), - ], - ..Default::default() - }; - - apply_dynamic_platform_qemu_boot_with_kvm_probe(&mut qemu, &cargo, || false); - - assert_eq!( - qemu.args, - ["-machine", "q35,smbus=off", "-net", "none", "-vga", "none"] - ); -} - -#[test] -fn dynamic_x86_64_qemu_boot_disables_five_level_paging_cpu_feature() { - let _guard = ENV_LOCK.lock().unwrap(); - let _debug = TempEnvVar::unset(DYNAMIC_X86_64_QEMU_DEBUG_ENV); - let cargo = Cargo { - target: "scripts/targets/std/pie/x86_64-unknown-linux-musl.json".to_string(), - to_bin: true, - ..Default::default() - }; - let mut qemu = QemuConfig { - args: vec![ - "-cpu".to_string(), - "host,+x2apic".to_string(), - "-machine".to_string(), - "q35".to_string(), - ], - ..Default::default() - }; - - apply_dynamic_platform_qemu_boot_with_kvm_probe(&mut qemu, &cargo, || false); - apply_dynamic_platform_qemu_boot_with_kvm_probe(&mut qemu, &cargo, || false); - - assert_eq!( - qemu.args, - [ - "-cpu", - "host,+x2apic,-la57", - "-machine", - "q35", - "-net", - "none", - "-vga", - "none" - ] - ); -} - -#[test] -fn dynamic_x86_64_qemu_boot_enables_vmx_nested_features_for_vmx_backend() { - let _guard = ENV_LOCK.lock().unwrap(); - let _debug = TempEnvVar::unset(DYNAMIC_X86_64_QEMU_DEBUG_ENV); - let cargo = Cargo { - target: "scripts/targets/pie/x86_64-unknown-none.json".to_string(), - features: vec!["vmx".to_string()], - to_bin: true, - ..Default::default() - }; - let mut qemu = QemuConfig { - args: vec!["-cpu".to_string(), "host".to_string()], - ..Default::default() - }; - - apply_dynamic_platform_qemu_boot_with_kvm_probe(&mut qemu, &cargo, || false); - apply_dynamic_platform_qemu_boot_with_kvm_probe(&mut qemu, &cargo, || false); - - assert_eq!( - qemu.args, - [ - "-cpu", - "host,-la57,+vmx-ept,+vmx-unrestricted-guest,+vmx-flexpriority", - "-net", - "none", - "-vga", - "none" - ] - ); -} - -#[test] -fn dynamic_x86_64_qemu_boot_enables_svm_nested_features_for_svm_backend() { - let _guard = ENV_LOCK.lock().unwrap(); - let _debug = TempEnvVar::unset(DYNAMIC_X86_64_QEMU_DEBUG_ENV); - let cargo = Cargo { - target: "scripts/targets/pie/x86_64-unknown-none.json".to_string(), - features: vec!["svm".to_string()], - to_bin: true, - ..Default::default() - }; - let mut qemu = QemuConfig { - args: vec!["-cpu".to_string(), "host".to_string()], - ..Default::default() - }; - - apply_dynamic_platform_qemu_boot_with_kvm_probe(&mut qemu, &cargo, || false); - apply_dynamic_platform_qemu_boot_with_kvm_probe(&mut qemu, &cargo, || false); - - assert_eq!( - qemu.args, - [ - "-cpu", - "host,-la57,+svm,+npt,+nrip-save", - "-net", - "none", - "-vga", - "none" - ] - ); -} - -#[test] -fn dynamic_x86_64_qemu_boot_keeps_explicit_network_and_vga_args() { - let _guard = ENV_LOCK.lock().unwrap(); - let _debug = TempEnvVar::unset(DYNAMIC_X86_64_QEMU_DEBUG_ENV); - let cargo = Cargo { - target: "scripts/targets/std/pie/x86_64-unknown-linux-musl.json".to_string(), - to_bin: true, - ..Default::default() - }; - let mut qemu = QemuConfig { - args: vec![ - "-netdev".to_string(), - "user,id=net0".to_string(), - "-device".to_string(), - "virtio-net-pci,netdev=net0".to_string(), - "-vga".to_string(), - "std".to_string(), - ], - ..Default::default() - }; - - apply_dynamic_platform_qemu_boot_with_kvm_probe(&mut qemu, &cargo, || false); - - assert_eq!( - qemu.args, - [ - "-netdev", - "user,id=net0", - "-device", - "virtio-net-pci,netdev=net0", - "-vga", - "std" - ] - ); -} - -#[test] -fn dynamic_x86_64_qemu_boot_can_enable_debug_stub() { - let _guard = ENV_LOCK.lock().unwrap(); - let _debug = TempEnvVar::set(DYNAMIC_X86_64_QEMU_DEBUG_ENV, "1"); - let cargo = Cargo { - target: "scripts/targets/std/pie/x86_64-unknown-linux-musl.json".to_string(), - to_bin: true, - ..Default::default() - }; - let mut qemu = QemuConfig { - args: vec!["-nographic".to_string()], - ..Default::default() - }; - - apply_dynamic_platform_qemu_boot_with_kvm_probe(&mut qemu, &cargo, || false); - - assert_eq!( - qemu.args, - [ - "-nographic", - "-net", - "none", - "-vga", - "none", - "-no-reboot", - "-S", - "-s" - ] - ); -} - -#[test] -fn non_dynamic_aarch64_cargo_keeps_existing_qemu_boot() { - let _guard = ENV_LOCK.lock().unwrap(); - let _debug = TempEnvVar::set(DYNAMIC_X86_64_QEMU_DEBUG_ENV, "1"); - let cargo = Cargo { - target: "aarch64-unknown-none-softfloat".to_string(), - features: vec![], - to_bin: false, - ..Default::default() - }; - let mut qemu = QemuConfig { - uefi: false, - to_bin: false, - ..Default::default() - }; - - apply_dynamic_platform_qemu_boot_with_kvm_probe(&mut qemu, &cargo, || false); - - assert!(!qemu.uefi); - assert!(!qemu.to_bin); -} - -#[test] -fn non_dynamic_riscv64_cargo_keeps_existing_qemu_boot() { - let _guard = ENV_LOCK.lock().unwrap(); - let _debug = TempEnvVar::set(DYNAMIC_X86_64_QEMU_DEBUG_ENV, "1"); - let cargo = Cargo { - target: "riscv64gc-unknown-none-elf".to_string(), - features: vec![], - to_bin: false, - ..Default::default() - }; - let mut qemu = QemuConfig { - uefi: false, - to_bin: false, - args: vec!["-snapshot".to_string()], - ..Default::default() - }; - - apply_dynamic_platform_qemu_boot_with_kvm_probe(&mut qemu, &cargo, || false); - - assert!(!qemu.uefi); - assert!(!qemu.to_bin); - assert_eq!(qemu.args, ["-snapshot"]); -} - -#[test] -fn x86_64_qemu_uses_kvm_when_available() { - let cargo = Cargo { - target: "x86_64-unknown-none".to_string(), - ..Default::default() - }; - let mut qemu = QemuConfig { - args: vec!["-nographic".to_string()], - ..Default::default() - }; - - apply_x86_64_kvm_accel_if_available_with_probe(&mut qemu, &cargo, || true); - - assert_eq!(qemu.args, ["-nographic", "-accel", "kvm"]); -} - -#[test] -fn qemu_boot_rewrite_uses_kvm_for_x86_64_when_available() { - let _guard = ENV_LOCK.lock().unwrap(); - let _debug = TempEnvVar::unset(DYNAMIC_X86_64_QEMU_DEBUG_ENV); - let cargo = Cargo { - target: "scripts/targets/std/x86_64-unknown-linux-musl.json".to_string(), - ..Default::default() - }; - let mut qemu = QemuConfig { - args: vec!["-nographic".to_string()], - ..Default::default() - }; - - apply_dynamic_platform_qemu_boot_with_kvm_probe(&mut qemu, &cargo, || true); - - assert_eq!( - qemu.args, - [ - "-nographic", - "-accel", - "kvm", - "-net", - "none", - "-vga", - "none" - ] - ); -} - -#[test] -fn x86_64_qemu_keeps_explicit_accel() { - let cargo = Cargo { - target: "x86_64-unknown-none".to_string(), - ..Default::default() - }; - let mut qemu = QemuConfig { - args: vec![ - "-nographic".to_string(), - "-accel".to_string(), - "tcg,thread=single".to_string(), - ], - ..Default::default() - }; - - apply_x86_64_kvm_accel_if_available_with_probe(&mut qemu, &cargo, || true); - - assert_eq!(qemu.args, ["-nographic", "-accel", "tcg,thread=single"]); -} - -#[test] -fn non_x86_64_qemu_does_not_use_kvm() { - let cargo = Cargo { - target: "riscv64gc-unknown-none-elf".to_string(), - ..Default::default() - }; - let mut qemu = QemuConfig { - args: vec!["-nographic".to_string()], - ..Default::default() - }; - - apply_x86_64_kvm_accel_if_available_with_probe(&mut qemu, &cargo, || true); - - assert_eq!(qemu.args, ["-nographic"]); -} - -#[test] -fn x86_64_qemu_does_not_use_kvm_without_permission() { - let cargo = Cargo { - target: "x86_64-unknown-none".to_string(), - ..Default::default() - }; - let mut qemu = QemuConfig { - args: vec!["-nographic".to_string()], - ..Default::default() - }; - - apply_x86_64_kvm_accel_if_available_with_probe(&mut qemu, &cargo, || false); - - assert_eq!(qemu.args, ["-nographic"]); -} diff --git a/scripts/axbuild/src/test/qemu/tests/mod.rs b/scripts/axbuild/src/test/qemu/tests/mod.rs index b7824a6c7e..9eecca3de4 100644 --- a/scripts/axbuild/src/test/qemu/tests/mod.rs +++ b/scripts/axbuild/src/test/qemu/tests/mod.rs @@ -1,47 +1,2 @@ -use std::{ - env, - ffi::{OsStr, OsString}, - sync::{LazyLock, Mutex}, -}; - -mod boot; mod discovery; mod rendering; - -static ENV_LOCK: LazyLock> = LazyLock::new(|| Mutex::new(())); - -struct TempEnvVar { - key: &'static str, - original: Option, -} - -impl TempEnvVar { - fn set(key: &'static str, value: impl AsRef) -> Self { - let original = env::var_os(key); - unsafe { - env::set_var(key, value); - } - Self { key, original } - } - - fn unset(key: &'static str) -> Self { - let original = env::var_os(key); - unsafe { - env::remove_var(key); - } - Self { key, original } - } -} - -impl Drop for TempEnvVar { - fn drop(&mut self) { - match self.original.as_ref() { - Some(value) => unsafe { - env::set_var(self.key, value); - }, - None => unsafe { - env::remove_var(self.key); - }, - } - } -} diff --git a/test-suit/arceos/axtest/smoke/qemu-loongarch64.toml b/test-suit/arceos/axtest/smoke/qemu-loongarch64.toml index 42e3ff1999..7063b9ba25 100644 --- a/test-suit/arceos/axtest/smoke/qemu-loongarch64.toml +++ b/test-suit/arceos/axtest/smoke/qemu-loongarch64.toml @@ -10,7 +10,8 @@ args = [ ] timeout = 60 -uefi = false +# The LoongArch std/PIC axtest image requires the dynamic UEFI handoff. +uefi = true to_bin = true success_regex = ["AXTEST_SUITE_OK"] fail_regex = ["(?i)\\bpanic(?:ked)?\\b", "AXTEST_SUITE_FAIL", "AXTEST_CASE status=fail"] diff --git a/test-suit/arceos/axtest/smoke/qemu-x86_64.toml b/test-suit/arceos/axtest/smoke/qemu-x86_64.toml index 4c12b26204..803ae4a266 100644 --- a/test-suit/arceos/axtest/smoke/qemu-x86_64.toml +++ b/test-suit/arceos/axtest/smoke/qemu-x86_64.toml @@ -10,7 +10,8 @@ args = [ ] timeout = 60 -uefi = false -to_bin = false +# The x86_64 std/PIC axtest image requires the dynamic UEFI handoff. +uefi = true +to_bin = true success_regex = ["AXTEST_SUITE_OK"] fail_regex = ["(?i)\\bpanic(?:ked)?\\b", "AXTEST_SUITE_FAIL", "AXTEST_CASE status=fail"] diff --git a/test-suit/arceos/c/qemu-loongarch64.toml b/test-suit/arceos/c/qemu-loongarch64.toml index d1f64409a6..e6d474a13f 100644 --- a/test-suit/arceos/c/qemu-loongarch64.toml +++ b/test-suit/arceos/c/qemu-loongarch64.toml @@ -9,7 +9,9 @@ args = [ "user,id=net0", "-nographic", ] -uefi = false +# LoongArch dynamic platform C images use the same UEFI boot contract as the +# Rust suite; keep it explicit instead of relying on axbuild boot rewriting. +uefi = true to_bin = true success_regex = ["ArceOS C test suite run OK!"] fail_regex = ["(?i)\\bpanic(?:ked)?\\b", "ARCEOS_C_TEST_FAIL"] diff --git a/test-suit/arceos/c/qemu-x86_64.toml b/test-suit/arceos/c/qemu-x86_64.toml index 5f7a14fcc1..020e114352 100644 --- a/test-suit/arceos/c/qemu-x86_64.toml +++ b/test-suit/arceos/c/qemu-x86_64.toml @@ -7,8 +7,10 @@ args = [ "-netdev", "user,id=net0", ] -uefi = false -to_bin = false +# x86_64 dynamic platform images boot through the configured UEFI firmware. +# Keep the boot contract explicit instead of relying on axbuild rewriting it. +uefi = true +to_bin = true success_regex = ["ArceOS C test suite run OK!"] fail_regex = ["(?i)\\bpanic(?:ked)?\\b", "ARCEOS_C_TEST_FAIL"] timeout = 120 diff --git a/test-suit/arceos/rust/Cargo.toml b/test-suit/arceos/rust/Cargo.toml index 72fb5e2220..3eb7815c8a 100644 --- a/test-suit/arceos/rust/Cargo.toml +++ b/test-suit/arceos/rust/Cargo.toml @@ -7,7 +7,9 @@ publish = false [features] default = [] std = [] -ax-std = ["dep:ax-driver", "dep:ax-hal", "dep:ax-std"] +# Every kernel test reaches ax-std through this feature, so it owns the ArceOS +# baseline instead of relying on axbuild to add it for the selected test case. +ax-std = ["dep:ax-driver", "dep:ax-hal", "dep:ax-std", "ax-std/arceos"] memtest = ["ax-std", "dep:rand", "ax-std/alloc", "ax-std/multitask"] task-yield = ["ax-std", "ax-std/multitask"] sched-rr = ["ax-std", "ax-std/alloc", "ax-std/multitask", "ax-std/sched-rr"] diff --git a/test-suit/arceos/rust/qemu-aarch64.toml b/test-suit/arceos/rust/qemu-aarch64.toml index b64190502c..45d1bc80b0 100644 --- a/test-suit/arceos/rust/qemu-aarch64.toml +++ b/test-suit/arceos/rust/qemu-aarch64.toml @@ -25,7 +25,7 @@ args = [ timeout = 120 uefi = false -to_bin = false +to_bin = true success_regex = ["ArceOS test suite run OK!"] fail_regex = ["(?i)\\bpanic(?:ked)?\\b", "ARCEOS_TEST_FAIL"] diff --git a/test-suit/arceos/rust/qemu-loongarch64.toml b/test-suit/arceos/rust/qemu-loongarch64.toml index 710ff27427..81d4a23ebc 100644 --- a/test-suit/arceos/rust/qemu-loongarch64.toml +++ b/test-suit/arceos/rust/qemu-loongarch64.toml @@ -20,7 +20,9 @@ args = [ ] timeout = 120 -uefi = false +# LoongArch dynamic platform images boot through the configured UEFI firmware. +# Keeping this in the test config avoids relying on axbuild to rewrite QEMU args. +uefi = true to_bin = true success_regex = ["ArceOS test suite run OK!"] fail_regex = ["(?i)\\bpanic(?:ked)?\\b", "ARCEOS_TEST_FAIL"] diff --git a/test-suit/arceos/rust/qemu-x86_64.toml b/test-suit/arceos/rust/qemu-x86_64.toml index f4c60d66fb..9f030175e5 100644 --- a/test-suit/arceos/rust/qemu-x86_64.toml +++ b/test-suit/arceos/rust/qemu-x86_64.toml @@ -20,8 +20,10 @@ args = [ ] timeout = 120 -uefi = false -to_bin = false +# x86_64 dynamic platform images boot through the configured UEFI firmware. +# Keep the boot contract explicit instead of relying on axbuild rewriting it. +uefi = true +to_bin = true success_regex = ["ArceOS test suite run OK!"] fail_regex = ["(?i)\\bpanic(?:ked)?\\b", "ARCEOS_TEST_FAIL"] diff --git a/test-suit/axvisor/normal/board-asus-nuc15crh/build-x86_64-unknown-none.toml b/test-suit/axvisor/normal/board-asus-nuc15crh/build-x86_64-unknown-none.toml index d2652d2b45..eab0c7e03f 100644 --- a/test-suit/axvisor/normal/board-asus-nuc15crh/build-x86_64-unknown-none.toml +++ b/test-suit/axvisor/normal/board-asus-nuc15crh/build-x86_64-unknown-none.toml @@ -1,5 +1,5 @@ env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2", RDRIVE_ACPI_LOAD_AML = "0" } -features = [] +features = ["vmx"] log = "Info" target = "x86_64-unknown-none" vm_configs = ["os/axvisor/configs/vms/asus-nuc15crh/linux-smp1.toml"] diff --git a/test-suit/axvisor/normal/qemu/smoke/qemu-x86_64-svm.toml b/test-suit/axvisor/normal/qemu/smoke/qemu-x86_64-svm.toml index e5553095f3..1e92e3db24 100644 --- a/test-suit/axvisor/normal/qemu/smoke/qemu-x86_64-svm.toml +++ b/test-suit/axvisor/normal/qemu/smoke/qemu-x86_64-svm.toml @@ -1,5 +1,4 @@ args = [ - "-nodefaults", "-no-user-config", "-display", "none", @@ -8,9 +7,9 @@ args = [ "-monitor", "none", "-cpu", - "host", + "host,-la57,+svm,+npt,+nrip-save", "-machine", - "q35,sata=off,smbus=off,i8042=off,usb=off,graphics=off", + "q35,smbus=off,usb=off,graphics=off", "-smp", "1", "-accel", @@ -21,6 +20,10 @@ args = [ "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-x86_64-alpine.img", "-m", "512M", + "-net", + "none", + "-vga", + "none", ] timeout = 600 fail_regex = [ @@ -32,5 +35,5 @@ fail_regex = [ success_regex = ["(?m)^guest linux test pass!\\s*$"] shell_prefix = "~ #" shell_init_cmd = "pwd && echo 'guest linux test pass!'" -to_bin = false -uefi = false +to_bin = true +uefi = true diff --git a/test-suit/axvisor/normal/qemu/smoke/qemu-x86_64-vmx.toml b/test-suit/axvisor/normal/qemu/smoke/qemu-x86_64-vmx.toml index e5553095f3..c65401610c 100644 --- a/test-suit/axvisor/normal/qemu/smoke/qemu-x86_64-vmx.toml +++ b/test-suit/axvisor/normal/qemu/smoke/qemu-x86_64-vmx.toml @@ -1,5 +1,4 @@ args = [ - "-nodefaults", "-no-user-config", "-display", "none", @@ -8,9 +7,9 @@ args = [ "-monitor", "none", "-cpu", - "host", + "host,-la57,+vmx-ept,+vmx-unrestricted-guest,+vmx-flexpriority", "-machine", - "q35,sata=off,smbus=off,i8042=off,usb=off,graphics=off", + "q35,smbus=off,usb=off,graphics=off", "-smp", "1", "-accel", @@ -21,6 +20,10 @@ args = [ "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-x86_64-alpine.img", "-m", "512M", + "-net", + "none", + "-vga", + "none", ] timeout = 600 fail_regex = [ @@ -32,5 +35,5 @@ fail_regex = [ success_regex = ["(?m)^guest linux test pass!\\s*$"] shell_prefix = "~ #" shell_init_cmd = "pwd && echo 'guest linux test pass!'" -to_bin = false -uefi = false +to_bin = true +uefi = true diff --git a/test-suit/axvisor/uefi/qemu-nimbos/qemu-x86_64.toml b/test-suit/axvisor/uefi/qemu-nimbos/qemu-x86_64.toml index 075dc08c59..b3c7d6b450 100644 --- a/test-suit/axvisor/uefi/qemu-nimbos/qemu-x86_64.toml +++ b/test-suit/axvisor/uefi/qemu-nimbos/qemu-x86_64.toml @@ -21,10 +21,11 @@ fail_regex = [ "(?i)kernel panic", "(?i)permission denied", ] -# Keep this CI case as a bounded UEFI smoke test. Full x86_64 VMX UEFI -# guest execution is still incomplete and does not reliably reach NimbOS -# usertests yet. +# Keep this CI case as a bounded UEFI smoke test. The host AxVisor image must +# also be entered through OVMF; otherwise QEMU falls back to SeaBIOS and the +# UEFI-only AxVisor/NimbOS handoff never starts. success_regex = ["VM\\[1\\] boot success"] timeout = 180 -to_bin = false -uefi = false +# UEFI execution requires the converted BIN artifact on the ESP. +to_bin = true +uefi = true diff --git a/test-suit/starryos/qemu/system/qemu-loongarch64.toml b/test-suit/starryos/qemu/system/qemu-loongarch64.toml index 1aa7b8d879..8423462a54 100644 --- a/test-suit/starryos/qemu/system/qemu-loongarch64.toml +++ b/test-suit/starryos/qemu/system/qemu-loongarch64.toml @@ -23,7 +23,8 @@ args = [ "-device", "virtio-tablet-pci", ] -uefi = false +# The std/PIC test image must be loaded through the LoongArch UEFI handoff. +uefi = true to_bin = true shell_prefix = "root@starry:" test_commands = [ diff --git a/test-suit/starryos/qemu/system/qemu-x86_64.toml b/test-suit/starryos/qemu/system/qemu-x86_64.toml index 377b91aa7d..322372f268 100644 --- a/test-suit/starryos/qemu/system/qemu-x86_64.toml +++ b/test-suit/starryos/qemu/system/qemu-x86_64.toml @@ -31,8 +31,9 @@ args = [ "-device", "usb-storage,bus=xhci.0,drive=usbdisk,serial=starry-usb-storage", ] -uefi = false -to_bin = false +# The std/PIC test image must use the x86_64 UEFI handoff instead of direct -kernel loading. +uefi = true +to_bin = true shell_prefix = "root@starry:" test_commands = [ ''' diff --git a/test-suit/starryos/qemu/tty-console-input-burst/qemu-loongarch64.toml b/test-suit/starryos/qemu/tty-console-input-burst/qemu-loongarch64.toml index f99eae7853..4ec6e24d00 100644 --- a/test-suit/starryos/qemu/tty-console-input-burst/qemu-loongarch64.toml +++ b/test-suit/starryos/qemu/tty-console-input-burst/qemu-loongarch64.toml @@ -21,7 +21,8 @@ args = [ "-device", "virtio-tablet-pci", ] -uefi = false +# This dynamic LoongArch test uses the same UEFI handoff as the Starry kernel test image. +uefi = true to_bin = true shell_prefix = "root@starry:" shell_init_cmd = "/usr/bin/tty-input-burst.sh" diff --git a/test-suit/starryos/qemu/tty-console-input-burst/qemu-x86_64.toml b/test-suit/starryos/qemu/tty-console-input-burst/qemu-x86_64.toml index b0ecc96b7f..caa352ee11 100644 --- a/test-suit/starryos/qemu/tty-console-input-burst/qemu-x86_64.toml +++ b/test-suit/starryos/qemu/tty-console-input-burst/qemu-x86_64.toml @@ -7,8 +7,9 @@ args = [ "-drive", "id=nvm,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-x86_64-alpine.img", ] -uefi = false -to_bin = false +# This std/PIC test image requires the x86_64 UEFI handoff before the tty test starts. +uefi = true +to_bin = true shell_prefix = "root@starry:" shell_init_cmd = "/usr/bin/tty-input-burst.sh" success_regex = ["(?m)^STARRY_TTY_INPUT_BURST_PASSED\\s*$"]