From 12858f14bde7849d19f15737cf5fee292a3df0f9 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 18 Feb 2026 18:59:56 +0800 Subject: [PATCH 01/11] ci: add dependency-aware test target selection Add `cargo xtask affected` command that analyzes git changes against a base ref and determines which test targets (QEMU aarch64/x86_64, board phytiumpi/rk3568) actually need to run. The analysis works in three phases: 1. git diff detects changed files 2. cargo metadata builds the workspace reverse dependency graph, then BFS propagates changes to all affected crates 3. Path-based and crate-based rules map affected crates to concrete test targets Update CI workflows (test-qemu.yml, test-board.yml) to run a lightweight `detect` job on ubuntu-latest before dispatching actual tests on self-hosted hardware runners, skipping targets unaffected by the change. Co-authored-by: Cursor --- .github/workflows/test-board.yml | 80 +++++- .github/workflows/test-qemu.yml | 83 +++++-- xtask/src/affected.rs | 401 +++++++++++++++++++++++++++++++ xtask/src/main.rs | 13 + 4 files changed, 547 insertions(+), 30 deletions(-) create mode 100644 xtask/src/affected.rs diff --git a/.github/workflows/test-board.yml b/.github/workflows/test-board.yml index 3719e76e..6258c041 100644 --- a/.github/workflows/test-board.yml +++ b/.github/workflows/test-board.yml @@ -3,23 +3,77 @@ name: Test for BOARD on: [push, pull_request, workflow_dispatch] jobs: + detect: + name: "Detect affected targets" + runs-on: ubuntu-latest + outputs: + skip_all: ${{ steps.analyze.outputs.skip_all }} + board_phytiumpi: ${{ steps.analyze.outputs.board_phytiumpi }} + board_rk3568: ${{ steps.analyze.outputs.board_rk3568 }} + board_matrix: ${{ steps.matrix.outputs.board_matrix }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Rust toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Cache cargo build + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: affected-${{ runner.os }}-${{ hashFiles('xtask/Cargo.toml') }} + + - name: Determine base ref + id: baseref + run: | + if [ "${{ github.event_name }}" = "pull_request" ]; then + echo "ref=origin/${{ github.base_ref }}" >> "$GITHUB_OUTPUT" + elif [ "${{ github.event_name }}" = "push" ]; then + if [ "${{ github.event.before }}" != "0000000000000000000000000000000000000000" ]; then + echo "ref=${{ github.event.before }}" >> "$GITHUB_OUTPUT" + else + echo "ref=HEAD~1" >> "$GITHUB_OUTPUT" + fi + else + echo "ref=origin/main" >> "$GITHUB_OUTPUT" + fi + + - name: Analyze affected targets + id: analyze + run: cargo xtask affected --base "${{ steps.baseref.outputs.ref }}" + + - name: Build board test matrix + id: matrix + run: | + MATRIX="[]" + if [ "${{ steps.analyze.outputs.board_phytiumpi }}" = "true" ]; then + MATRIX=$(echo "$MATRIX" | jq -c '. + [ + {"board":"phytiumpi","vmconfigs":"configs/vms/arceos-aarch64-e2000-smp1.toml","vmconfigs_name":"ArceOS"}, + {"board":"phytiumpi","vmconfigs":"configs/vms/linux-aarch64-e2000-smp1.toml","vmconfigs_name":"Linux"} + ]') + fi + if [ "${{ steps.analyze.outputs.board_rk3568 }}" = "true" ]; then + MATRIX=$(echo "$MATRIX" | jq -c '. + [ + {"board":"roc-rk3568-pc","vmconfigs":"configs/vms/arceos-aarch64-rk3568-smp1.toml","vmconfigs_name":"ArceOS"}, + {"board":"roc-rk3568-pc","vmconfigs":"configs/vms/linux-aarch64-rk3568-smp1.toml","vmconfigs_name":"Linux"} + ]') + fi + echo "board_matrix=$MATRIX" >> "$GITHUB_OUTPUT" + echo "Generated board matrix: $MATRIX" + test-board: + needs: detect + if: needs.detect.outputs.skip_all != 'true' && needs.detect.outputs.board_matrix != '[]' name: "Test board: ${{ matrix.board }} - ${{ matrix.vmconfigs_name }}" strategy: matrix: - include: - - board: phytiumpi - vmconfigs: configs/vms/arceos-aarch64-e2000-smp1.toml - vmconfigs_name: ArceOS - - board: phytiumpi - vmconfigs: configs/vms/linux-aarch64-e2000-smp1.toml - vmconfigs_name: Linux - - board: roc-rk3568-pc - vmconfigs: configs/vms/arceos-aarch64-rk3568-smp1.toml - vmconfigs_name: ArceOS - - board: roc-rk3568-pc - vmconfigs: configs/vms/linux-aarch64-rk3568-smp1.toml - vmconfigs_name: Linux + include: ${{ fromJson(needs.detect.outputs.board_matrix) }} fail-fast: false runs-on: - self-hosted diff --git a/.github/workflows/test-qemu.yml b/.github/workflows/test-qemu.yml index 8ba4c1e5..3f2650af 100644 --- a/.github/workflows/test-qemu.yml +++ b/.github/workflows/test-qemu.yml @@ -3,27 +3,76 @@ name: Test for QEMU on: [push, pull_request, workflow_dispatch] jobs: + detect: + name: "Detect affected targets" + runs-on: ubuntu-latest + outputs: + skip_all: ${{ steps.analyze.outputs.skip_all }} + qemu_aarch64: ${{ steps.analyze.outputs.qemu_aarch64 }} + qemu_x86_64: ${{ steps.analyze.outputs.qemu_x86_64 }} + qemu_matrix: ${{ steps.matrix.outputs.qemu_matrix }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Rust toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Cache cargo build + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: affected-${{ runner.os }}-${{ hashFiles('xtask/Cargo.toml') }} + + - name: Determine base ref + id: baseref + run: | + if [ "${{ github.event_name }}" = "pull_request" ]; then + echo "ref=origin/${{ github.base_ref }}" >> "$GITHUB_OUTPUT" + elif [ "${{ github.event_name }}" = "push" ]; then + if [ "${{ github.event.before }}" != "0000000000000000000000000000000000000000" ]; then + echo "ref=${{ github.event.before }}" >> "$GITHUB_OUTPUT" + else + echo "ref=HEAD~1" >> "$GITHUB_OUTPUT" + fi + else + echo "ref=origin/main" >> "$GITHUB_OUTPUT" + fi + + - name: Analyze affected targets + id: analyze + run: cargo xtask affected --base "${{ steps.baseref.outputs.ref }}" + + - name: Build QEMU test matrix + id: matrix + run: | + MATRIX="[]" + if [ "${{ steps.analyze.outputs.qemu_aarch64 }}" = "true" ]; then + MATRIX=$(echo "$MATRIX" | jq -c '. + [ + {"arch":"aarch64","vmconfigs":"configs/vms/arceos-aarch64-qemu-smp1.toml","vmconfigs_name":"ArceOS","vmimage_name":"qemu_aarch64_arceos"}, + {"arch":"aarch64","vmconfigs":"configs/vms/linux-aarch64-qemu-smp1.toml","vmconfigs_name":"Linux","vmimage_name":"qemu_aarch64_linux"} + ]') + fi + if [ "${{ steps.analyze.outputs.qemu_x86_64 }}" = "true" ]; then + MATRIX=$(echo "$MATRIX" | jq -c '. + [ + {"arch":"x86_64","vmconfigs":"configs/vms/nimbos-x86_64-qemu-smp1.toml","vmconfigs_name":"NimbOS","vmimage_name":"qemu_x86_64_nimbos"} + ]') + fi + echo "qemu_matrix=$MATRIX" >> "$GITHUB_OUTPUT" + echo "Generated QEMU matrix: $MATRIX" + test-qemu: + needs: detect + if: needs.detect.outputs.skip_all != 'true' && needs.detect.outputs.qemu_matrix != '[]' name: "Test qemu: ${{ matrix.arch }} - ${{ matrix.vmconfigs_name }}" strategy: matrix: - include: - - arch: aarch64 - vmconfigs: configs/vms/arceos-aarch64-qemu-smp1.toml - vmconfigs_name: ArceOS - vmimage_name: qemu_aarch64_arceos - - arch: aarch64 - vmconfigs: configs/vms/linux-aarch64-qemu-smp1.toml - vmconfigs_name: Linux - vmimage_name: qemu_aarch64_linux - # - arch: riscv64 - # vmconfigs: configs/vms/arceos-riscv64-qemu-smp1.toml - # vmconfigs_name: ArceOS - # vmimage_name: qemu_arceos_riscv64 - - arch: x86_64 - vmconfigs: configs/vms/nimbos-x86_64-qemu-smp1.toml - vmconfigs_name: NimbOS - vmimage_name: qemu_x86_64_nimbos + include: ${{ fromJson(needs.detect.outputs.qemu_matrix) }} fail-fast: false runs-on: - self-hosted diff --git a/xtask/src/affected.rs b/xtask/src/affected.rs new file mode 100644 index 00000000..292f9468 --- /dev/null +++ b/xtask/src/affected.rs @@ -0,0 +1,401 @@ +// Copyright 2025 The Axvisor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Dependency-aware test scope analysis. +//! +//! Determines which test targets (QEMU configurations, development boards) need +//! to run based on the files changed in a git commit or pull request. +//! +//! The analysis works in three phases: +//! 1. **File detection**: `git diff` identifies changed files +//! 2. **Dependency propagation**: `cargo metadata` builds the workspace dependency +//! graph, then a reverse BFS finds all transitively affected crates +//! 3. **Target mapping**: Changed files and affected crates are mapped to concrete +//! test targets using path-based and crate-based rules + +use std::collections::{HashMap, HashSet, VecDeque}; +use std::io::Write; +use std::process::Command; + +use anyhow::{Context, Result}; +use cargo_metadata::MetadataCommand; +use serde::Serialize; + +/// Boolean flags indicating which test targets should run. +#[derive(Debug, Default, Serialize)] +pub struct TestScope { + pub skip_all: bool, + pub qemu_aarch64: bool, + pub qemu_x86_64: bool, + pub board_phytiumpi: bool, + pub board_rk3568: bool, + pub changed_crates: Vec, + pub affected_crates: Vec, +} + +impl TestScope { + fn all() -> Self { + Self { + qemu_aarch64: true, + qemu_x86_64: true, + board_phytiumpi: true, + board_rk3568: true, + ..Default::default() + } + } + + fn enable_all_aarch64(&mut self) { + self.qemu_aarch64 = true; + self.board_phytiumpi = true; + self.board_rk3568 = true; + } + + fn any_enabled(&self) -> bool { + self.qemu_aarch64 || self.qemu_x86_64 || self.board_phytiumpi || self.board_rk3568 + } +} + +type CrateMap = HashMap; +type ReverseDeps = HashMap>; + +/// Entry point: analyze changes against `base_ref` and print the result. +pub fn run(base_ref: &str) -> Result<()> { + let scope = analyze(base_ref)?; + + // Write to $GITHUB_OUTPUT when running inside GitHub Actions. + if let Ok(path) = std::env::var("GITHUB_OUTPUT") { + let mut file = std::fs::OpenOptions::new() + .append(true) + .open(&path) + .with_context(|| format!("Failed to open GITHUB_OUTPUT at {path}"))?; + writeln!(file, "skip_all={}", scope.skip_all)?; + writeln!(file, "qemu_aarch64={}", scope.qemu_aarch64)?; + writeln!(file, "qemu_x86_64={}", scope.qemu_x86_64)?; + writeln!(file, "board_phytiumpi={}", scope.board_phytiumpi)?; + writeln!(file, "board_rk3568={}", scope.board_rk3568)?; + } + + println!("{}", serde_json::to_string_pretty(&scope)?); + Ok(()) +} + +fn analyze(base_ref: &str) -> Result { + let changed_files = get_changed_files(base_ref)?; + + eprintln!("[affected] changed files ({}):", changed_files.len()); + for f in &changed_files { + eprintln!(" {f}"); + } + + if changed_files.is_empty() { + eprintln!("[affected] no changes detected → skip all tests"); + return Ok(TestScope { skip_all: true, ..Default::default() }); + } + + let has_code_changes = changed_files.iter().any(|f| !is_non_code_file(f)); + if !has_code_changes { + eprintln!("[affected] only non-code files changed → skip all tests"); + return Ok(TestScope { skip_all: true, ..Default::default() }); + } + + // Phase 1 & 2: build dependency graph and propagate changes. + let (crate_map, reverse_deps) = build_workspace_graph()?; + let changed_crates = map_files_to_crates(&changed_files, &crate_map); + let affected_crates = find_all_affected(&changed_crates, &reverse_deps); + + eprintln!("[affected] directly changed crates: {:?}", changed_crates); + eprintln!("[affected] all affected crates: {:?}", affected_crates); + + // Phase 3: map to test targets. + let mut scope = determine_targets(&changed_files, &affected_crates); + scope.changed_crates = sorted_vec(&changed_crates); + scope.affected_crates = sorted_vec(&affected_crates); + + eprintln!("[affected] test scope: qemu_aarch64={} qemu_x86_64={} board_phytiumpi={} board_rk3568={}", + scope.qemu_aarch64, scope.qemu_x86_64, scope.board_phytiumpi, scope.board_rk3568); + + Ok(scope) +} + +// --------------------------------------------------------------------------- +// Phase 1: detect changed files +// --------------------------------------------------------------------------- + +fn get_changed_files(base_ref: &str) -> Result> { + let try_diff = |args: &[&str]| -> Option> { + let output = Command::new("git").args(args).output().ok()?; + if !output.status.success() { + return None; + } + Some( + String::from_utf8(output.stdout) + .ok()? + .lines() + .filter(|l| !l.is_empty()) + .map(String::from) + .collect(), + ) + }; + + // Try the requested base ref first, fall back to HEAD~1. + if let Some(files) = try_diff(&["diff", "--name-only", base_ref]) { + return Ok(files); + } + eprintln!("[affected] base ref '{base_ref}' not reachable, falling back to HEAD~1"); + + try_diff(&["diff", "--name-only", "HEAD~1"]) + .context("git diff failed for both the requested base ref and HEAD~1") +} + +fn is_non_code_file(path: &str) -> bool { + const SKIP_DIRS: &[&str] = &["doc/"]; + const SKIP_EXTS: &[&str] = &[".md", ".txt", ".png", ".jpg", ".jpeg", ".svg", ".gif"]; + const SKIP_FILES: &[&str] = &["LICENSE", ".gitignore", ".gitattributes"]; + + SKIP_DIRS.iter().any(|d| path.starts_with(d)) + || SKIP_EXTS.iter().any(|e| path.ends_with(e)) + || SKIP_FILES.iter().any(|f| path == *f) +} + +// --------------------------------------------------------------------------- +// Phase 2: workspace dependency graph & propagation +// --------------------------------------------------------------------------- + +fn build_workspace_graph() -> Result<(CrateMap, ReverseDeps)> { + let metadata = MetadataCommand::new() + .exec() + .context("cargo metadata failed")?; + + let ws_root = metadata.workspace_root.as_str(); + let ws_ids: HashSet<_> = metadata.workspace_members.iter().collect(); + + let mut crate_map = CrateMap::new(); + let mut id_to_name = HashMap::new(); + + for pkg in &metadata.packages { + if ws_ids.contains(&pkg.id) { + let dir = pkg + .manifest_path + .parent() + .unwrap() + .strip_prefix(ws_root) + .unwrap_or(pkg.manifest_path.parent().unwrap()) + .to_string(); + // Ensure the directory path ends with '/' for prefix matching. + let dir = if dir.is_empty() { String::new() } else { format!("{dir}/") }; + crate_map.insert(pkg.name.clone(), dir); + id_to_name.insert(pkg.id.clone(), pkg.name.clone()); + } + } + + let mut reverse_deps = ReverseDeps::new(); + if let Some(resolve) = &metadata.resolve { + for node in &resolve.nodes { + let Some(node_name) = id_to_name.get(&node.id) else { continue }; + for dep in &node.deps { + if let Some(dep_name) = id_to_name.get(&dep.pkg) { + reverse_deps + .entry(dep_name.clone()) + .or_default() + .insert(node_name.clone()); + } + } + } + } + + eprintln!("[affected] workspace crates: {:?}", crate_map.keys().collect::>()); + eprintln!("[affected] reverse deps:"); + for (k, v) in &reverse_deps { + eprintln!(" {k} ← {:?}", v); + } + + Ok((crate_map, reverse_deps)) +} + +fn map_files_to_crates(files: &[String], crate_map: &CrateMap) -> HashSet { + let mut result = HashSet::new(); + for file in files { + // Pick the longest matching prefix to handle nested crate directories. + let mut best: Option<&str> = None; + for (name, dir) in crate_map { + if !dir.is_empty() && file.starts_with(dir.as_str()) { + if best.is_none() || dir.len() > crate_map[best.unwrap()].len() { + best = Some(name.as_str()); + } + } + } + if let Some(name) = best { + result.insert(name.to_string()); + } + } + result +} + +fn find_all_affected(changed: &HashSet, reverse_deps: &ReverseDeps) -> HashSet { + let mut affected = changed.clone(); + let mut queue: VecDeque<_> = changed.iter().cloned().collect(); + + while let Some(current) = queue.pop_front() { + if let Some(dependents) = reverse_deps.get(¤t) { + for dep in dependents { + if affected.insert(dep.clone()) { + queue.push_back(dep.clone()); + } + } + } + } + affected +} + +// --------------------------------------------------------------------------- +// Phase 3: map affected crates + changed files → test targets +// --------------------------------------------------------------------------- + +fn determine_targets(changed_files: &[String], affected_crates: &HashSet) -> TestScope { + let mut scope = TestScope::default(); + + // ── Rule 1: root build config changes → run everything ── + if changed_files.iter().any(|f| { + matches!(f.as_str(), "Cargo.toml" | "Cargo.lock" | "rust-toolchain.toml") + }) { + return TestScope::all(); + } + + // ── Rule 2: build-tool (xtask) changes → run everything ── + if affected_crates.contains("xtask") { + return TestScope::all(); + } + + // ── Rule 3: core module changes → run everything ── + // axruntime and axconfig are foundational; a change propagates to all targets. + if ["axruntime", "axconfig"] + .iter() + .any(|c| affected_crates.contains(*c)) + { + return TestScope::all(); + } + + // ── Rule 4: kernel common code (non-arch-specific) → run everything ── + if changed_files.iter().any(|f| { + f.starts_with("kernel/") && !f.starts_with("kernel/src/hal/arch/") + }) { + return TestScope::all(); + } + + // ── Rule 5: architecture-specific kernel code ── + for file in changed_files { + if file.starts_with("kernel/src/hal/arch/aarch64/") { + scope.enable_all_aarch64(); + } + if file.starts_with("kernel/src/hal/arch/x86_64/") { + scope.qemu_x86_64 = true; + } + } + + // ── Rule 6: platform crate ── + if affected_crates.contains("axplat-x86-qemu-q35") { + scope.qemu_x86_64 = true; + } + + // ── Rule 7: filesystem module → targets with `fs` feature ── + if affected_crates.contains("axfs") { + scope.qemu_aarch64 = true; // linux guest uses rootfs + scope.board_phytiumpi = true; + scope.board_rk3568 = true; + } + + // ── Rule 8: driver module → board-specific analysis ── + if affected_crates.contains("driver") { + let phytium = changed_files.iter().any(|f| f.contains("phytium")); + let rockchip = changed_files + .iter() + .any(|f| f.contains("rockchip") || f.contains("rk3568")); + let common_driver = changed_files.iter().any(|f| { + f.starts_with("modules/driver/") + && !f.contains("phytium") + && !f.contains("rockchip") + && !f.contains("rk3568") + }); + + if common_driver { + scope.board_phytiumpi = true; + scope.board_rk3568 = true; + } + if phytium { + scope.board_phytiumpi = true; + } + if rockchip { + scope.board_rk3568 = true; + } + } + + // ── Rule 9: CI workflow / config file changes ── + for file in changed_files { + if file.starts_with(".github/workflows/") { + if file.contains("qemu") { + scope.qemu_aarch64 = true; + scope.qemu_x86_64 = true; + } + if file.contains("board") || file.contains("uboot") { + scope.board_phytiumpi = true; + scope.board_rk3568 = true; + } + } + } + + // ── Rule 10: board / VM config file changes ── + for file in changed_files { + if file.starts_with("configs/board/") { + if file.contains("qemu-aarch64") { + scope.qemu_aarch64 = true; + } + if file.contains("qemu-x86_64") { + scope.qemu_x86_64 = true; + } + if file.contains("phytiumpi") { + scope.board_phytiumpi = true; + } + if file.contains("roc-rk3568") { + scope.board_rk3568 = true; + } + } + if file.starts_with("configs/vms/") { + if file.contains("aarch64") { + scope.qemu_aarch64 = true; + if file.contains("e2000") { + scope.board_phytiumpi = true; + } + if file.contains("rk3568") { + scope.board_rk3568 = true; + } + } + if file.contains("x86_64") { + scope.qemu_x86_64 = true; + } + } + } + + // If nothing was enabled after all rules, treat as "skip all". + if !scope.any_enabled() { + scope.skip_all = true; + } + + scope +} + +fn sorted_vec(set: &HashSet) -> Vec { + let mut v: Vec<_> = set.iter().cloned().collect(); + v.sort(); + v +} diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 7aee9ab4..b9a3a86e 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -22,6 +22,7 @@ use clap::{Args, Parser, Subcommand}; use std::fs; use std::path::{Path, PathBuf}; +mod affected; mod cargo; mod clippy; mod ctx; @@ -62,6 +63,8 @@ enum Commands { Image(image::ImageArgs), /// Manage local devspace dependencies Devspace(DevspaceArgs), + /// Analyze which test targets are affected by recent changes + Affected(AffectedArgs), } #[derive(Parser)] @@ -149,6 +152,13 @@ enum DevspaceCommand { Stop, } +#[derive(Parser)] +struct AffectedArgs { + /// Git ref to diff against (e.g. origin/main, HEAD~1, a commit SHA) + #[arg(long, default_value = "origin/main")] + base: String, +} + #[tokio::main] async fn main() -> Result<()> { let cli = Cli::parse(); @@ -193,6 +203,9 @@ async fn main() -> Result<()> { DevspaceCommand::Start => devspace::start()?, DevspaceCommand::Stop => devspace::stop()?, }, + Commands::Affected(args) => { + affected::run(&args.base)?; + } } Ok(()) From f6ef6f66aca2f957472ce516b084835c9b1214ea Mon Sep 17 00:00:00 2001 From: yoinspiration Date: Wed, 18 Feb 2026 20:15:53 +0800 Subject: [PATCH 02/11] fix: use to_string() for cargo_metadata PackageName type cargo_metadata 0.23 returns PackageName instead of String for package names. Convert with to_string() to match HashMap key types. Co-authored-by: Cursor --- xtask/src/affected.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/xtask/src/affected.rs b/xtask/src/affected.rs index 292f9468..e27064bc 100644 --- a/xtask/src/affected.rs +++ b/xtask/src/affected.rs @@ -194,8 +194,8 @@ fn build_workspace_graph() -> Result<(CrateMap, ReverseDeps)> { .to_string(); // Ensure the directory path ends with '/' for prefix matching. let dir = if dir.is_empty() { String::new() } else { format!("{dir}/") }; - crate_map.insert(pkg.name.clone(), dir); - id_to_name.insert(pkg.id.clone(), pkg.name.clone()); + crate_map.insert(pkg.name.to_string(), dir); + id_to_name.insert(pkg.id.clone(), pkg.name.to_string()); } } @@ -206,9 +206,9 @@ fn build_workspace_graph() -> Result<(CrateMap, ReverseDeps)> { for dep in &node.deps { if let Some(dep_name) = id_to_name.get(&dep.pkg) { reverse_deps - .entry(dep_name.clone()) + .entry(dep_name.to_string()) .or_default() - .insert(node_name.clone()); + .insert(node_name.to_string()); } } } From 7f7f88bf927397eb82b91545b63c0cbce5410719 Mon Sep 17 00:00:00 2001 From: yoinspiration Date: Wed, 18 Feb 2026 20:18:14 +0800 Subject: [PATCH 03/11] fix: refine core module rule to check direct changes only The previous logic treated axruntime/axconfig as "all tests needed" whenever they appeared in the affected set. This caused false positives for target-specific deps (e.g. axplat-x86-qemu-q35 is a cfg(x86_64) dep of axruntime). Now only trigger all tests when these core modules are directly modified. Co-authored-by: Cursor --- xtask/src/affected.rs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/xtask/src/affected.rs b/xtask/src/affected.rs index e27064bc..27699744 100644 --- a/xtask/src/affected.rs +++ b/xtask/src/affected.rs @@ -118,7 +118,7 @@ fn analyze(base_ref: &str) -> Result { eprintln!("[affected] all affected crates: {:?}", affected_crates); // Phase 3: map to test targets. - let mut scope = determine_targets(&changed_files, &affected_crates); + let mut scope = determine_targets(&changed_files, &changed_crates, &affected_crates); scope.changed_crates = sorted_vec(&changed_crates); scope.affected_crates = sorted_vec(&affected_crates); @@ -262,7 +262,11 @@ fn find_all_affected(changed: &HashSet, reverse_deps: &ReverseDeps) -> H // Phase 3: map affected crates + changed files → test targets // --------------------------------------------------------------------------- -fn determine_targets(changed_files: &[String], affected_crates: &HashSet) -> TestScope { +fn determine_targets( + changed_files: &[String], + changed_crates: &HashSet, + affected_crates: &HashSet, +) -> TestScope { let mut scope = TestScope::default(); // ── Rule 1: root build config changes → run everything ── @@ -273,15 +277,18 @@ fn determine_targets(changed_files: &[String], affected_crates: &HashSet } // ── Rule 2: build-tool (xtask) changes → run everything ── - if affected_crates.contains("xtask") { + if changed_crates.contains("xtask") { return TestScope::all(); } - // ── Rule 3: core module changes → run everything ── - // axruntime and axconfig are foundational; a change propagates to all targets. + // ── Rule 3: core module *directly* changed → run everything ── + // axruntime and axconfig are foundational. Only trigger all tests when their + // source code is directly modified, not when they are transitively affected + // by a platform-specific crate (e.g. axplat-x86-qemu-q35 is a target-cfg dep + // of axruntime, but a change there should only require x86 testing). if ["axruntime", "axconfig"] .iter() - .any(|c| affected_crates.contains(*c)) + .any(|c| changed_crates.contains(*c)) { return TestScope::all(); } From 5717a2ba7f6493d42ecb0bdde7b9429e6413ba58 Mon Sep 17 00:00:00 2001 From: yoinspiration Date: Wed, 18 Feb 2026 20:20:58 +0800 Subject: [PATCH 04/11] docs: add design document for dependency-aware testing Co-authored-by: Cursor --- doc/dependency-aware-testing.md | 227 ++++++++++++++++++++++++++++++++ 1 file changed, 227 insertions(+) create mode 100644 doc/dependency-aware-testing.md diff --git a/doc/dependency-aware-testing.md b/doc/dependency-aware-testing.md new file mode 100644 index 00000000..3dc5b015 --- /dev/null +++ b/doc/dependency-aware-testing.md @@ -0,0 +1,227 @@ +# 依赖感知的测试目标选择 + +## 背景与动机 + +AxVisor 是一个运行在多种硬件平台上的 Hypervisor,其集成测试需要在 QEMU 模拟器和真实开发板上执行。在此之前,每次代码提交(push/PR)都会触发**全部**测试配置(QEMU aarch64、QEMU x86_64、飞腾派、RK3568),即使只修改了一行文档或某个板级驱动也是如此。 + +这带来了两个问题: + +1. **硬件资源浪费**:自托管 Runner 连接的开发板是稀缺资源,不必要的测试会阻塞其他任务。 +2. **反馈延迟**:全量测试耗时长,开发者等待时间增加。 + +与此同时,AxVisor 采用 Cargo workspace 组织多个 crate,crate 之间存在依赖关系。当一个底层模块(如 `axruntime`)被修改时,所有依赖它的上层模块都应该被重新测试——这就是**依赖感知测试**的核心需求。 + +## 设计概述 + +### 三阶段分析流程 + +``` +┌─────────────────────────────────────────────────────────┐ +│ 阶段 1:变更检测 │ +│ git diff --name-only │ +│ → 获取变更文件列表 │ +│ → 过滤非代码文件(文档、图片等) │ +└─────────────────────┬───────────────────────────────────┘ + ▼ +┌─────────────────────────────────────────────────────────┐ +│ 阶段 2:依赖传播 │ +│ cargo metadata → 构建 workspace 反向依赖图 │ +│ BFS 遍历 → 找出所有间接受影响的 crate │ +└─────────────────────┬───────────────────────────────────┘ + ▼ +┌─────────────────────────────────────────────────────────┐ +│ 阶段 3:目标映射 │ +│ 10 条规则将受影响的 crate + 变更文件 │ +│ 映射到具体的测试目标(QEMU/开发板) │ +└─────────────────────────────────────────────────────────┘ +``` + +### Workspace 内部依赖图 + +通过 `cargo metadata` 自动提取的 workspace 内部反向依赖关系: + +``` +axconfig ← axruntime, axvisor +axruntime ← axvisor +axfs ← (axruntime 间接依赖) +driver ← axvisor +axplat-x86-qemu-q35 ← axruntime (仅 x86_64 目标) +``` + +当某个 crate 被修改时,沿着反向依赖链向上传播。例如: + +- 修改 `axconfig` → `axruntime` 受影响 → `axvisor` 受影响 +- 修改 `driver` → `axvisor` 受影响 +- 修改 `axplat-x86-qemu-q35` → `axruntime` 受影响(但这是条件编译依赖,仅 x86_64) + +### 测试目标 + +| 目标 ID | 说明 | Runner 标签 | +|---------|------|-------------| +| `qemu_aarch64` | QEMU AArch64 模拟测试 | `[self-hosted, linux, intel]` | +| `qemu_x86_64` | QEMU x86_64 模拟测试 | `[self-hosted, linux, intel]` | +| `board_phytiumpi` | 飞腾派开发板测试 | `[self-hosted, linux, phytiumpi]` | +| `board_rk3568` | ROC-RK3568-PC 开发板测试 | `[self-hosted, linux, roc-rk3568-pc]` | + +## 映射规则 + +分析引擎按以下 10 条规则(优先级从高到低)将变更映射到测试目标: + +### 全量触发规则(返回所有目标) + +| 规则 | 触发条件 | 理由 | +|------|----------|------| +| Rule 1 | 根构建配置变更:`Cargo.toml`、`Cargo.lock`、`rust-toolchain.toml` | 依赖或工具链变更影响所有构建 | +| Rule 2 | `xtask/` 源码被**直接修改** | 构建工具变更可能影响所有构建流程 | +| Rule 3 | `axruntime` 或 `axconfig` 被**直接修改** | 核心基础模块,所有平台都依赖 | +| Rule 4 | `kernel/` 下非架构特定的代码变更(不在 `kernel/src/hal/arch/` 下) | VMM、Shell、调度等通用逻辑 | + +### 精确触发规则 + +| 规则 | 触发条件 | 触发目标 | +|------|----------|----------| +| Rule 5 | `kernel/src/hal/arch/aarch64/` 变更 | `qemu_aarch64` + `board_phytiumpi` + `board_rk3568` | +| Rule 5 | `kernel/src/hal/arch/x86_64/` 变更 | `qemu_x86_64` | +| Rule 6 | `axplat-x86-qemu-q35` crate 受影响 | `qemu_x86_64` | +| Rule 7 | `axfs` crate 受影响 | `qemu_aarch64` + `board_phytiumpi` + `board_rk3568` | +| Rule 8 | `driver` crate 受影响 — 飞腾派相关文件 | `board_phytiumpi` | +| Rule 8 | `driver` crate 受影响 — Rockchip 相关文件 | `board_rk3568` | +| Rule 8 | `driver` crate 受影响 — 通用驱动文件 | `board_phytiumpi` + `board_rk3568` | +| Rule 9 | `.github/workflows/` 下 QEMU 相关配置 | `qemu_aarch64` + `qemu_x86_64` | +| Rule 9 | `.github/workflows/` 下 Board/UBoot 相关配置 | `board_phytiumpi` + `board_rk3568` | +| Rule 10 | `configs/board/` 或 `configs/vms/` 下的配置文件 | 对应的特定目标 | + +### 跳过规则 + +以下文件变更不触发任何测试(`skip_all=true`): + +- `doc/` 目录下的文件 +- `*.md`、`*.txt`、`*.png`、`*.jpg`、`*.svg` 等 +- `LICENSE`、`.gitignore`、`.gitattributes` + +### 关于"直接修改"与"间接受影响"的区分 + +Rule 2 和 Rule 3 特意使用"直接修改的 crate"(`changed_crates`)而非"所有受影响的 crate"(`affected_crates`)进行判断。这是因为 `cargo metadata` 的依赖解析不区分条件编译依赖(`[target.'cfg(...)'.dependencies]`)。例如 `axruntime` 对 `axplat-x86-qemu-q35` 的依赖仅在 x86_64 目标下生效,但 `cargo metadata` 会无条件地将其包含在依赖图中。如果不区分,修改 x86 平台 crate 就会通过 `axruntime` 间接触发全量测试。 + +## 文件变更清单 + +| 文件 | 变更类型 | 说明 | +|------|----------|------| +| `xtask/src/affected.rs` | 新增 | 核心分析引擎(约 400 行) | +| `xtask/src/main.rs` | 修改 | 注册 `Affected` 子命令 | +| `.github/workflows/test-qemu.yml` | 修改 | 添加 `detect` job,动态构建测试矩阵 | +| `.github/workflows/test-board.yml` | 修改 | 添加 `detect` job,动态构建测试矩阵 | + +## CI 工作流变更 + +### 改动前 + +``` +push/PR → test-qemu job (固定 3 个矩阵项) → 全部在 self-hosted Runner 上执行 +push/PR → test-board job (固定 4 个矩阵项) → 全部在 self-hosted Runner 上执行 +``` + +### 改动后 + +``` +push/PR → detect job (ubuntu-latest, 轻量级) + │ + ├─ 分析影响范围 + ├─ 动态构建测试矩阵(仅包含受影响的目标) + │ + └──→ test job (self-hosted Runner) + 仅运行矩阵中的配置项 + 如果矩阵为空则整个 job 被跳过 +``` + +`detect` job 运行在 GitHub 提供的标准 `ubuntu-latest` Runner 上,不占用稀缺的硬件 Runner 资源。通过 `actions/cache` 缓存 xtask 的编译产物,后续运行接近零开销。 + +## 使用方法 + +### 本地使用 + +```bash +# 对比 main 分支,查看需要运行哪些测试 +cargo xtask affected --base origin/main + +# 对比上一个 commit +cargo xtask affected --base HEAD~1 + +# 对比某个特定 commit +cargo xtask affected --base abc1234 +``` + +输出示例: + +```json +{ + "skip_all": false, + "qemu_aarch64": true, + "qemu_x86_64": false, + "board_phytiumpi": false, + "board_rk3568": false, + "changed_crates": [ + "axvisor" + ], + "affected_crates": [ + "axvisor" + ] +} +``` + +同时 `stderr` 会输出详细的分析过程,便于调试: + +``` +[affected] changed files (1): + kernel/src/hal/arch/aarch64/api.rs +[affected] workspace crates: ["axvisor", "nop", "axconfig", ...] +[affected] reverse deps: + axconfig ← {"axruntime", "axvisor"} + axruntime ← {"axvisor"} + driver ← {"axvisor"} +[affected] directly changed crates: {"axvisor"} +[affected] all affected crates: {"axvisor"} +[affected] test scope: qemu_aarch64=true qemu_x86_64=false board_phytiumpi=false board_rk3568=false +``` + +### CI 中自动执行 + +无需手动操作。当 push 或创建 PR 时,CI 工作流会自动: + +1. 运行 `detect` job 分析影响范围 +2. 将分析结果写入 `$GITHUB_OUTPUT` +3. 根据结果动态构建测试矩阵 +4. 仅在受影响的硬件 Runner 上执行测试 + +## 验证结果 + +以下场景已在本地通过验证: + +| 场景 | 变更文件 | 结果 | +|------|----------|------| +| 只改文档 | `doc/shell.md` | `skip_all=true`,跳过所有测试 | +| 改 aarch64 HAL | `kernel/src/hal/arch/aarch64/api.rs` | QEMU aarch64 + 两块 ARM 开发板 | +| 改飞腾派驱动 | `modules/driver/src/blk/phytium.rs` | 仅飞腾派开发板 | +| 改 x86 平台 crate | `platform/x86-qemu-q35/src/lib.rs` | 仅 QEMU x86_64 | +| 改 axruntime | `modules/axruntime/src/lib.rs` | 全部测试(核心模块) | +| 改 kernel 通用代码 | `kernel/src/main.rs` | 全部测试 | +| 改 Rockchip 驱动 | `modules/driver/src/soc/rockchip/pm.rs` | 仅 RK3568 开发板 | + +## 扩展指南 + +### 添加新的开发板 + +当添加新的开发板支持时,需要: + +1. 在 `xtask/src/affected.rs` 的 `TestScope` 结构体中添加新的布尔字段 +2. 在 `determine_targets()` 中添加对应的规则 +3. 在 `run()` 中将新字段写入 `$GITHUB_OUTPUT` +4. 在 CI 工作流的 `Build board test matrix` 步骤中添加对应的矩阵项 + +### 添加新的 workspace crate + +无需额外操作。`cargo metadata` 会自动发现新的 workspace 成员及其依赖关系。如果新 crate 是平台特定的,需要在 `determine_targets()` 中添加对应的映射规则。 + +### 修改规则 + +所有映射规则集中在 `xtask/src/affected.rs` 的 `determine_targets()` 函数中,便于统一维护。 From b4721d5df4a63c743299683daa3646a873629c42 Mon Sep 17 00:00:00 2001 From: yoinspiration Date: Wed, 25 Feb 2026 01:34:40 +0800 Subject: [PATCH 05/11] ci: install libudev dependencies for detect jobs Install pkg-config and libudev-dev in workflow detect jobs so cargo xtask affected can build libudev-sys on ubuntu runners. Co-authored-by: Cursor --- .github/workflows/test-board.yml | 5 +++++ .github/workflows/test-qemu.yml | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/.github/workflows/test-board.yml b/.github/workflows/test-board.yml index 6258c041..70f3379b 100644 --- a/.github/workflows/test-board.yml +++ b/.github/workflows/test-board.yml @@ -20,6 +20,11 @@ jobs: - name: Setup Rust toolchain uses: dtolnay/rust-toolchain@stable + - name: Install system deps + run: | + sudo apt-get update + sudo apt-get install -y pkg-config libudev-dev + - name: Cache cargo build uses: actions/cache@v4 with: diff --git a/.github/workflows/test-qemu.yml b/.github/workflows/test-qemu.yml index 3f2650af..86589478 100644 --- a/.github/workflows/test-qemu.yml +++ b/.github/workflows/test-qemu.yml @@ -20,6 +20,11 @@ jobs: - name: Setup Rust toolchain uses: dtolnay/rust-toolchain@stable + - name: Install system deps + run: | + sudo apt-get update + sudo apt-get install -y pkg-config libudev-dev + - name: Cache cargo build uses: actions/cache@v4 with: From cdd49913a3741d47831c5e4ddd600a50353a2bf1 Mon Sep 17 00:00:00 2001 From: yoinspiration Date: Thu, 26 Feb 2026 22:04:54 +0800 Subject: [PATCH 06/11] xtask: factor out test target helpers Refactor affected test target selection by introducing internal TargetId and helper methods on TestScope. This centralizes how concrete CI targets are enabled so that adding new platforms/architectures touches fewer call sites while keeping the existing rules and behavior unchanged. Made-with: Cursor --- xtask/src/affected.rs | 87 +++++++++++++++++++++++++++++++------------ 1 file changed, 63 insertions(+), 24 deletions(-) diff --git a/xtask/src/affected.rs b/xtask/src/affected.rs index 27699744..eeec87d4 100644 --- a/xtask/src/affected.rs +++ b/xtask/src/affected.rs @@ -44,6 +44,15 @@ pub struct TestScope { pub affected_crates: Vec, } +/// Internal identifiers for concrete test targets. +#[derive(Copy, Clone)] +enum TargetId { + QemuAarch64, + QemuX86_64, + BoardPhytiumpi, + BoardRk3568, +} + impl TestScope { fn all() -> Self { Self { @@ -55,10 +64,44 @@ impl TestScope { } } + fn enable(&mut self, id: TargetId) { + match id { + TargetId::QemuAarch64 => self.qemu_aarch64 = true, + TargetId::QemuX86_64 => self.qemu_x86_64 = true, + TargetId::BoardPhytiumpi => self.board_phytiumpi = true, + TargetId::BoardRk3568 => self.board_rk3568 = true, + } + } + + fn enable_qemu_aarch64(&mut self) { + self.enable(TargetId::QemuAarch64); + } + + fn enable_qemu_x86_64(&mut self) { + self.enable(TargetId::QemuX86_64); + } + + fn enable_board_phytiumpi(&mut self) { + self.enable(TargetId::BoardPhytiumpi); + } + + fn enable_board_rk3568(&mut self) { + self.enable(TargetId::BoardRk3568); + } + + fn enable_all_qemu(&mut self) { + self.enable_qemu_aarch64(); + self.enable_qemu_x86_64(); + } + + fn enable_all_boards(&mut self) { + self.enable_board_phytiumpi(); + self.enable_board_rk3568(); + } + fn enable_all_aarch64(&mut self) { - self.qemu_aarch64 = true; - self.board_phytiumpi = true; - self.board_rk3568 = true; + self.enable_qemu_aarch64(); + self.enable_all_boards(); } fn any_enabled(&self) -> bool { @@ -306,20 +349,19 @@ fn determine_targets( scope.enable_all_aarch64(); } if file.starts_with("kernel/src/hal/arch/x86_64/") { - scope.qemu_x86_64 = true; + scope.enable_qemu_x86_64(); } } // ── Rule 6: platform crate ── if affected_crates.contains("axplat-x86-qemu-q35") { - scope.qemu_x86_64 = true; + scope.enable_qemu_x86_64(); } // ── Rule 7: filesystem module → targets with `fs` feature ── if affected_crates.contains("axfs") { - scope.qemu_aarch64 = true; // linux guest uses rootfs - scope.board_phytiumpi = true; - scope.board_rk3568 = true; + scope.enable_qemu_aarch64(); // linux guest uses rootfs + scope.enable_all_boards(); } // ── Rule 8: driver module → board-specific analysis ── @@ -336,14 +378,13 @@ fn determine_targets( }); if common_driver { - scope.board_phytiumpi = true; - scope.board_rk3568 = true; + scope.enable_all_boards(); } if phytium { - scope.board_phytiumpi = true; + scope.enable_board_phytiumpi(); } if rockchip { - scope.board_rk3568 = true; + scope.enable_board_rk3568(); } } @@ -351,12 +392,10 @@ fn determine_targets( for file in changed_files { if file.starts_with(".github/workflows/") { if file.contains("qemu") { - scope.qemu_aarch64 = true; - scope.qemu_x86_64 = true; + scope.enable_all_qemu(); } if file.contains("board") || file.contains("uboot") { - scope.board_phytiumpi = true; - scope.board_rk3568 = true; + scope.enable_all_boards(); } } } @@ -365,30 +404,30 @@ fn determine_targets( for file in changed_files { if file.starts_with("configs/board/") { if file.contains("qemu-aarch64") { - scope.qemu_aarch64 = true; + scope.enable_qemu_aarch64(); } if file.contains("qemu-x86_64") { - scope.qemu_x86_64 = true; + scope.enable_qemu_x86_64(); } if file.contains("phytiumpi") { - scope.board_phytiumpi = true; + scope.enable_board_phytiumpi(); } if file.contains("roc-rk3568") { - scope.board_rk3568 = true; + scope.enable_board_rk3568(); } } if file.starts_with("configs/vms/") { if file.contains("aarch64") { - scope.qemu_aarch64 = true; + scope.enable_qemu_aarch64(); if file.contains("e2000") { - scope.board_phytiumpi = true; + scope.enable_board_phytiumpi(); } if file.contains("rk3568") { - scope.board_rk3568 = true; + scope.enable_board_rk3568(); } } if file.contains("x86_64") { - scope.qemu_x86_64 = true; + scope.enable_qemu_x86_64(); } } } From e6126d023088114d0a3f2ebe07abc2424203eb07 Mon Sep 17 00:00:00 2001 From: yoinspiration Date: Thu, 26 Feb 2026 22:15:16 +0800 Subject: [PATCH 07/11] xtask: introduce target metadata table Extend the internal test target abstraction with Arch/TargetKind metadata and a static TARGETS table, plus a generic enable_where helper. This keeps architecture- and kind-based rules declarative while preserving existing external behavior. Made-with: Cursor --- xtask/src/affected.rs | 62 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 6 deletions(-) diff --git a/xtask/src/affected.rs b/xtask/src/affected.rs index eeec87d4..8fd203af 100644 --- a/xtask/src/affected.rs +++ b/xtask/src/affected.rs @@ -53,6 +53,50 @@ enum TargetId { BoardRk3568, } +/// High-level architecture categories of test targets. +#[derive(Copy, Clone, Eq, PartialEq)] +enum Arch { + AArch64, + X86_64, +} + +/// High-level kind of test target. +#[derive(Copy, Clone, Eq, PartialEq)] +enum TargetKind { + Qemu, + Board, +} + +/// Declarative description of each concrete test target. +struct TargetMeta { + id: TargetId, + arch: Arch, + kind: TargetKind, +} + +const TARGETS: &[TargetMeta] = &[ + TargetMeta { + id: TargetId::QemuAarch64, + arch: Arch::AArch64, + kind: TargetKind::Qemu, + }, + TargetMeta { + id: TargetId::QemuX86_64, + arch: Arch::X86_64, + kind: TargetKind::Qemu, + }, + TargetMeta { + id: TargetId::BoardPhytiumpi, + arch: Arch::AArch64, + kind: TargetKind::Board, + }, + TargetMeta { + id: TargetId::BoardRk3568, + arch: Arch::AArch64, + kind: TargetKind::Board, + }, +]; + impl TestScope { fn all() -> Self { Self { @@ -89,19 +133,25 @@ impl TestScope { self.enable(TargetId::BoardRk3568); } + fn enable_where(&mut self, mut pred: F) + where + F: FnMut(&TargetMeta) -> bool, + { + for meta in TARGETS.iter().filter(|m| pred(m)) { + self.enable(meta.id); + } + } + fn enable_all_qemu(&mut self) { - self.enable_qemu_aarch64(); - self.enable_qemu_x86_64(); + self.enable_where(|m| matches!(m.kind, TargetKind::Qemu)); } fn enable_all_boards(&mut self) { - self.enable_board_phytiumpi(); - self.enable_board_rk3568(); + self.enable_where(|m| matches!(m.kind, TargetKind::Board)); } fn enable_all_aarch64(&mut self) { - self.enable_qemu_aarch64(); - self.enable_all_boards(); + self.enable_where(|m| m.arch == Arch::AArch64); } fn any_enabled(&self) -> bool { From 430f3ef49b8f1a175db36351c75dfe02ada983f9 Mon Sep 17 00:00:00 2001 From: yoinspiration Date: Sun, 1 Mar 2026 13:48:24 +0800 Subject: [PATCH 08/11] ci: trigger pipeline run Made-with: Cursor From 0b6ff1833a07ecb9de1aeeffb55770e98db097b9 Mon Sep 17 00:00:00 2001 From: yoinspiration Date: Thu, 5 Mar 2026 14:06:38 +0800 Subject: [PATCH 09/11] ci(uboot): update Phytium Pi success regex Align uboot workflow success detection with upstream Phytium Pi boot banner. Made-with: Cursor --- .github/workflows/uboot.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/uboot.toml b/.github/workflows/uboot.toml index e82eacaf..ef36d350 100644 --- a/.github/workflows/uboot.toml +++ b/.github/workflows/uboot.toml @@ -11,8 +11,7 @@ success_regex = [ "All tests passed!", "Hello World!", "root@firefly:~#", - "root@phytium-Ubuntu:~#", - "Welcome to Phytium Buildroot", + "^Phytium Pi", "Last login: *", ] From 3b6b5fa260b9ed6e729fc3fbcb8cc0ecb3bb92bf Mon Sep 17 00:00:00 2001 From: yoinspiration Date: Thu, 5 Mar 2026 14:08:25 +0800 Subject: [PATCH 10/11] chore(gitignore): ignore idea config Ignore local JetBrains .idea directory to avoid committing IDE-specific settings. Made-with: Cursor --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 52916d4b..21927610 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Build output and other log/bin files from arceos /target /.vscode +/.idea /tmp /.arceos /.images From e8e83ea8217ba187efd48866f632f8039b114c95 Mon Sep 17 00:00:00 2001 From: yoinspiration Date: Fri, 6 Mar 2026 00:56:09 +0800 Subject: [PATCH 11/11] ci: harden checkout against transient network failures Configure git HTTP settings before checkout and add checkout step timeouts to reduce flaky TLS/connectivity fetch failures in CI. Made-with: Cursor --- .github/workflows/test-board.yml | 16 ++++++++++++++++ .github/workflows/test-qemu.yml | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/.github/workflows/test-board.yml b/.github/workflows/test-board.yml index 70f3379b..7d7a7de4 100644 --- a/.github/workflows/test-board.yml +++ b/.github/workflows/test-board.yml @@ -12,8 +12,16 @@ jobs: board_rk3568: ${{ steps.analyze.outputs.board_rk3568 }} board_matrix: ${{ steps.matrix.outputs.board_matrix }} steps: + - name: Harden git network settings + run: | + git config --global http.version HTTP/1.1 + git config --global http.lowSpeedLimit 1000 + git config --global http.lowSpeedTime 60 + git config --global http.postBuffer 104857600 + - name: Checkout uses: actions/checkout@v4 + timeout-minutes: 10 with: fetch-depth: 0 @@ -92,8 +100,16 @@ jobs: [ -d .git/modules ] && rm -rf .git/modules || true [ -d .git ] && git clean -ffdx || true + - name: Harden git network settings + run: | + git config --global http.version HTTP/1.1 + git config --global http.lowSpeedLimit 1000 + git config --global http.lowSpeedTime 60 + git config --global http.postBuffer 104857600 + - name: Checkout uses: actions/checkout@v4 + timeout-minutes: 10 with: clean: true submodules: recursive diff --git a/.github/workflows/test-qemu.yml b/.github/workflows/test-qemu.yml index 86589478..24c6b39c 100644 --- a/.github/workflows/test-qemu.yml +++ b/.github/workflows/test-qemu.yml @@ -12,8 +12,16 @@ jobs: qemu_x86_64: ${{ steps.analyze.outputs.qemu_x86_64 }} qemu_matrix: ${{ steps.matrix.outputs.qemu_matrix }} steps: + - name: Harden git network settings + run: | + git config --global http.version HTTP/1.1 + git config --global http.lowSpeedLimit 1000 + git config --global http.lowSpeedTime 60 + git config --global http.postBuffer 104857600 + - name: Checkout uses: actions/checkout@v4 + timeout-minutes: 10 with: fetch-depth: 0 @@ -91,8 +99,16 @@ jobs: [ -d .git/modules ] && rm -rf .git/modules || true [ -d .git ] && git clean -ffdx || true + - name: Harden git network settings + run: | + git config --global http.version HTTP/1.1 + git config --global http.lowSpeedLimit 1000 + git config --global http.lowSpeedTime 60 + git config --global http.postBuffer 104857600 + - name: Checkout uses: actions/checkout@v4 + timeout-minutes: 10 with: clean: true submodules: recursive