From aa440f7160625d84b371aa833600ba1fbf39cd63 Mon Sep 17 00:00:00 2001 From: wangyining Date: Fri, 29 May 2026 17:02:06 +0800 Subject: [PATCH 1/7] stages produced by restoreSource() will inherit CI-only Cargo mirror configuration --- Jenkinsfile | 42 ++++++-------- scripts/ci/prefetch_cargo_deps.sh | 96 +++++++++++++++++++++++++++++++ scripts/ci/setup_cargo_mirror.sh | 41 +++++++++++++ 3 files changed, 155 insertions(+), 24 deletions(-) create mode 100755 scripts/ci/prefetch_cargo_deps.sh create mode 100755 scripts/ci/setup_cargo_mirror.sh diff --git a/Jenkinsfile b/Jenkinsfile index 32f30579..4715c6cb 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -269,24 +269,7 @@ def prefetchCargoDeps() { sh '''#!/bin/bash set -euo pipefail ''' + stageLogTeeLine('Prefetch Dependencies') + ''' -echo "==> Prefetching cargo dependencies for all platforms..." - -declare -A ARCH_TARGET=( - [aarch64]=aarch64-unknown-none-softfloat - [x86_64]=x86_64-unknown-none - [riscv64]=riscv64gc-unknown-none-elf -) -for platform in x86_64-qemu-virt aarch64-qemu-virt aarch64-crosvm-virt; do - arch="${platform%%-*}" - target="${ARCH_TARGET[$arch]}" - cp "platforms/${platform}/defconfig" .config - cargo fetch --manifest-path entry/Cargo.toml --target "$target" || true -done - -cargo fetch --manifest-path tee_apps/sh/Cargo.toml || true -cargo fetch --manifest-path xtask/crate_rootfs/Cargo.toml || true - -echo "==> Dependency prefetch complete" +scripts/ci/prefetch_cargo_deps.sh ''' } finally { fixWorkspaceOwnership(stageWorkspace) @@ -410,6 +393,14 @@ cargo +"${AUX_RUST_TOOLCHAIN}" fmt --all --check } } +def cargoOfflineEnv() { + return [ + 'CARGO_NET_OFFLINE=true', + 'CARGO_HTTP_TIMEOUT=120', + 'CARGO_NET_RETRY=3', + ] +} + def runClippyAndBuild(String platform) { def stageName = "Clippy+Build: ${platform}" initStageLog(stageName) @@ -420,7 +411,7 @@ def runClippyAndBuild(String platform) { deleteDir() restoreSource() - withEnv(["TARGET_DIR=${buildTargetDir}"]) { + withEnv(["TARGET_DIR=${buildTargetDir}"] + cargoOfflineEnv()) { sh """#!/bin/bash set -euo pipefail ${stageLogTeeLine(stageName)} @@ -448,22 +439,24 @@ def runClippyAndRuntime(String arch) { restoreSource() withEnv(["TARGET_DIR=${runtimeTargetDir}", "STAGE_LOG=${stageLog}"]) { - sh """#!/bin/bash + withEnv(cargoOfflineEnv()) { + sh """#!/bin/bash set -euo pipefail ${stageLogTeeLine(stageName)} cp platforms/${platform}/defconfig .config make clippy """ - runUnitTests(arch) - generateCoverageHtml(arch) - copyCoverageToWorkspace(arch) + runUnitTests(arch) + generateCoverageHtml(arch) + copyCoverageToWorkspace(arch) - sh """#!/bin/bash + sh """#!/bin/bash set -euo pipefail ${stageLogTeeLine(stageName)} cp platforms/${platform}/defconfig .config stdbuf -oL -eL make build """ + } dir('test-harness') { git branch: "${env.TEST_HARNESS_BRANCH}", @@ -644,6 +637,7 @@ def prepareSource() { deleteDir() checkoutProject() markSafeDirectory() + sh './scripts/ci/setup_cargo_mirror.sh' env.GIT_COMMIT = sh(script: 'git rev-parse HEAD', returnStdout: true).trim() echo "Checked out HEAD: ${env.GIT_COMMIT}" if (env.giteePullRequestIid?.trim()) { diff --git a/scripts/ci/prefetch_cargo_deps.sh b/scripts/ci/prefetch_cargo_deps.sh new file mode 100755 index 00000000..5c6ccedc --- /dev/null +++ b/scripts/ci/prefetch_cargo_deps.sh @@ -0,0 +1,96 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "${BASH_SOURCE[0]}")/../.." + +retry() { + local attempts="$1" + shift + local i + for i in $(seq 1 "${attempts}"); do + "$@" && return 0 + if [[ "${i}" == "${attempts}" ]]; then + return 1 + fi + echo "Command failed, retrying (${i}/${attempts}): $*" >&2 + sleep 5 + done +} + +fetch() { + local label="$1" + shift + echo "==> Fetching ${label}" + retry 3 cargo fetch "$@" +} + +fetch_offline() { + local label="$1" + shift + echo "==> Verifying offline cache for ${label}" + CARGO_NET_OFFLINE=true cargo fetch "$@" +} + +arch_for_platform() { + local platform="$1" + case "${platform}" in + x86_64-*) echo "x86_64" ;; + aarch64-*) echo "aarch64" ;; + riscv64-*) echo "riscv64" ;; + *) + echo "Unsupported platform: ${platform}" >&2 + return 1 + ;; + esac +} + +target_for_arch() { + local arch="$1" + case "${arch}" in + x86_64) echo "x86_64-unknown-none" ;; + aarch64) echo "aarch64-unknown-none-softfloat" ;; + riscv64) echo "riscv64gc-unknown-none-elf" ;; + *) + echo "Unsupported arch: ${arch}" >&2 + return 1 + ;; + esac +} + +echo "==> Cargo environment" +cargo --version +rustc --version +echo "CARGO_HOME=${CARGO_HOME:-${HOME}/.cargo}" +echo "CARGO_REGISTRIES_CRATES_IO_PROTOCOL=${CARGO_REGISTRIES_CRATES_IO_PROTOCOL:-}" +echo "CARGO_HTTP_TIMEOUT=${CARGO_HTTP_TIMEOUT:-}" +echo "CARGO_NET_RETRY=${CARGO_NET_RETRY:-}" + +platforms=( + x86_64-qemu-virt + aarch64-qemu-virt + aarch64-crosvm-virt + riscv64-qemu-virt +) + +for platform in "${platforms[@]}"; do + arch="$(arch_for_platform "${platform}")" + target="$(target_for_arch "${arch}")" + cp "platforms/${platform}/defconfig" .config + fetch "entry ${platform} (${target})" --manifest-path entry/Cargo.toml --target "${target}" --locked +done + +fetch "main workspace x86_64 UEFI target" --manifest-path Cargo.toml --target x86_64-unknown-uefi --locked +fetch "xtask workspace" --manifest-path xtask/Cargo.toml --locked +fetch "tee app shell" --manifest-path tee_apps/sh/Cargo.toml + +for platform in "${platforms[@]}"; do + arch="$(arch_for_platform "${platform}")" + target="$(target_for_arch "${arch}")" + cp "platforms/${platform}/defconfig" .config + fetch_offline "entry ${platform} (${target})" --manifest-path entry/Cargo.toml --target "${target}" --locked +done + +fetch_offline "main workspace x86_64 UEFI target" --manifest-path Cargo.toml --target x86_64-unknown-uefi --locked +fetch_offline "xtask workspace" --manifest-path xtask/Cargo.toml --locked + +echo "==> Cargo dependency prefetch complete" diff --git a/scripts/ci/setup_cargo_mirror.sh b/scripts/ci/setup_cargo_mirror.sh new file mode 100755 index 00000000..805147d6 --- /dev/null +++ b/scripts/ci/setup_cargo_mirror.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "${BASH_SOURCE[0]}")/../.." + +mirror_url="${XKERNEL_CARGO_REGISTRY_MIRROR:-sparse+https://rsproxy.cn/index/}" +mirror_name="${XKERNEL_CARGO_REGISTRY_MIRROR_NAME:-rsproxy}" +config_path=".cargo/.config.toml" + +case "${mirror_url}" in + none|off|disable|disabled) + rm -f "${config_path}" + echo "==> Cargo registry mirror disabled; using default crates.io registry" + exit 0 + ;; + sparse+*|git+*) ;; + http://*|https://*) mirror_url="sparse+${mirror_url}" ;; + *) + echo "Invalid XKERNEL_CARGO_REGISTRY_MIRROR: ${mirror_url}" >&2 + echo "Use sparse+https://..., git+https://..., https://..., or 'none'." >&2 + exit 1 + ;; +esac + +mkdir -p .cargo +cat > "${config_path}" < Cargo registry mirror configured: ${mirror_name} -> ${mirror_url}" -- Gitee From c96387697a3a612c152955012d560370c10f97f9 Mon Sep 17 00:00:00 2001 From: wangyining Date: Fri, 29 May 2026 17:15:03 +0800 Subject: [PATCH 2/7] add lock fot cargo --- Jenkinsfile | 2 +- scripts/ci/prefetch_cargo_deps.sh | 37 +++++++++++++++++++++++-------- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 4715c6cb..ee4f9714 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -269,7 +269,7 @@ def prefetchCargoDeps() { sh '''#!/bin/bash set -euo pipefail ''' + stageLogTeeLine('Prefetch Dependencies') + ''' -scripts/ci/prefetch_cargo_deps.sh +ROOT_WS="${ROOT_WS}" scripts/ci/prefetch_cargo_deps.sh ''' } finally { fixWorkspaceOwnership(stageWorkspace) diff --git a/scripts/ci/prefetch_cargo_deps.sh b/scripts/ci/prefetch_cargo_deps.sh index 5c6ccedc..89094ed8 100755 --- a/scripts/ci/prefetch_cargo_deps.sh +++ b/scripts/ci/prefetch_cargo_deps.sh @@ -24,11 +24,29 @@ fetch() { retry 3 cargo fetch "$@" } -fetch_offline() { +fetch_offline_locked() { local label="$1" shift - echo "==> Verifying offline cache for ${label}" - CARGO_NET_OFFLINE=true cargo fetch "$@" + echo "==> Verifying locked offline cache for ${label}" + CARGO_NET_OFFLINE=true cargo fetch "$@" --locked +} + +sync_lockfiles_to_source_cache() { + if [[ -z "${ROOT_WS:-}" || ! -d "${ROOT_WS}/source-cache" ]]; then + return 0 + fi + + if [[ -f Cargo.lock ]]; then + cp Cargo.lock "${ROOT_WS}/source-cache/Cargo.lock" + fi + if [[ -f xtask/Cargo.lock ]]; then + mkdir -p "${ROOT_WS}/source-cache/xtask" + cp xtask/Cargo.lock "${ROOT_WS}/source-cache/xtask/Cargo.lock" + fi + if [[ -f tee_apps/Cargo.lock ]]; then + mkdir -p "${ROOT_WS}/source-cache/tee_apps" + cp tee_apps/Cargo.lock "${ROOT_WS}/source-cache/tee_apps/Cargo.lock" + fi } arch_for_platform() { @@ -76,21 +94,22 @@ for platform in "${platforms[@]}"; do arch="$(arch_for_platform "${platform}")" target="$(target_for_arch "${arch}")" cp "platforms/${platform}/defconfig" .config - fetch "entry ${platform} (${target})" --manifest-path entry/Cargo.toml --target "${target}" --locked + fetch "entry ${platform} (${target})" --manifest-path entry/Cargo.toml --target "${target}" done -fetch "main workspace x86_64 UEFI target" --manifest-path Cargo.toml --target x86_64-unknown-uefi --locked -fetch "xtask workspace" --manifest-path xtask/Cargo.toml --locked +fetch "main workspace x86_64 UEFI target" --manifest-path Cargo.toml --target x86_64-unknown-uefi +fetch "xtask workspace" --manifest-path xtask/Cargo.toml fetch "tee app shell" --manifest-path tee_apps/sh/Cargo.toml for platform in "${platforms[@]}"; do arch="$(arch_for_platform "${platform}")" target="$(target_for_arch "${arch}")" cp "platforms/${platform}/defconfig" .config - fetch_offline "entry ${platform} (${target})" --manifest-path entry/Cargo.toml --target "${target}" --locked + fetch_offline_locked "entry ${platform} (${target})" --manifest-path entry/Cargo.toml --target "${target}" done -fetch_offline "main workspace x86_64 UEFI target" --manifest-path Cargo.toml --target x86_64-unknown-uefi --locked -fetch_offline "xtask workspace" --manifest-path xtask/Cargo.toml --locked +fetch_offline_locked "main workspace x86_64 UEFI target" --manifest-path Cargo.toml --target x86_64-unknown-uefi +fetch_offline_locked "xtask workspace" --manifest-path xtask/Cargo.toml +sync_lockfiles_to_source_cache echo "==> Cargo dependency prefetch complete" -- Gitee From 75463b0d0ccf1eb0c6e78876757b2ee9e9d1ab27 Mon Sep 17 00:00:00 2001 From: wangyining Date: Fri, 29 May 2026 17:50:33 +0800 Subject: [PATCH 3/7] add logic readable --- Jenkinsfile | 223 ++++++++++++------------------ scripts/ci/prefetch_cargo_deps.sh | 115 --------------- scripts/ci/setup_cargo_mirror.sh | 41 ------ 3 files changed, 86 insertions(+), 293 deletions(-) delete mode 100755 scripts/ci/prefetch_cargo_deps.sh delete mode 100755 scripts/ci/setup_cargo_mirror.sh diff --git a/Jenkinsfile b/Jenkinsfile index ee4f9714..f3f3d14e 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -7,7 +7,7 @@ pipeline { agent { docker { image 'yeanwang/x-kernel-builder:v1.5' - args '-v /var/run/docker.sock:/var/run/docker.sock -v /var/jenkins_home/cargo/registry:/usr/local/cargo/registry -v /var/jenkins_home/.rustup/toolchains:/usr/local/rustup/toolchains -v /var/jenkins_home/xkernel-target:/xkernel-target --privileged -u root:root' + args '-v /var/run/docker.sock:/var/run/docker.sock -v /var/jenkins_home/cargo/registry:/usr/local/cargo/registry -v /var/jenkins_home/cargo/config.toml:/usr/local/cargo/config.toml:ro -v /var/jenkins_home/.rustup/toolchains:/usr/local/rustup/toolchains -v /var/jenkins_home/xkernel-target:/xkernel-target --privileged -u root:root' } } @@ -34,162 +34,141 @@ pipeline { } stages { - stage('Prepare Source') { + stage('Source: Checkout & PR Base') { steps { script { env.ROOT_WS = env.WORKSPACE currentBuild.description = "PR#${env.giteePullRequestIid ?: 'manual'}" prepareSource() - // 先创建 6 个并行检查占位(较早创建 → Gitee 列表靠下);顺序 4 项在后续 start/finish + // 先创建 6 个并行检查占位(较早创建 → Gitee 列表靠下);顺序 3 项在后续 start/finish giteeStartParallelCheckRuns() - giteeStartCheckRun('Prepare Source') - ciResults['Prepare Source'] = [status: 'passed'] + giteeStartCheckRun('Source: Checkout & PR Base') + ciResults['Source: Checkout & PR Base'] = [status: 'passed'] } } post { success { - script { ciResults['Prepare Source'] = [status: 'passed'] } + script { ciResults['Source: Checkout & PR Base'] = [status: 'passed'] } } failure { - script { ciResults['Prepare Source'] = [status: 'failed', detail: '源码准备失败(可能是分支分叉需要 rebase)'] } + script { ciResults['Source: Checkout & PR Base'] = [status: 'failed', detail: '源码准备失败(可能是分支分叉需要 rebase)'] } } always { - script { ciFinishGiteeStage('Prepare Source', ciResults, '源码准备失败(可能是分支分叉需要 rebase)') } + script { ciFinishGiteeStage('Source: Checkout & PR Base', ciResults, '源码准备失败(可能是分支分叉需要 rebase)') } } } } - stage('Check Environment') { + stage('Setup: Toolchains & Targets') { steps { script { - giteeStartCheckRun('Check Environment') + giteeStartCheckRun('Setup: Toolchains & Targets') checkBuildEnvironment() - ciResults['Check Environment'] = [status: 'passed'] + ciResults['Setup: Toolchains & Targets'] = [status: 'passed'] } } post { success { - script { ciResults['Check Environment'] = [status: 'passed'] } + script { ciResults['Setup: Toolchains & Targets'] = [status: 'passed'] } } failure { - script { ciResults['Check Environment'] = [status: 'failed', detail: 'Rust 工具链组件或 target 安装失败'] } + script { ciResults['Setup: Toolchains & Targets'] = [status: 'failed', detail: 'Rust 工具链组件或 target 安装失败'] } } always { - script { ciFinishGiteeStage('Check Environment', ciResults, 'Rust 工具链组件或 target 安装失败') } + script { ciFinishGiteeStage('Setup: Toolchains & Targets', ciResults, 'Rust 工具链组件或 target 安装失败') } } } } - stage('Rustfmt') { + stage('Check: Rustfmt') { steps { script { - giteeStartCheckRun('Rustfmt') + giteeStartCheckRun('Check: Rustfmt') runRustfmt() - ciResults['Rustfmt'] = [status: 'passed'] + ciResults['Check: Rustfmt'] = [status: 'passed'] } } post { success { - script { ciResults['Rustfmt'] = [status: 'passed'] } + script { ciResults['Check: Rustfmt'] = [status: 'passed'] } } failure { - script { ciResults['Rustfmt'] = [status: 'failed', detail: 'cargo fmt --check 发现格式问题'] } + script { ciResults['Check: Rustfmt'] = [status: 'failed', detail: 'cargo fmt --check 发现格式问题'] } } always { - script { ciFinishGiteeStage('Rustfmt', ciResults, 'cargo fmt --check 发现格式问题') } - } - } - } - - stage('Prefetch Dependencies') { - steps { - script { - giteeStartCheckRun('Prefetch Dependencies') - prefetchCargoDeps() - ciResults['Prefetch Dependencies'] = [status: 'passed'] - } - } - post { - success { - script { ciResults['Prefetch Dependencies'] = [status: 'passed'] } - } - failure { - script { ciResults['Prefetch Dependencies'] = [status: 'failed', detail: 'cargo fetch 失败'] } - } - always { - script { ciFinishGiteeStage('Prefetch Dependencies', ciResults, 'cargo fetch 失败') } + script { ciFinishGiteeStage('Check: Rustfmt', ciResults, 'cargo fmt --check 发现格式问题') } } } } stage('Build & Test') { parallel { - stage('Clippy+Build: aarch64-crosvm-virt') { + stage('Build Check: aarch64-crosvm-virt') { steps { script { - giteeEnsureCheckRunStarted('Clippy+Build: aarch64-crosvm-virt') + giteeEnsureCheckRunStarted('Build Check: aarch64-crosvm-virt') runClippyAndBuild('aarch64-crosvm-virt') - ciResults['Clippy+Build: aarch64-crosvm-virt'] = [status: 'passed'] + ciResults['Build Check: aarch64-crosvm-virt'] = [status: 'passed'] } } post { - success { script { ciResults['Clippy+Build: aarch64-crosvm-virt'] = [status: 'passed'] } } - failure { script { ciResults['Clippy+Build: aarch64-crosvm-virt'] = [status: 'failed', detail: 'clippy 或 build 失败'] } } - always { script { ciFinishGiteeStage('Clippy+Build: aarch64-crosvm-virt', ciResults, 'clippy 或 build 失败') } } + success { script { ciResults['Build Check: aarch64-crosvm-virt'] = [status: 'passed'] } } + failure { script { ciResults['Build Check: aarch64-crosvm-virt'] = [status: 'failed', detail: 'clippy 或 build 失败'] } } + always { script { ciFinishGiteeStage('Build Check: aarch64-crosvm-virt', ciResults, 'clippy 或 build 失败') } } } } - stage('Clippy+Runtime: riscv64-qemu-virt') { + stage('Runtime Test: x86_64-qemu-virt') { steps { script { - giteeEnsureCheckRunStarted('Clippy+Runtime: riscv64-qemu-virt') - runClippyAndRuntime('riscv64') - ciResults['Clippy+Runtime: riscv64-qemu-virt'] = [status: 'passed'] + giteeEnsureCheckRunStarted('Runtime Test: x86_64-qemu-virt') + runClippyAndRuntime('x86_64') + ciResults['Runtime Test: x86_64-qemu-virt'] = [status: 'passed'] } } post { - success { script { ciResults['Clippy+Runtime: riscv64-qemu-virt'] = [status: 'passed'] } } - failure { script { ciResults['Clippy+Runtime: riscv64-qemu-virt'] = [status: 'failed', detail: '阶段失败,详见下方日志。'] } } - always { script { ciFinishGiteeStage('Clippy+Runtime: riscv64-qemu-virt', ciResults, '阶段失败,详见下方日志。') } } + success { script { ciResults['Runtime Test: x86_64-qemu-virt'] = [status: 'passed'] } } + failure { + script { ciResults['Runtime Test: x86_64-qemu-virt'] = [status: 'failed', detail: '阶段失败,详见下方日志。'] } + } + always { script { ciFinishGiteeStage('Runtime Test: x86_64-qemu-virt', ciResults, '阶段失败,详见下方日志。') } } } } - stage('Clippy+Runtime: x86_64-qemu-virt') { + stage('Runtime Test: aarch64-qemu-virt') { steps { script { - giteeEnsureCheckRunStarted('Clippy+Runtime: x86_64-qemu-virt') - runClippyAndRuntime('x86_64') - ciResults['Clippy+Runtime: x86_64-qemu-virt'] = [status: 'passed'] + giteeEnsureCheckRunStarted('Runtime Test: aarch64-qemu-virt') + runClippyAndRuntime('aarch64') + ciResults['Runtime Test: aarch64-qemu-virt'] = [status: 'passed'] } } post { - success { script { ciResults['Clippy+Runtime: x86_64-qemu-virt'] = [status: 'passed'] } } + success { script { ciResults['Runtime Test: aarch64-qemu-virt'] = [status: 'passed'] } } failure { - script { ciResults['Clippy+Runtime: x86_64-qemu-virt'] = [status: 'failed', detail: '阶段失败,详见下方日志。'] } + script { ciResults['Runtime Test: aarch64-qemu-virt'] = [status: 'failed', detail: '阶段失败,详见下方日志。'] } } - always { script { ciFinishGiteeStage('Clippy+Runtime: x86_64-qemu-virt', ciResults, '阶段失败,详见下方日志。') } } + always { script { ciFinishGiteeStage('Runtime Test: aarch64-qemu-virt', ciResults, '阶段失败,详见下方日志。') } } } } - stage('Clippy+Runtime: aarch64-qemu-virt') { + stage('Runtime Test: riscv64-qemu-virt') { steps { script { - giteeEnsureCheckRunStarted('Clippy+Runtime: aarch64-qemu-virt') - runClippyAndRuntime('aarch64') - ciResults['Clippy+Runtime: aarch64-qemu-virt'] = [status: 'passed'] + giteeEnsureCheckRunStarted('Runtime Test: riscv64-qemu-virt') + runClippyAndRuntime('riscv64') + ciResults['Runtime Test: riscv64-qemu-virt'] = [status: 'passed'] } } post { - success { script { ciResults['Clippy+Runtime: aarch64-qemu-virt'] = [status: 'passed'] } } - failure { - script { ciResults['Clippy+Runtime: aarch64-qemu-virt'] = [status: 'failed', detail: '阶段失败,详见下方日志。'] } - } - always { script { ciFinishGiteeStage('Clippy+Runtime: aarch64-qemu-virt', ciResults, '阶段失败,详见下方日志。') } } + success { script { ciResults['Runtime Test: riscv64-qemu-virt'] = [status: 'passed'] } } + failure { script { ciResults['Runtime Test: riscv64-qemu-virt'] = [status: 'failed', detail: '阶段失败,详见下方日志。'] } } + always { script { ciFinishGiteeStage('Runtime Test: riscv64-qemu-virt', ciResults, '阶段失败,详见下方日志。') } } } } - stage('TEE: x86_64') { + stage('TEE Storage: x86_64') { steps { script { - giteeEnsureCheckRunStarted('TEE: x86_64') + giteeEnsureCheckRunStarted('TEE Storage: x86_64') teeResults['x86_64'] = runTeeStorageTest('x86_64') - ciResults['TEE: x86_64'] = [status: 'passed'] + ciResults['TEE Storage: x86_64'] = [status: 'passed'] } } post { @@ -197,18 +176,18 @@ pipeline { if (!teeResults.containsKey('x86_64')) { teeResults['x86_64'] = [arch: 'x86_64', passed: 0, failed: 0, status: 'failed', errorSnippet: '构建或启动阶段失败,请查看 Jenkins 日志'] } - ciResults['TEE: x86_64'] = [status: 'failed', detail: teeResults['x86_64']?.errorSnippet ?: 'TEE 测试失败'] + ciResults['TEE Storage: x86_64'] = [status: 'failed', detail: teeResults['x86_64']?.errorSnippet ?: 'TEE 测试失败'] } } - success { script { ciResults['TEE: x86_64'] = [status: 'passed'] } } - always { script { ciFinishGiteeStage('TEE: x86_64', ciResults, 'TEE 测试失败') } } + success { script { ciResults['TEE Storage: x86_64'] = [status: 'passed'] } } + always { script { ciFinishGiteeStage('TEE Storage: x86_64', ciResults, 'TEE 测试失败') } } } } - stage('TEE: aarch64') { + stage('TEE Storage: aarch64') { steps { script { - giteeEnsureCheckRunStarted('TEE: aarch64') + giteeEnsureCheckRunStarted('TEE Storage: aarch64') teeResults['aarch64'] = runTeeStorageTest('aarch64') - ciResults['TEE: aarch64'] = [status: 'passed'] + ciResults['TEE Storage: aarch64'] = [status: 'passed'] } } post { @@ -216,10 +195,10 @@ pipeline { if (!teeResults.containsKey('aarch64')) { teeResults['aarch64'] = [arch: 'aarch64', passed: 0, failed: 0, status: 'failed', errorSnippet: '构建或启动阶段失败,请查看 Jenkins 日志'] } - ciResults['TEE: aarch64'] = [status: 'failed', detail: teeResults['aarch64']?.errorSnippet ?: 'TEE 测试失败'] + ciResults['TEE Storage: aarch64'] = [status: 'failed', detail: teeResults['aarch64']?.errorSnippet ?: 'TEE 测试失败'] } } - success { script { ciResults['TEE: aarch64'] = [status: 'passed'] } } - always { script { ciFinishGiteeStage('TEE: aarch64', ciResults, 'TEE 测试失败') } } + success { script { ciResults['TEE Storage: aarch64'] = [status: 'passed'] } } + always { script { ciFinishGiteeStage('TEE Storage: aarch64', ciResults, 'TEE 测试失败') } } } } } @@ -245,7 +224,7 @@ pipeline { notifyGiteePullRequest(built.comment) giteeFinalizeAllCheckRuns(ciResults, failedStageLogs) giteeRefreshFailedCheckOutputs(ciResults, failedStageLogs) - // Gitee 检查列表按更新时间排序:构建末尾按顺序刷新 4 个顺序 stage,使其排在 6 个并行之上 + // Gitee 检查列表按更新时间排序:构建末尾按顺序刷新 3 个顺序 stage,使其排在 6 个并行之上 giteeReorderSequentialCheckRuns(ciResults, failedStageLogs) if (currentBuild.currentResult == 'SUCCESS') { giteeTestPass() @@ -259,26 +238,8 @@ pipeline { } } -def prefetchCargoDeps() { - initStageLog('Prefetch Dependencies') - ws("${env.ROOT_WS}/prefetch") { - def stageWorkspace = pwd() - try { - deleteDir() - restoreSource() - sh '''#!/bin/bash -set -euo pipefail -''' + stageLogTeeLine('Prefetch Dependencies') + ''' -ROOT_WS="${ROOT_WS}" scripts/ci/prefetch_cargo_deps.sh -''' - } finally { - fixWorkspaceOwnership(stageWorkspace) - } - } -} - def checkBuildEnvironment() { - initStageLog('Check Environment') + initStageLog('Setup: Toolchains & Targets') ws("${env.ROOT_WS}/env-check") { def stageWorkspace = pwd() try { @@ -286,7 +247,7 @@ def checkBuildEnvironment() { restoreSource() sh '''#!/bin/bash set -euo pipefail -''' + stageLogTeeLine('Check Environment') + ''' +''' + stageLogTeeLine('Setup: Toolchains & Targets') + ''' echo "==> Checking Rust build environment..." NIGHTLY_TOOLCHAIN="${AUX_RUST_TOOLCHAIN}" @@ -376,7 +337,7 @@ rustup +"${NIGHTLY_TOOLCHAIN}" target list --installed } def runRustfmt() { - initStageLog('Rustfmt') + initStageLog('Check: Rustfmt') ws("${env.ROOT_WS}/rustfmt") { def stageWorkspace = pwd() try { @@ -384,7 +345,7 @@ def runRustfmt() { restoreSource() sh """#!/bin/bash set -euo pipefail -${stageLogTeeLine('Rustfmt')} +${stageLogTeeLine('Check: Rustfmt')} cargo +"${AUX_RUST_TOOLCHAIN}" fmt --all --check """ } finally { @@ -393,16 +354,8 @@ cargo +"${AUX_RUST_TOOLCHAIN}" fmt --all --check } } -def cargoOfflineEnv() { - return [ - 'CARGO_NET_OFFLINE=true', - 'CARGO_HTTP_TIMEOUT=120', - 'CARGO_NET_RETRY=3', - ] -} - def runClippyAndBuild(String platform) { - def stageName = "Clippy+Build: ${platform}" + def stageName = "Build Check: ${platform}" initStageLog(stageName) ws("${env.ROOT_WS}/clippy-build-${platform}") { def stageWorkspace = pwd() @@ -411,7 +364,7 @@ def runClippyAndBuild(String platform) { deleteDir() restoreSource() - withEnv(["TARGET_DIR=${buildTargetDir}"] + cargoOfflineEnv()) { + withEnv(["TARGET_DIR=${buildTargetDir}"]) { sh """#!/bin/bash set -euo pipefail ${stageLogTeeLine(stageName)} @@ -428,7 +381,7 @@ stdbuf -oL -eL make build def runClippyAndRuntime(String arch) { def platform = "${arch}-qemu-virt" - def stageName = "Clippy+Runtime: ${platform}" + def stageName = "Runtime Test: ${platform}" def stageLog = stageLogFile(stageName) initStageLog(stageName) def runtimeTargetDir = targetDirForArch(arch) @@ -439,24 +392,22 @@ def runClippyAndRuntime(String arch) { restoreSource() withEnv(["TARGET_DIR=${runtimeTargetDir}", "STAGE_LOG=${stageLog}"]) { - withEnv(cargoOfflineEnv()) { - sh """#!/bin/bash + sh """#!/bin/bash set -euo pipefail ${stageLogTeeLine(stageName)} cp platforms/${platform}/defconfig .config make clippy """ - runUnitTests(arch) - generateCoverageHtml(arch) - copyCoverageToWorkspace(arch) + runUnitTests(arch) + generateCoverageHtml(arch) + copyCoverageToWorkspace(arch) - sh """#!/bin/bash + sh """#!/bin/bash set -euo pipefail ${stageLogTeeLine(stageName)} cp platforms/${platform}/defconfig .config stdbuf -oL -eL make build """ - } dir('test-harness') { git branch: "${env.TEST_HARNESS_BRANCH}", @@ -630,18 +581,17 @@ def restoreReplayGiteeEnv() { } def prepareSource() { - initStageLog('Prepare Source') + initStageLog('Source: Checkout & PR Base') ws("${env.ROOT_WS}/source-cache") { def sourceWorkspace = pwd() try { deleteDir() checkoutProject() markSafeDirectory() - sh './scripts/ci/setup_cargo_mirror.sh' env.GIT_COMMIT = sh(script: 'git rev-parse HEAD', returnStdout: true).trim() echo "Checked out HEAD: ${env.GIT_COMMIT}" if (env.giteePullRequestIid?.trim()) { - checkNotDiverged('Prepare Source') + checkNotDiverged('Source: Checkout & PR Base') } } finally { fixWorkspaceOwnership(sourceWorkspace) @@ -837,22 +787,21 @@ def giteeCheckIdsFile() { /** 并行阶段顺序(PR 评论表格 + gitee_check_runs.py manifest) */ def ciParallelStageOrder() { return [ - 'Clippy+Build: aarch64-crosvm-virt', - 'Clippy+Runtime: x86_64-qemu-virt', - 'Clippy+Runtime: aarch64-qemu-virt', - 'Clippy+Runtime: riscv64-qemu-virt', - 'TEE: x86_64', - 'TEE: aarch64', + 'Build Check: aarch64-crosvm-virt', + 'Runtime Test: x86_64-qemu-virt', + 'Runtime Test: aarch64-qemu-virt', + 'Runtime Test: riscv64-qemu-virt', + 'TEE Storage: x86_64', + 'TEE Storage: aarch64', ] } /** 串行阶段顺序(PR 评论表格 + gitee_check_runs.py manifest) */ def ciSequentialStageOrder() { return [ - 'Prepare Source', - 'Check Environment', - 'Rustfmt', - 'Prefetch Dependencies', + 'Source: Checkout & PR Base', + 'Setup: Toolchains & Targets', + 'Check: Rustfmt', ] } @@ -1007,7 +956,7 @@ def targetTripleFor(String archOrPlatform) { } def runTeeStorageTest(String arch) { - def stageName = "TEE: ${arch}" + def stageName = "TEE Storage: ${arch}" initStageLog(stageName) def result = [arch: arch, passed: 0, failed: 0, status: 'unknown', errorSnippet: ''] def muslTarget = "${arch}-unknown-linux-musl" diff --git a/scripts/ci/prefetch_cargo_deps.sh b/scripts/ci/prefetch_cargo_deps.sh deleted file mode 100755 index 89094ed8..00000000 --- a/scripts/ci/prefetch_cargo_deps.sh +++ /dev/null @@ -1,115 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -cd "$(dirname "${BASH_SOURCE[0]}")/../.." - -retry() { - local attempts="$1" - shift - local i - for i in $(seq 1 "${attempts}"); do - "$@" && return 0 - if [[ "${i}" == "${attempts}" ]]; then - return 1 - fi - echo "Command failed, retrying (${i}/${attempts}): $*" >&2 - sleep 5 - done -} - -fetch() { - local label="$1" - shift - echo "==> Fetching ${label}" - retry 3 cargo fetch "$@" -} - -fetch_offline_locked() { - local label="$1" - shift - echo "==> Verifying locked offline cache for ${label}" - CARGO_NET_OFFLINE=true cargo fetch "$@" --locked -} - -sync_lockfiles_to_source_cache() { - if [[ -z "${ROOT_WS:-}" || ! -d "${ROOT_WS}/source-cache" ]]; then - return 0 - fi - - if [[ -f Cargo.lock ]]; then - cp Cargo.lock "${ROOT_WS}/source-cache/Cargo.lock" - fi - if [[ -f xtask/Cargo.lock ]]; then - mkdir -p "${ROOT_WS}/source-cache/xtask" - cp xtask/Cargo.lock "${ROOT_WS}/source-cache/xtask/Cargo.lock" - fi - if [[ -f tee_apps/Cargo.lock ]]; then - mkdir -p "${ROOT_WS}/source-cache/tee_apps" - cp tee_apps/Cargo.lock "${ROOT_WS}/source-cache/tee_apps/Cargo.lock" - fi -} - -arch_for_platform() { - local platform="$1" - case "${platform}" in - x86_64-*) echo "x86_64" ;; - aarch64-*) echo "aarch64" ;; - riscv64-*) echo "riscv64" ;; - *) - echo "Unsupported platform: ${platform}" >&2 - return 1 - ;; - esac -} - -target_for_arch() { - local arch="$1" - case "${arch}" in - x86_64) echo "x86_64-unknown-none" ;; - aarch64) echo "aarch64-unknown-none-softfloat" ;; - riscv64) echo "riscv64gc-unknown-none-elf" ;; - *) - echo "Unsupported arch: ${arch}" >&2 - return 1 - ;; - esac -} - -echo "==> Cargo environment" -cargo --version -rustc --version -echo "CARGO_HOME=${CARGO_HOME:-${HOME}/.cargo}" -echo "CARGO_REGISTRIES_CRATES_IO_PROTOCOL=${CARGO_REGISTRIES_CRATES_IO_PROTOCOL:-}" -echo "CARGO_HTTP_TIMEOUT=${CARGO_HTTP_TIMEOUT:-}" -echo "CARGO_NET_RETRY=${CARGO_NET_RETRY:-}" - -platforms=( - x86_64-qemu-virt - aarch64-qemu-virt - aarch64-crosvm-virt - riscv64-qemu-virt -) - -for platform in "${platforms[@]}"; do - arch="$(arch_for_platform "${platform}")" - target="$(target_for_arch "${arch}")" - cp "platforms/${platform}/defconfig" .config - fetch "entry ${platform} (${target})" --manifest-path entry/Cargo.toml --target "${target}" -done - -fetch "main workspace x86_64 UEFI target" --manifest-path Cargo.toml --target x86_64-unknown-uefi -fetch "xtask workspace" --manifest-path xtask/Cargo.toml -fetch "tee app shell" --manifest-path tee_apps/sh/Cargo.toml - -for platform in "${platforms[@]}"; do - arch="$(arch_for_platform "${platform}")" - target="$(target_for_arch "${arch}")" - cp "platforms/${platform}/defconfig" .config - fetch_offline_locked "entry ${platform} (${target})" --manifest-path entry/Cargo.toml --target "${target}" -done - -fetch_offline_locked "main workspace x86_64 UEFI target" --manifest-path Cargo.toml --target x86_64-unknown-uefi -fetch_offline_locked "xtask workspace" --manifest-path xtask/Cargo.toml -sync_lockfiles_to_source_cache - -echo "==> Cargo dependency prefetch complete" diff --git a/scripts/ci/setup_cargo_mirror.sh b/scripts/ci/setup_cargo_mirror.sh deleted file mode 100755 index 805147d6..00000000 --- a/scripts/ci/setup_cargo_mirror.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -cd "$(dirname "${BASH_SOURCE[0]}")/../.." - -mirror_url="${XKERNEL_CARGO_REGISTRY_MIRROR:-sparse+https://rsproxy.cn/index/}" -mirror_name="${XKERNEL_CARGO_REGISTRY_MIRROR_NAME:-rsproxy}" -config_path=".cargo/.config.toml" - -case "${mirror_url}" in - none|off|disable|disabled) - rm -f "${config_path}" - echo "==> Cargo registry mirror disabled; using default crates.io registry" - exit 0 - ;; - sparse+*|git+*) ;; - http://*|https://*) mirror_url="sparse+${mirror_url}" ;; - *) - echo "Invalid XKERNEL_CARGO_REGISTRY_MIRROR: ${mirror_url}" >&2 - echo "Use sparse+https://..., git+https://..., https://..., or 'none'." >&2 - exit 1 - ;; -esac - -mkdir -p .cargo -cat > "${config_path}" < Cargo registry mirror configured: ${mirror_name} -> ${mirror_url}" -- Gitee From 68355fbfc7189d1b1965cef057301f4e9b9d9ce1 Mon Sep 17 00:00:00 2001 From: wangyining Date: Sat, 30 May 2026 00:16:58 +0800 Subject: [PATCH 4/7] split ci logic --- Jenkinsfile | 774 ++++++++++---------------- scripts/ci/check_build_environment.sh | 84 +++ scripts/ci/run_tee_storage_test.sh | 65 +++ scripts/ci/run_unit_tests.sh | 75 +++ 4 files changed, 513 insertions(+), 485 deletions(-) create mode 100755 scripts/ci/check_build_environment.sh create mode 100755 scripts/ci/run_tee_storage_test.sh create mode 100755 scripts/ci/run_unit_tests.sh diff --git a/Jenkinsfile b/Jenkinsfile index f3f3d14e..675c209c 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -15,6 +15,8 @@ pipeline { skipDefaultCheckout(true) timestamps() parallelsAlwaysFailFast() + timeout(time: 90, unit: 'MINUTES') + buildDiscarder(logRotator(numToKeepStr: '30', artifactNumToKeepStr: '10')) } environment { @@ -39,22 +41,12 @@ pipeline { script { env.ROOT_WS = env.WORKSPACE currentBuild.description = "PR#${env.giteePullRequestIid ?: 'manual'}" - prepareSource() - // 先创建 6 个并行检查占位(较早创建 → Gitee 列表靠下);顺序 3 项在后续 start/finish - giteeStartParallelCheckRuns() - giteeStartCheckRun('Source: Checkout & PR Base') - ciResults['Source: Checkout & PR Base'] = [status: 'passed'] - } - } - post { - success { - script { ciResults['Source: Checkout & PR Base'] = [status: 'passed'] } - } - failure { - script { ciResults['Source: Checkout & PR Base'] = [status: 'failed', detail: '源码准备失败(可能是分支分叉需要 rebase)'] } - } - always { - script { ciFinishGiteeStage('Source: Checkout & PR Base', ciResults, '源码准备失败(可能是分支分叉需要 rebase)') } + runCiStage(sourceStageName(), ciFailureDetail(sourceStageName()), false) { + prepareSource() + // 先创建 6 个并行检查占位(较早创建 -> Gitee 列表靠下);顺序 3 项在后续 start/finish + giteeStartParallelCheckRuns() + giteeStartCheckRun(sourceStageName()) + } } } } @@ -62,20 +54,9 @@ pipeline { stage('Setup: Toolchains & Targets') { steps { script { - giteeStartCheckRun('Setup: Toolchains & Targets') - checkBuildEnvironment() - ciResults['Setup: Toolchains & Targets'] = [status: 'passed'] - } - } - post { - success { - script { ciResults['Setup: Toolchains & Targets'] = [status: 'passed'] } - } - failure { - script { ciResults['Setup: Toolchains & Targets'] = [status: 'failed', detail: 'Rust 工具链组件或 target 安装失败'] } - } - always { - script { ciFinishGiteeStage('Setup: Toolchains & Targets', ciResults, 'Rust 工具链组件或 target 安装失败') } + runCiStage(setupStageName(), ciFailureDetail(setupStageName())) { + checkBuildEnvironment() + } } } } @@ -83,123 +64,17 @@ pipeline { stage('Check: Rustfmt') { steps { script { - giteeStartCheckRun('Check: Rustfmt') - runRustfmt() - ciResults['Check: Rustfmt'] = [status: 'passed'] - } - } - post { - success { - script { ciResults['Check: Rustfmt'] = [status: 'passed'] } - } - failure { - script { ciResults['Check: Rustfmt'] = [status: 'failed', detail: 'cargo fmt --check 发现格式问题'] } - } - always { - script { ciFinishGiteeStage('Check: Rustfmt', ciResults, 'cargo fmt --check 发现格式问题') } + runCiStage(rustfmtStageName(), ciFailureDetail(rustfmtStageName())) { + runRustfmt() + } } } } stage('Build & Test') { - parallel { - stage('Build Check: aarch64-crosvm-virt') { - steps { - script { - giteeEnsureCheckRunStarted('Build Check: aarch64-crosvm-virt') - runClippyAndBuild('aarch64-crosvm-virt') - ciResults['Build Check: aarch64-crosvm-virt'] = [status: 'passed'] - } - } - post { - success { script { ciResults['Build Check: aarch64-crosvm-virt'] = [status: 'passed'] } } - failure { script { ciResults['Build Check: aarch64-crosvm-virt'] = [status: 'failed', detail: 'clippy 或 build 失败'] } } - always { script { ciFinishGiteeStage('Build Check: aarch64-crosvm-virt', ciResults, 'clippy 或 build 失败') } } - } - } - stage('Runtime Test: x86_64-qemu-virt') { - steps { - script { - giteeEnsureCheckRunStarted('Runtime Test: x86_64-qemu-virt') - runClippyAndRuntime('x86_64') - ciResults['Runtime Test: x86_64-qemu-virt'] = [status: 'passed'] - } - } - post { - success { script { ciResults['Runtime Test: x86_64-qemu-virt'] = [status: 'passed'] } } - failure { - script { ciResults['Runtime Test: x86_64-qemu-virt'] = [status: 'failed', detail: '阶段失败,详见下方日志。'] } - } - always { script { ciFinishGiteeStage('Runtime Test: x86_64-qemu-virt', ciResults, '阶段失败,详见下方日志。') } } - } - } - stage('Runtime Test: aarch64-qemu-virt') { - steps { - script { - giteeEnsureCheckRunStarted('Runtime Test: aarch64-qemu-virt') - runClippyAndRuntime('aarch64') - ciResults['Runtime Test: aarch64-qemu-virt'] = [status: 'passed'] - } - } - post { - success { script { ciResults['Runtime Test: aarch64-qemu-virt'] = [status: 'passed'] } } - failure { - script { ciResults['Runtime Test: aarch64-qemu-virt'] = [status: 'failed', detail: '阶段失败,详见下方日志。'] } - } - always { script { ciFinishGiteeStage('Runtime Test: aarch64-qemu-virt', ciResults, '阶段失败,详见下方日志。') } } - } - } - stage('Runtime Test: riscv64-qemu-virt') { - steps { - script { - giteeEnsureCheckRunStarted('Runtime Test: riscv64-qemu-virt') - runClippyAndRuntime('riscv64') - ciResults['Runtime Test: riscv64-qemu-virt'] = [status: 'passed'] - } - } - post { - success { script { ciResults['Runtime Test: riscv64-qemu-virt'] = [status: 'passed'] } } - failure { script { ciResults['Runtime Test: riscv64-qemu-virt'] = [status: 'failed', detail: '阶段失败,详见下方日志。'] } } - always { script { ciFinishGiteeStage('Runtime Test: riscv64-qemu-virt', ciResults, '阶段失败,详见下方日志。') } } - } - } - stage('TEE Storage: x86_64') { - steps { - script { - giteeEnsureCheckRunStarted('TEE Storage: x86_64') - teeResults['x86_64'] = runTeeStorageTest('x86_64') - ciResults['TEE Storage: x86_64'] = [status: 'passed'] - } - } - post { - failure { script { - if (!teeResults.containsKey('x86_64')) { - teeResults['x86_64'] = [arch: 'x86_64', passed: 0, failed: 0, status: 'failed', errorSnippet: '构建或启动阶段失败,请查看 Jenkins 日志'] - } - ciResults['TEE Storage: x86_64'] = [status: 'failed', detail: teeResults['x86_64']?.errorSnippet ?: 'TEE 测试失败'] - } } - success { script { ciResults['TEE Storage: x86_64'] = [status: 'passed'] } } - always { script { ciFinishGiteeStage('TEE Storage: x86_64', ciResults, 'TEE 测试失败') } } - } - } - stage('TEE Storage: aarch64') { - steps { - script { - giteeEnsureCheckRunStarted('TEE Storage: aarch64') - teeResults['aarch64'] = runTeeStorageTest('aarch64') - ciResults['TEE Storage: aarch64'] = [status: 'passed'] - } - } - post { - failure { script { - if (!teeResults.containsKey('aarch64')) { - teeResults['aarch64'] = [arch: 'aarch64', passed: 0, failed: 0, status: 'failed', errorSnippet: '构建或启动阶段失败,请查看 Jenkins 日志'] - } - ciResults['TEE Storage: aarch64'] = [status: 'failed', detail: teeResults['aarch64']?.errorSnippet ?: 'TEE 测试失败'] - } } - success { script { ciResults['TEE Storage: aarch64'] = [status: 'passed'] } } - always { script { ciFinishGiteeStage('TEE Storage: aarch64', ciResults, 'TEE 测试失败') } } - } + steps { + script { + parallel ciParallelBranches() } } } @@ -209,162 +84,221 @@ pipeline { post { always { script { - restoreReplayGiteeEnv() - fixWorkspaceOwnership(env.WORKSPACE) - def failedStageLogs = archiveFailedStageLogs(ciResults) - archiveArtifacts artifacts: [ - 'stage-logs/**/*.log', - '**/artifacts/**/*', '**/logs/**/*', '**/unittest-output.log', - '**/tee-test-output.log', - '**/coverage-html/**/*', '**/coverage.info', '**/coverage.xml', '**/coverage.txt' - ].join(','), allowEmptyArchive: true - deleteOldCiComments() - def coverageSummary = collectCoverageSummary() - def built = buildCombinedComment(ciResults, coverageSummary, failedStageLogs) - notifyGiteePullRequest(built.comment) - giteeFinalizeAllCheckRuns(ciResults, failedStageLogs) - giteeRefreshFailedCheckOutputs(ciResults, failedStageLogs) - // Gitee 检查列表按更新时间排序:构建末尾按顺序刷新 3 个顺序 stage,使其排在 6 个并行之上 - giteeReorderSequentialCheckRuns(ciResults, failedStageLogs) - if (currentBuild.currentResult == 'SUCCESS') { - giteeTestPass() - } else { - giteeTestReset() - } - fixWorkspaceOwnership(env.WORKSPACE) + finalizeCiBuild() } cleanWs deleteDirs: true, disableDeferredWipeout: true, notFailBuild: true } } } -def checkBuildEnvironment() { - initStageLog('Setup: Toolchains & Targets') - ws("${env.ROOT_WS}/env-check") { - def stageWorkspace = pwd() - try { - deleteDir() - restoreSource() - sh '''#!/bin/bash -set -euo pipefail -''' + stageLogTeeLine('Setup: Toolchains & Targets') + ''' -echo "==> Checking Rust build environment..." -NIGHTLY_TOOLCHAIN="${AUX_RUST_TOOLCHAIN}" - -retry() { - local attempts="$1" - shift - local i - for i in $(seq 1 "${attempts}"); do - "$@" && return 0 - if [ "${i}" = "${attempts}" ]; then - return 1 - fi - echo "Command failed, retrying (${i}/${attempts}): $*" >&2 - sleep 5 - done -} - -eval "$( -python3 <<'PY' -import shlex -import tomllib - -with open("rust-toolchain.toml", "rb") as f: - toolchain = tomllib.load(f)["toolchain"] - -def array(name, values): - print(f"{name}=(" + " ".join(shlex.quote(v) for v in values) + ")") - -print("XKERNEL_TOOLCHAIN=" + shlex.quote(toolchain["channel"])) -array("XKERNEL_COMPONENTS", toolchain.get("components", [])) -array("XKERNEL_TARGETS", toolchain.get("targets", [])) -PY -)" +def sourceStageName() { return 'Source: Checkout & PR Base' } +def setupStageName() { return 'Setup: Toolchains & Targets' } +def rustfmtStageName() { return 'Check: Rustfmt' } -DEFAULT_EXTRA_TARGETS=( - x86_64-unknown-uefi - x86_64-unknown-linux-musl - aarch64-unknown-linux-musl - riscv64gc-unknown-linux-musl -) -NIGHTLY_TARGETS=( - x86_64-unknown-linux-musl - aarch64-unknown-linux-musl - riscv64gc-unknown-linux-musl -) +def runtimeTestArchitectures() { return ['x86_64', 'aarch64', 'riscv64'] } +def teeTestArchitectures() { return ['x86_64', 'aarch64'] } -dedup_words() { - printf '%s\n' "$@" | awk 'NF && !seen[$0]++' +def ciSequentialStages() { + return [ + [name: sourceStageName(), failure: '源码准备失败(可能是分支分叉需要 rebase)'], + [name: setupStageName(), failure: 'Rust 工具链组件或 target 安装失败'], + [name: rustfmtStageName(), failure: 'cargo fmt --check 发现格式问题'], + ] } -mapfile -t DEFAULT_TARGETS < <(dedup_words "${XKERNEL_TARGETS[@]}" "${DEFAULT_EXTRA_TARGETS[@]}") +def ciParallelStages() { + def stages = [[ + name: 'Build Check: aarch64-crosvm-virt', + failure: 'clippy 或 build 失败', + type: 'build', + platform: 'aarch64-crosvm-virt', + ]] + + runtimeTestArchitectures().each { arch -> + stages << [ + name: "Runtime Test: ${arch}-qemu-virt", + failure: 'clippy、单元测试、覆盖率或 runtime 测试失败', + type: 'runtime', + arch: arch, + ] + } -default_install_args=("${XKERNEL_TOOLCHAIN}" --profile minimal --no-self-update) -for component in "${XKERNEL_COMPONENTS[@]}"; do - default_install_args+=(--component "${component}") -done -for target in "${DEFAULT_TARGETS[@]}"; do - default_install_args+=(--target "${target}") -done + teeTestArchitectures().each { arch -> + stages << [ + name: "TEE Storage: ${arch}", + failure: 'TEE 测试失败', + type: 'tee', + arch: arch, + ] + } -nightly_install_args=("${NIGHTLY_TOOLCHAIN}" --profile minimal --component rustfmt --no-self-update) -for target in "${NIGHTLY_TARGETS[@]}"; do - nightly_install_args+=(--target "${target}") -done + return stages +} -echo "==> Installing x-kernel toolchain: ${XKERNEL_TOOLCHAIN}" -retry 3 rustup toolchain install "${default_install_args[@]}" +def ciStageNames(List stages) { + return stages.collect { it.name } +} -echo "==> Installing auxiliary nightly toolchain: ${NIGHTLY_TOOLCHAIN}" -retry 3 rustup toolchain install "${nightly_install_args[@]}" +def ciFailureDetail(String stageName) { + def stage = (ciSequentialStages() + ciParallelStages()).find { it.name == stageName } + return stage?.failure ?: "${stageName} 失败,请查看 Jenkins 日志" +} -echo "==> Active default toolchain" -cargo --version -rustc --version -rustup show active-toolchain +def archiveArtifactPatterns() { + return [ + 'stage-logs/**/*.log', + '**/artifacts/**/*', + '**/logs/**/*', + '**/unittest-output.log', + '**/tee-test-output.log', + '**/coverage-html/**/*', + '**/coverage.info', + '**/coverage.xml', + '**/coverage.txt', + ] +} -echo "==> Installed default targets" -rustup target list --installed +def finalizeCiBuild() { + restoreReplayGiteeEnv() + fixWorkspaceOwnership(env.WORKSPACE) -echo "==> Installed nightly targets" -rustup +"${NIGHTLY_TOOLCHAIN}" target list --installed -''' - } finally { - fixWorkspaceOwnership(stageWorkspace) + def failedStageLogs = archiveFailedStageLogs(ciResults) + archiveArtifacts artifacts: archiveArtifactPatterns().join(','), allowEmptyArchive: true + + deleteOldCiComments() + def coverageSummary = collectCoverageSummary() + def built = buildCombinedComment(ciResults, coverageSummary, failedStageLogs) + notifyGiteePullRequest(built.comment) + + giteeFinalizeAllCheckRuns(ciResults, failedStageLogs) + giteeRefreshFailedCheckOutputs(ciResults, failedStageLogs) + giteeReorderSequentialCheckRuns(ciResults, failedStageLogs) + + if (currentBuild.currentResult == 'SUCCESS') { + giteeTestPass() + } else { + giteeTestReset() + } + + fixWorkspaceOwnership(env.WORKSPACE) +} + +def runCiStage(String stageName, String failedDetail, Closure body) { + runCiStage(stageName, failedDetail, true, body) +} + +def runCiStage(String stageName, String failedDetail, boolean startCheckRun, Closure body) { + if (startCheckRun) { + giteeStartCheckRun(stageName) + } + + try { + body.call() + ciResults[stageName] = [status: 'passed'] + } catch (e) { + ciResults[stageName] = [status: 'failed', detail: buildFailureDetail(stageName, failedDetail, e)] + throw e + } finally { + ciFinishGiteeStage(stageName, ciResults, failedDetail) + } +} + +def buildFailureDetail(String stageName, String defaultDetail, Throwable error) { + def details = [] + if (defaultDetail?.trim()) { + details << defaultDetail.trim() + } + + def message = error?.message?.trim() + if (message && !details.any { message.contains(it) }) { + details << "Jenkins 异常: ${message}" + } + + return details ? details.join('\n') : "${stageName} 失败,请查看 Jenkins 日志" +} + +def ciParallelBranches() { + def branches = [:] + + ciParallelStages().each { stageSpec -> + def spec = stageSpec + branches[spec.name] = { + runParallelCiStage(spec.name, spec.failure) { + runCiWorkload(spec) + } } } + + branches.failFast = true + return branches } -def runRustfmt() { - initStageLog('Check: Rustfmt') - ws("${env.ROOT_WS}/rustfmt") { +def runCiWorkload(Map spec) { + switch (spec.type) { + case 'build': + runClippyAndBuild(spec.platform) + break + case 'runtime': + runClippyAndRuntime(spec.arch) + break + case 'tee': + teeResults[spec.arch] = runTeeStorageTest(spec.arch) + break + default: + error("Unsupported CI stage type: ${spec.type}") + } +} + +def runParallelCiStage(String stageName, String failedDetail, Closure body) { + stage(stageName) { + runCiStage(stageName, failedDetail, false) { + giteeEnsureCheckRunStarted(stageName) + body.call() + } + } +} + +def withCleanSourceWorkspace(String relativePath, Closure body) { + ws("${env.ROOT_WS}/${relativePath}") { def stageWorkspace = pwd() try { deleteDir() restoreSource() - sh """#!/bin/bash -set -euo pipefail -${stageLogTeeLine('Check: Rustfmt')} -cargo +"${AUX_RUST_TOOLCHAIN}" fmt --all --check -""" + body.call(stageWorkspace) } finally { fixWorkspaceOwnership(stageWorkspace) } } } +def checkBuildEnvironment() { + initStageLog(setupStageName()) + withCleanSourceWorkspace('env-check') { + sh """#!/bin/bash +set -euo pipefail +${stageLogTeeLine(setupStageName())} +scripts/ci/check_build_environment.sh +""" + } +} + +def runRustfmt() { + initStageLog(rustfmtStageName()) + withCleanSourceWorkspace('rustfmt') { + sh """#!/bin/bash +set -euo pipefail +${stageLogTeeLine(rustfmtStageName())} +cargo +"${AUX_RUST_TOOLCHAIN}" fmt --all --check +""" + } +} + def runClippyAndBuild(String platform) { def stageName = "Build Check: ${platform}" initStageLog(stageName) - ws("${env.ROOT_WS}/clippy-build-${platform}") { - def stageWorkspace = pwd() - def buildTargetDir = "/xkernel-target/build-${platform}" - try { - deleteDir() - restoreSource() - - withEnv(["TARGET_DIR=${buildTargetDir}"]) { + def buildTargetDir = "/xkernel-target/build-${platform}" + withCleanSourceWorkspace("clippy-build-${platform}") { + withEnv(["TARGET_DIR=${buildTargetDir}"]) { sh """#!/bin/bash set -euo pipefail ${stageLogTeeLine(stageName)} @@ -372,9 +306,6 @@ cp platforms/${platform}/defconfig .config make clippy stdbuf -oL -eL make build """ - } - } finally { - fixWorkspaceOwnership(stageWorkspace) } } } @@ -385,139 +316,49 @@ def runClippyAndRuntime(String arch) { def stageLog = stageLogFile(stageName) initStageLog(stageName) def runtimeTargetDir = targetDirForArch(arch) - ws("${env.ROOT_WS}/${arch}") { - def stageWorkspace = pwd() - try { - deleteDir() - restoreSource() - withEnv(["TARGET_DIR=${runtimeTargetDir}", "STAGE_LOG=${stageLog}"]) { - sh """#!/bin/bash + withCleanSourceWorkspace(arch) { + withEnv(["TARGET_DIR=${runtimeTargetDir}", "STAGE_LOG=${stageLog}"]) { + sh """#!/bin/bash set -euo pipefail ${stageLogTeeLine(stageName)} cp platforms/${platform}/defconfig .config make clippy """ - runUnitTests(arch) - generateCoverageHtml(arch) - copyCoverageToWorkspace(arch) + runUnitTests(arch) + generateCoverageHtml(arch) + copyCoverageToWorkspace(arch) - sh """#!/bin/bash + sh """#!/bin/bash set -euo pipefail ${stageLogTeeLine(stageName)} cp platforms/${platform}/defconfig .config stdbuf -oL -eL make build """ - dir('test-harness') { - git branch: "${env.TEST_HARNESS_BRANCH}", - url: "${env.TEST_HARNESS_REPO}" - markSafeDirectory() - - def hostfwdPort - def vsockCid - switch (arch) { - case 'x86_64': - hostfwdPort = '5556' - vsockCid = '101' - break - case 'aarch64': - hostfwdPort = '5557' - vsockCid = '102' - break - case 'riscv64': - hostfwdPort = '5560' - vsockCid = '103' - break - default: - error("Unsupported runtime test architecture: ${arch}") - } - withEnv(["XKERNEL_REMOTE=${pwd()}/..", "ARCH=${arch}", - "STARRY_SKIP_BUILD=1", - "ROOTFS_CACHE_DIR=/xkernel-target/rootfs-cache", - "GUEST_CASES_TARGET_DIR=${runtimeTargetDir}/guest-cases-${arch}"]) { - sh """#!/bin/bash + dir('test-harness') { + git branch: "${env.TEST_HARNESS_BRANCH}", + url: "${env.TEST_HARNESS_REPO}" + markSafeDirectory() + + withEnv(["XKERNEL_REMOTE=${pwd()}/..", "ARCH=${arch}", + "STARRY_SKIP_BUILD=1", + "ROOTFS_CACHE_DIR=/xkernel-target/rootfs-cache"]) { + sh """#!/bin/bash set -euo pipefail ${stageLogTeeLine(stageName)} stdbuf -oL -eL make ci-test run """ - } } } - } finally { - fixWorkspaceOwnership(stageWorkspace) } } } def runUnitTests(String arch) { - def ansiFilter = stageLogAnsiFilterPipe() - def stageTee = env.STAGE_LOG?.trim() - ? "exec > >(${ansiFilter} | tee -a '${env.STAGE_LOG}') 2>&1" - : '' sh """#!/bin/bash set -euo pipefail -${stageTee} - -ROOTFS_VERSION=20260302 -ROOTFS_CACHE="/xkernel-target/rootfs-cache" -ROOTFS_CACHED="\${ROOTFS_CACHE}/rootfs-${arch}.img" -mkdir -p "\${ROOTFS_CACHE}" - -if [ ! -f "\${ROOTFS_CACHED}" ]; then - IMG_URL="https://gitee.com/openkylin/x-kernel-image/releases/download/\${ROOTFS_VERSION}" - curl -f -L "\${IMG_URL}/rootfs-${arch}.img.xz" -o "\${ROOTFS_CACHED}.xz" - xz -df "\${ROOTFS_CACHED}.xz" -fi -cp --reflink=auto "\${ROOTFS_CACHED}" disk.img - -TIMEOUT=480 -if [ "${arch}" = "aarch64" ]; then - TIMEOUT=481 -fi - -sed -i -e 's/WARN/__TEMP__/g' -e 's/ERROR/WARN/g' -e 's/__TEMP__/ERROR/g' .config - -set +e -timeout \${TIMEOUT} stdbuf -oL -eL make UNITTEST=y VSOCK=n NET=n run | ${ansiFilter} | tee unittest-output.log -status=\${PIPESTATUS[0]} -set -e - -if [ "\${status}" -eq 124 ]; then - echo "Unit test timed out after \${TIMEOUT}s" - exit 1 -fi - -if grep -q "UNITTEST_STATUS: TESTS_FAILED" unittest-output.log; then - echo "Unit tests failed" - exit 1 -fi - -if grep -q "UNITTEST_STATUS: ALL_TESTS_PASSED" unittest-output.log; then - exit 0 -fi - -if grep -q "panicked at" unittest-output.log; then - echo "Kernel panic detected during unit tests" - exit 1 -fi - -if grep -q "test result:.*FAILED" unittest-output.log; then - echo "Legacy unit test failure detected" - exit 1 -fi - -if grep -q "test result: ok" unittest-output.log; then - exit 0 -fi - -if [ "\${status}" -ne 0 ]; then - echo "Unit test command exited with status \${status}" - exit 1 -fi - -echo "Unable to determine test result from unit test output" -exit 1 +scripts/ci/run_unit_tests.sh '${arch}' """ } @@ -581,7 +422,7 @@ def restoreReplayGiteeEnv() { } def prepareSource() { - initStageLog('Source: Checkout & PR Base') + initStageLog(sourceStageName()) ws("${env.ROOT_WS}/source-cache") { def sourceWorkspace = pwd() try { @@ -591,7 +432,7 @@ def prepareSource() { env.GIT_COMMIT = sh(script: 'git rev-parse HEAD', returnStdout: true).trim() echo "Checked out HEAD: ${env.GIT_COMMIT}" if (env.giteePullRequestIid?.trim()) { - checkNotDiverged('Source: Checkout & PR Base') + checkNotDiverged(sourceStageName()) } } finally { fixWorkspaceOwnership(sourceWorkspace) @@ -740,15 +581,39 @@ def targetDirForArch(String arch) { return "/xkernel-target/runtime-${arch}" } -def allocateFreePort() { - return sh(script: "python3 -c \"import socket; s=socket.socket(); s.bind(('',0)); print(s.getsockname()[1]); s.close()\"", - returnStdout: true).trim().toInteger() +def teePortFor(String arch) { + def base = arch == 'x86_64' ? 21000 : 21100 + def build = env.BUILD_NUMBER?.toInteger() ?: 1 + return firstFreeTcpPort(base + ((build % 50) * 2), 80) } -def allocateFreeCid() { +def firstFreeTcpPort(int startPort, int attempts) { + return sh(script: """#!/bin/bash +set -euo pipefail +python3 - '${startPort}' '${attempts}' <<'PY' +import socket +import sys + +start = int(sys.argv[1]) +attempts = int(sys.argv[2]) +for port in range(start, start + attempts): + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + try: + sock.bind(('127.0.0.1', port)) + except OSError: + continue + print(port) + raise SystemExit(0) +raise SystemExit(f'no free TCP port in [{start}, {start + attempts})') +PY +""", returnStdout: true).trim().toInteger() +} + +def teeVsockCidFor(String arch) { + def archOffset = arch == 'x86_64' ? 1 : 2 def build = env.BUILD_NUMBER?.toInteger() ?: 1 - def stage = env.STAGE_NAME?.hashCode()?.abs() ?: 0 - return 100 + ((build * 7 + stage) % 2000000) + return 100000000 + (build * 10) + archOffset } def giteeTestPass() { giteePrApi('POST', 'test', 'pass', '--data-urlencode \'force=true\'') } @@ -786,23 +651,12 @@ def giteeCheckIdsFile() { /** 并行阶段顺序(PR 评论表格 + gitee_check_runs.py manifest) */ def ciParallelStageOrder() { - return [ - 'Build Check: aarch64-crosvm-virt', - 'Runtime Test: x86_64-qemu-virt', - 'Runtime Test: aarch64-qemu-virt', - 'Runtime Test: riscv64-qemu-virt', - 'TEE Storage: x86_64', - 'TEE Storage: aarch64', - ] + return ciStageNames(ciParallelStages()) } /** 串行阶段顺序(PR 评论表格 + gitee_check_runs.py manifest) */ def ciSequentialStageOrder() { - return [ - 'Source: Checkout & PR Base', - 'Setup: Toolchains & Targets', - 'Check: Rustfmt', - ] + return ciStageNames(ciSequentialStages()) } /** @@ -877,9 +731,9 @@ def giteeFinishCheckRun(String stageName, Map ciResults, Map failedStageLogs = [ } /** 保证 ciResults 有状态后再 finish,避免 Gitee 检查一直停在「进行中」。 */ -def ciFinishGiteeStage(String stageName, Map results, String failedDetail) { +def ciFinishGiteeStage(String stageName, Map results, String failedDetail = null) { if (!results[stageName]?.status) { - results[stageName] = [status: 'failed', detail: failedDetail ?: "${stageName} 缺少 CI 结果"] + results[stageName] = [status: 'failed', detail: failedDetail ?: ciFailureDetail(stageName)] echo "WARN: ${stageName} missing ciResults status before Gitee finish" } giteeFinishCheckRun(stageName, results, [:]) @@ -959,98 +813,47 @@ def runTeeStorageTest(String arch) { def stageName = "TEE Storage: ${arch}" initStageLog(stageName) def result = [arch: arch, passed: 0, failed: 0, status: 'unknown', errorSnippet: ''] - def muslTarget = "${arch}-unknown-linux-musl" - def muslLinker = "${arch}-linux-musl-gcc" - def targetUpper = muslTarget.toUpperCase().replaceAll('-', '_') def teeTargetDir = "/xkernel-target/tee-${arch}" + def teeHostfwdPort = teePortFor(arch) + def teeVsockCid = teeVsockCidFor(arch) - ws("${env.ROOT_WS}/tee-test-${arch}") { - def stageWorkspace = pwd() - def teeHostfwdPort = allocateFreePort() - def teeVsockCid = allocateFreeCid() - try { - deleteDir() - restoreSource() - - withEnv(["TARGET_DIR=${teeTargetDir}"]) { + withCleanSourceWorkspace("tee-test-${arch}") { stageWorkspace -> + withEnv(["TARGET_DIR=${teeTargetDir}", + "HOSTFWD_PORT=${teeHostfwdPort}", + "VSOCK_CID=${teeVsockCid}"]) { sh """#!/bin/bash set -euo pipefail ${stageLogTeeLine(stageName)} - -LIBUTEE_DIR="/xkernel-target/libutee-${arch}" -mkdir -p "\${LIBUTEE_DIR}" - -echo "==> Syncing rust-libutee..." -if [ -d "\${LIBUTEE_DIR}/.git" ]; then - git -C "\${LIBUTEE_DIR}" fetch --depth 1 origin HEAD && git -C "\${LIBUTEE_DIR}" reset --hard FETCH_HEAD -else - git clone --depth 1 ${env.LIBUTEE_REPO} "\${LIBUTEE_DIR}" -fi - -echo "==> Building storage_test for ${muslTarget}..." -( cd "\${LIBUTEE_DIR}" && CC=${muslLinker} cargo +"\${AUX_RUST_TOOLCHAIN}" build --bin storage_test --release --target ${muslTarget} ) - -echo "==> Building tee_apps/sh with TEE_INIT_APPS=/tee/storage_test..." -TEE_INIT_APPS="/tee/storage_test" RUSTFLAGS= CC=${muslLinker} \\ - CARGO_TARGET_${targetUpper}_LINKER=${muslLinker} \\ - cargo build --release --target ${muslTarget} --manifest-path tee_apps/sh/Cargo.toml \\ - --target-dir "\${TARGET_DIR}/tee-apps" - -echo "==> Creating rootfs..." -env -u CARGO_BUILD_TARGET RUSTFLAGS= cargo run --release \\ - --manifest-path xtask/crate_rootfs/Cargo.toml \\ - --target-dir "\${TARGET_DIR}/crate-rootfs" -- \\ - --image disk.img --size-bytes 64M \\ - --copy "\${TARGET_DIR}/tee-apps/${muslTarget}/release/sh":/bin/sh \\ - --copy "\${LIBUTEE_DIR}/target/${muslTarget}/release/storage_test":/tee/storage_test - -echo "==> Building kernel..." -cp ${defconfigFor(arch)} .config -make build - -echo "==> Running TEE storage test..." -set +e -timeout 1200 stdbuf -oL -eL make HOSTFWD_PORT=${teeHostfwdPort} VSOCK_CID=${teeVsockCid} justrun 2>&1 | tee tee-test-output.log -QEMU_STATUS=\${PIPESTATUS[0]} -set -e - -if [ "\${QEMU_STATUS}" -eq 124 ]; then - echo "TEE_RESULT: TIMEOUT" | tee -a tee-test-output.log -elif [ "\${QEMU_STATUS}" -ne 0 ]; then - echo "TEE_RESULT: QEMU_ERROR(\${QEMU_STATUS})" | tee -a tee-test-output.log -fi +scripts/ci/run_tee_storage_test.sh '${arch}' """ - } - - def logText = readFile("${stageWorkspace}/tee-test-output.log") - result.passed = logText.split('<<< test success', -1).length - 1 - result.failed = logText.split('<<< test failed', -1).length - 1 - - if (logText.contains('TEE_RESULT: TIMEOUT')) { - result.status = 'timeout' - result.errorSnippet = "QEMU 运行超时(360s),测试未能完成\n通过: ${result.passed},失败: ${result.failed}" - } else if (logText.contains('TEE_RESULT: QEMU_ERROR')) { - result.status = 'failed' - result.errorSnippet = extractSnippet(logText, 'TEE_RESULT: QEMU_ERROR', 5) - } else if (logText.contains('panicked at')) { - result.status = 'panic' - result.errorSnippet = extractSnippet(logText, 'panicked at', 8) - } else if (result.failed > 0) { - result.status = 'failed' - result.errorSnippet = extractSnippet(logText, '<<< test failed', 5) - } else if (result.passed > 0) { - result.status = 'passed' - } else { - result.status = 'no_output' - result.errorSnippet = '未检测到任何测试输出,QEMU 可能未正常启动' - } + } - if (result.status != 'passed') { - error("TEE Storage Test ${arch}: ${result.status} (passed=${result.passed}, failed=${result.failed})") - } + def logText = readFile("${stageWorkspace}/tee-test-output.log") + result.passed = logText.split('<<< test success', -1).length - 1 + result.failed = logText.split('<<< test failed', -1).length - 1 + + if (logText.contains('TEE_RESULT: TIMEOUT')) { + result.status = 'timeout' + result.errorSnippet = "QEMU 运行超时(1200s),测试未能完成 +通过: ${result.passed},失败: ${result.failed}" + } else if (logText.contains('TEE_RESULT: QEMU_ERROR')) { + result.status = 'failed' + result.errorSnippet = extractSnippet(logText, 'TEE_RESULT: QEMU_ERROR', 5) + } else if (logText.contains('panicked at')) { + result.status = 'panic' + result.errorSnippet = extractSnippet(logText, 'panicked at', 8) + } else if (result.failed > 0) { + result.status = 'failed' + result.errorSnippet = extractSnippet(logText, '<<< test failed', 5) + } else if (result.passed > 0) { + result.status = 'passed' + } else { + result.status = 'no_output' + result.errorSnippet = '未检测到任何测试输出,QEMU 可能未正常启动' + } - } finally { - fixWorkspaceOwnership(stageWorkspace) + if (result.status != 'passed') { + error("TEE Storage Test ${arch}: ${result.status} (passed=${result.passed}, failed=${result.failed})") } } @@ -1193,14 +996,14 @@ def archiveFailedStageLogs(Map ciResults) { } def buildCombinedComment(Map ciResults, String coverageSummary, Map failedStageLogs = [:]) { - def result = buildCiComment(ciResults, coverageSummary) + def result = buildCiComment(ciResults, coverageSummary, failedStageLogs) return [ comment: "\n${result.body}", allPassed: result.allPassed, ] } -def buildCiComment(Map results, String coverageSummary = '') { +def buildCiComment(Map results, String coverageSummary = '', Map failedStageLogs = [:]) { def stagesUrl = "${env.BUILD_URL}stages/" def stageOrder = ciStageOrder() def normalizedResults = [:] @@ -1242,9 +1045,10 @@ ${rows} } def errorBlocks = stageOrder.findAll { name -> - normalizedResults[name].status != 'passed' && normalizedResults[name].detail?.trim() + normalizedResults[name].status != 'passed' && + (failedStageLogs[name]?.trim() || normalizedResults[name].detail?.trim()) }.collect { name -> - def detail = normalizedResults[name].detail.take(1000) + def detail = (failedStageLogs[name]?.trim() ?: normalizedResults[name].detail).take(4000) "\n### ❌ ${name}\n\n
\n查看错误详情\n\n" + '```' + "\n${detail}\n" + '```' + "\n
" }.join('\n') diff --git a/scripts/ci/check_build_environment.sh b/scripts/ci/check_build_environment.sh new file mode 100755 index 00000000..fa508fc9 --- /dev/null +++ b/scripts/ci/check_build_environment.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "==> Checking Rust build environment..." +NIGHTLY_TOOLCHAIN="${AUX_RUST_TOOLCHAIN}" + +retry() { + local attempts="$1" + shift + local i + for i in $(seq 1 "${attempts}"); do + "$@" && return 0 + if [ "${i}" = "${attempts}" ]; then + return 1 + fi + echo "Command failed, retrying (${i}/${attempts}): $*" >&2 + sleep 5 + done +} + +eval "$( +python3 <<'PY_TOOLCHAIN' +import shlex +import tomllib + +with open("rust-toolchain.toml", "rb") as f: + toolchain = tomllib.load(f)["toolchain"] + +def array(name, values): + print(f"{name}=(" + " ".join(shlex.quote(v) for v in values) + ")") + +print("XKERNEL_TOOLCHAIN=" + shlex.quote(toolchain["channel"])) +array("XKERNEL_COMPONENTS", toolchain.get("components", [])) +array("XKERNEL_TARGETS", toolchain.get("targets", [])) +PY_TOOLCHAIN +)" + +DEFAULT_EXTRA_TARGETS=( + x86_64-unknown-uefi + x86_64-unknown-linux-musl + aarch64-unknown-linux-musl + riscv64gc-unknown-linux-musl +) +NIGHTLY_TARGETS=( + x86_64-unknown-linux-musl + aarch64-unknown-linux-musl + riscv64gc-unknown-linux-musl +) + +dedup_words() { + printf '%s\n' "$@" | awk 'NF && !seen[$0]++' +} + +mapfile -t DEFAULT_TARGETS < <(dedup_words "${XKERNEL_TARGETS[@]}" "${DEFAULT_EXTRA_TARGETS[@]}") + +default_install_args=("${XKERNEL_TOOLCHAIN}" --profile minimal --no-self-update) +for component in "${XKERNEL_COMPONENTS[@]}"; do + default_install_args+=(--component "${component}") +done +for target in "${DEFAULT_TARGETS[@]}"; do + default_install_args+=(--target "${target}") +done + +nightly_install_args=("${NIGHTLY_TOOLCHAIN}" --profile minimal --component rustfmt --no-self-update) +for target in "${NIGHTLY_TARGETS[@]}"; do + nightly_install_args+=(--target "${target}") +done + +echo "==> Installing x-kernel toolchain: ${XKERNEL_TOOLCHAIN}" +retry 3 rustup toolchain install "${default_install_args[@]}" + +echo "==> Installing auxiliary nightly toolchain: ${NIGHTLY_TOOLCHAIN}" +retry 3 rustup toolchain install "${nightly_install_args[@]}" + +echo "==> Active default toolchain" +cargo --version +rustc --version +rustup show active-toolchain + +echo "==> Installed default targets" +rustup target list --installed + +echo "==> Installed nightly targets" +rustup +"${NIGHTLY_TOOLCHAIN}" target list --installed diff --git a/scripts/ci/run_tee_storage_test.sh b/scripts/ci/run_tee_storage_test.sh new file mode 100755 index 00000000..8c1489b7 --- /dev/null +++ b/scripts/ci/run_tee_storage_test.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +set -euo pipefail + +arch="${1:?usage: run_tee_storage_test.sh }" +musl_target="${arch}-unknown-linux-musl" +musl_linker="${arch}-linux-musl-gcc" +target_upper="${musl_target^^}" +target_upper="${target_upper//-/_}" + +: "${AUX_RUST_TOOLCHAIN:?AUX_RUST_TOOLCHAIN is required}" +: "${LIBUTEE_REPO:?LIBUTEE_REPO is required}" +: "${TARGET_DIR:?TARGET_DIR is required}" +: "${HOSTFWD_PORT:?HOSTFWD_PORT is required}" +: "${VSOCK_CID:?VSOCK_CID is required}" + +libutee_dir="/xkernel-target/libutee-${arch}" +mkdir -p "${libutee_dir}" + +echo "==> Syncing rust-libutee..." +if [ -d "${libutee_dir}/.git" ]; then + git -C "${libutee_dir}" fetch --depth 1 origin HEAD + git -C "${libutee_dir}" reset --hard FETCH_HEAD +else + git clone --depth 1 "${LIBUTEE_REPO}" "${libutee_dir}" +fi + +echo "==> Building storage_test for ${musl_target}..." +( + cd "${libutee_dir}" + CC="${musl_linker}" cargo +"${AUX_RUST_TOOLCHAIN}" build \ + --bin storage_test --release --target "${musl_target}" +) + +echo "==> Building tee_apps/sh with TEE_INIT_APPS=/tee/storage_test..." +env \ + TEE_INIT_APPS="/tee/storage_test" \ + RUSTFLAGS= \ + CC="${musl_linker}" \ + "CARGO_TARGET_${target_upper}_LINKER=${musl_linker}" \ + cargo build --release --target "${musl_target}" --manifest-path tee_apps/sh/Cargo.toml \ + --target-dir "${TARGET_DIR}/tee-apps" + +echo "==> Creating rootfs..." +env -u CARGO_BUILD_TARGET RUSTFLAGS= cargo run --release \ + --manifest-path xtask/crate_rootfs/Cargo.toml \ + --target-dir "${TARGET_DIR}/crate-rootfs" -- \ + --image disk.img --size-bytes 64M \ + --copy "${TARGET_DIR}/tee-apps/${musl_target}/release/sh":/bin/sh \ + --copy "${libutee_dir}/target/${musl_target}/release/storage_test":/tee/storage_test + +echo "==> Building kernel..." +cp "platforms/${arch}-qemu-virt/defconfig" .config +make build + +echo "==> Running TEE storage test..." +set +e +timeout 1200 stdbuf -oL -eL make HOSTFWD_PORT="${HOSTFWD_PORT}" VSOCK_CID="${VSOCK_CID}" justrun 2>&1 | tee tee-test-output.log +qemu_status=${PIPESTATUS[0]} +set -e + +if [ "${qemu_status}" -eq 124 ]; then + echo "TEE_RESULT: TIMEOUT" | tee -a tee-test-output.log +elif [ "${qemu_status}" -ne 0 ]; then + echo "TEE_RESULT: QEMU_ERROR(${qemu_status})" | tee -a tee-test-output.log +fi diff --git a/scripts/ci/run_unit_tests.sh b/scripts/ci/run_unit_tests.sh new file mode 100755 index 00000000..ac8f06eb --- /dev/null +++ b/scripts/ci/run_unit_tests.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +set -euo pipefail + +arch="${1:?usage: run_unit_tests.sh }" + +ansi_filter() { + sed -u \ + -e 's/\x1[Bb]\[[0-9;]*[a-zA-Z]//g' \ + -e 's/\x9[Bb]\[[0-9;]*[a-zA-Z]//g' \ + -e 's/\[[0-9;]*[mK]//g' +} + +if [ -n "${STAGE_LOG:-}" ]; then + exec > >(ansi_filter | tee -a "${STAGE_LOG}") 2>&1 +fi + +ROOTFS_VERSION=20260302 +ROOTFS_CACHE="/xkernel-target/rootfs-cache" +ROOTFS_CACHED="${ROOTFS_CACHE}/rootfs-${arch}.img" +mkdir -p "${ROOTFS_CACHE}" + +if [ ! -f "${ROOTFS_CACHED}" ]; then + IMG_URL="https://gitee.com/openkylin/x-kernel-image/releases/download/${ROOTFS_VERSION}" + curl -f -L "${IMG_URL}/rootfs-${arch}.img.xz" -o "${ROOTFS_CACHED}.xz" + xz -df "${ROOTFS_CACHED}.xz" +fi +cp --reflink=auto "${ROOTFS_CACHED}" disk.img + +TIMEOUT=480 +if [ "${arch}" = "aarch64" ]; then + TIMEOUT=481 +fi + +sed -i -e 's/WARN/__TEMP__/g' -e 's/ERROR/WARN/g' -e 's/__TEMP__/ERROR/g' .config + +set +e +timeout "${TIMEOUT}" stdbuf -oL -eL make UNITTEST=y VSOCK=n NET=n run | ansi_filter | tee unittest-output.log +status=${PIPESTATUS[0]} +set -e + +if [ "${status}" -eq 124 ]; then + echo "Unit test timed out after ${TIMEOUT}s" + exit 1 +fi + +if grep -q "UNITTEST_STATUS: TESTS_FAILED" unittest-output.log; then + echo "Unit tests failed" + exit 1 +fi + +if grep -q "UNITTEST_STATUS: ALL_TESTS_PASSED" unittest-output.log; then + exit 0 +fi + +if grep -q "panicked at" unittest-output.log; then + echo "Kernel panic detected during unit tests" + exit 1 +fi + +if grep -q "test result:.*FAILED" unittest-output.log; then + echo "Legacy unit test failure detected" + exit 1 +fi + +if grep -q "test result: ok" unittest-output.log; then + exit 0 +fi + +if [ "${status}" -ne 0 ]; then + echo "Unit test command exited with status ${status}" + exit 1 +fi + +echo "Unable to determine test result from unit test output" +exit 1 -- Gitee From add3fb5ec80a089f703c496484028ddcdb37c980 Mon Sep 17 00:00:00 2001 From: wangyining Date: Sat, 30 May 2026 00:22:25 +0800 Subject: [PATCH 5/7] fmt --- Jenkinsfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 675c209c..f537447d 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -834,8 +834,7 @@ scripts/ci/run_tee_storage_test.sh '${arch}' if (logText.contains('TEE_RESULT: TIMEOUT')) { result.status = 'timeout' - result.errorSnippet = "QEMU 运行超时(1200s),测试未能完成 -通过: ${result.passed},失败: ${result.failed}" + result.errorSnippet = "QEMU 运行超时(1200s),测试未能完成\\n通过: ${result.passed},失败: ${result.failed}" } else if (logText.contains('TEE_RESULT: QEMU_ERROR')) { result.status = 'failed' result.errorSnippet = extractSnippet(logText, 'TEE_RESULT: QEMU_ERROR', 5) -- Gitee From 9811ca50397f9da234e0f27e0e1b995e3090c96e Mon Sep 17 00:00:00 2001 From: wangyining Date: Sat, 30 May 2026 00:24:44 +0800 Subject: [PATCH 6/7] fix --- Jenkinsfile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index f537447d..21e68cb4 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,7 +1,9 @@ #!/usr/bin/env groovy -def ciResults = [:] -def teeResults = [:] +import groovy.transform.Field + +@Field Map ciResults = [:] +@Field Map teeResults = [:] pipeline { agent { -- Gitee From aeec919218a7b62f0e7dd4180ce187f68b8a4fb7 Mon Sep 17 00:00:00 2001 From: wangyining Date: Sat, 30 May 2026 01:25:45 +0800 Subject: [PATCH 7/7] approve display --- Jenkinsfile | 66 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 42 insertions(+), 24 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 21e68cb4..5aeac5d4 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -148,6 +148,7 @@ def ciFailureDetail(String stageName) { def archiveArtifactPatterns() { return [ + 'ci-summary.md', 'stage-logs/**/*.log', '**/artifacts/**/*', '**/logs/**/*', @@ -170,6 +171,8 @@ def finalizeCiBuild() { deleteOldCiComments() def coverageSummary = collectCoverageSummary() def built = buildCombinedComment(ciResults, coverageSummary, failedStageLogs) + writeFile file: 'ci-summary.md', text: built.comment.replaceFirst(/^\n/, '') + currentBuild.description = buildShortBuildDescription(ciResults) notifyGiteePullRequest(built.comment) giteeFinalizeAllCheckRuns(ciResults, failedStageLogs) @@ -185,6 +188,21 @@ def finalizeCiBuild() { fixWorkspaceOwnership(env.WORKSPACE) } +def buildShortBuildDescription(Map results) { + def pr = env.giteePullRequestIid?.trim() ? "PR#${env.giteePullRequestIid}" : 'manual' + def sha = resolveHeadSha()?.take(8) ?: env.GIT_COMMIT?.take(8) ?: '' + def failedStages = ciStageOrder().findAll { results[it]?.status == 'failed' } + if (currentBuild.currentResult == 'SUCCESS' && failedStages.isEmpty()) { + return "${pr} ✅ ${sha}".trim() + } + if (!failedStages.isEmpty()) { + def failed = failedStages.take(2).join(', ') + def suffix = failedStages.size() > 2 ? " +${failedStages.size() - 2}" : '' + return "${pr} ❌ ${failed}${suffix} ${sha}".trim() + } + return "${pr} ❌ ${currentBuild.currentResult} ${sha}".trim() +} + def runCiStage(String stageName, String failedDetail, Closure body) { runCiStage(stageName, failedDetail, true, body) } @@ -276,7 +294,7 @@ def withCleanSourceWorkspace(String relativePath, Closure body) { def checkBuildEnvironment() { initStageLog(setupStageName()) withCleanSourceWorkspace('env-check') { - sh """#!/bin/bash + sh label: 'Install Rust toolchains and targets', script: """#!/bin/bash set -euo pipefail ${stageLogTeeLine(setupStageName())} scripts/ci/check_build_environment.sh @@ -287,7 +305,7 @@ scripts/ci/check_build_environment.sh def runRustfmt() { initStageLog(rustfmtStageName()) withCleanSourceWorkspace('rustfmt') { - sh """#!/bin/bash + sh label: 'Check Rust formatting', script: """#!/bin/bash set -euo pipefail ${stageLogTeeLine(rustfmtStageName())} cargo +"${AUX_RUST_TOOLCHAIN}" fmt --all --check @@ -301,7 +319,7 @@ def runClippyAndBuild(String platform) { def buildTargetDir = "/xkernel-target/build-${platform}" withCleanSourceWorkspace("clippy-build-${platform}") { withEnv(["TARGET_DIR=${buildTargetDir}"]) { - sh """#!/bin/bash + sh label: "Clippy and build ${platform}", script: """#!/bin/bash set -euo pipefail ${stageLogTeeLine(stageName)} cp platforms/${platform}/defconfig .config @@ -321,7 +339,7 @@ def runClippyAndRuntime(String arch) { withCleanSourceWorkspace(arch) { withEnv(["TARGET_DIR=${runtimeTargetDir}", "STAGE_LOG=${stageLog}"]) { - sh """#!/bin/bash + sh label: "Clippy ${platform}", script: """#!/bin/bash set -euo pipefail ${stageLogTeeLine(stageName)} cp platforms/${platform}/defconfig .config @@ -331,7 +349,7 @@ make clippy generateCoverageHtml(arch) copyCoverageToWorkspace(arch) - sh """#!/bin/bash + sh label: "Build ${platform}", script: """#!/bin/bash set -euo pipefail ${stageLogTeeLine(stageName)} cp platforms/${platform}/defconfig .config @@ -346,7 +364,7 @@ stdbuf -oL -eL make build withEnv(["XKERNEL_REMOTE=${pwd()}/..", "ARCH=${arch}", "STARRY_SKIP_BUILD=1", "ROOTFS_CACHE_DIR=/xkernel-target/rootfs-cache"]) { - sh """#!/bin/bash + sh label: "Run starry-test-harness ${arch}", script: """#!/bin/bash set -euo pipefail ${stageLogTeeLine(stageName)} stdbuf -oL -eL make ci-test run @@ -358,7 +376,7 @@ stdbuf -oL -eL make ci-test run } def runUnitTests(String arch) { - sh """#!/bin/bash + sh label: "Unit tests ${arch}", script: """#!/bin/bash set -euo pipefail scripts/ci/run_unit_tests.sh '${arch}' """ @@ -369,7 +387,7 @@ def generateCoverageHtml(String arch) { def baseDir = targetDirForArch(arch) def covInfo = "${baseDir}/${triple}/release/coverage.info" def htmlOut = "${baseDir}/${triple}/release/coverage-html" - sh """#!/bin/bash + sh label: "Generate coverage HTML ${arch}", script: """#!/bin/bash set -euo pipefail if [ ! -f "${covInfo}" ]; then echo "No coverage.info found, skipping HTML report" @@ -387,7 +405,7 @@ def copyCoverageToWorkspace(String arch) { def triple = targetTripleFor(arch) def baseDir = targetDirForArch(arch) def srcDir = "${baseDir}/${triple}/release" - sh """#!/bin/bash + sh label: "Collect coverage artifacts ${arch}", script: """#!/bin/bash set -euo pipefail mkdir -p coverage-artifacts for f in coverage-html coverage.info coverage.xml coverage.txt; do @@ -431,7 +449,7 @@ def prepareSource() { deleteDir() checkoutProject() markSafeDirectory() - env.GIT_COMMIT = sh(script: 'git rev-parse HEAD', returnStdout: true).trim() + env.GIT_COMMIT = sh(label: 'Resolve checked-out commit', script: 'git rev-parse HEAD', returnStdout: true).trim() echo "Checked out HEAD: ${env.GIT_COMMIT}" if (env.giteePullRequestIid?.trim()) { checkNotDiverged(sourceStageName()) @@ -445,7 +463,7 @@ def prepareSource() { def checkNotDiverged(String stageName = '') { def targetBranch = env.giteeTargetBranch ?: env.DEFAULT_BRANCH def teeLine = stageName ? stageLogTeeLine(stageName) : '' - def result = sh(script: """#!/bin/bash + def result = sh(label: 'Check PR branch is rebased', script: """#!/bin/bash set -euo pipefail ${teeLine} git fetch origin ${targetBranch} --quiet @@ -462,7 +480,7 @@ fi } def restoreSource() { - sh "tar cf - -C '${env.ROOT_WS}/source-cache' . | tar xf -" + sh label: 'Restore source snapshot', script: "tar cf - -C '${env.ROOT_WS}/source-cache' . | tar xf -" markSafeDirectory() } @@ -491,7 +509,7 @@ def checkoutProject() { } def markSafeDirectory() { - sh '''#!/bin/bash + sh label: 'Mark Git safe.directory', script: '''#!/bin/bash set -euo pipefail dir="$(pwd)" @@ -525,7 +543,7 @@ def deleteOldCiComments() { def namespace = env.giteeTargetNamespace ?: 'openkylin' def repo = env.giteeTargetRepoName ?: 'x-kernel' withCredentials([string(credentialsId: 'gitee-token-secret', variable: 'GITEE_TOKEN')]) { - def ids = sh(script: """#!/bin/bash + def ids = sh(label: 'Find old Gitee CI comments', script: """#!/bin/bash curl -sS --max-time 15 \ 'https://gitee.com/api/v5/repos/${namespace}/${repo}/pulls/${prNumber}/comments?page=1&per_page=100' \ --data-urlencode "access_token=\${GITEE_TOKEN}" | \ @@ -545,7 +563,7 @@ for c in comments: if (ids) { ids.split('\n').each { commentId -> if (commentId?.trim()) { - sh(script: """#!/bin/bash + sh(label: 'Delete old Gitee CI comment', script: """#!/bin/bash curl -sS --max-time 10 -X DELETE \ 'https://gitee.com/api/v5/repos/${namespace}/${repo}/pulls/comments/${commentId.trim()}' \ --data-urlencode "access_token=\${GITEE_TOKEN}" || true @@ -590,7 +608,7 @@ def teePortFor(String arch) { } def firstFreeTcpPort(int startPort, int attempts) { - return sh(script: """#!/bin/bash + return sh(label: 'Allocate TEE hostfwd port', script: """#!/bin/bash set -euo pipefail python3 - '${startPort}' '${attempts}' <<'PY' import socket @@ -628,7 +646,7 @@ def resolveHeadSha() { if (env.sha?.trim()) return env.sha.trim() def sourceCache = "${env.ROOT_WS}/source-cache" if (env.ROOT_WS?.trim() && fileExists("${sourceCache}/.git")) { - return sh(script: "git -C '${sourceCache}' rev-parse HEAD", returnStdout: true).trim() + return sh(label: 'Resolve cached source commit', script: "git -C '${sourceCache}' rev-parse HEAD", returnStdout: true).trim() } return null } @@ -710,7 +728,7 @@ def giteeCheck(String action, String stageName = null, Map ciResults = null, Map try { withCredentials([string(credentialsId: 'gitee-token-secret', variable: 'GITEE_TOKEN')]) { - sh(script: """#!/bin/bash + sh(label: 'Update Gitee check run', script: """#!/bin/bash set -euo pipefail python3 '${scriptPath}' \\ --owner '${namespace}' \\ @@ -759,7 +777,7 @@ def giteePrApi(String method, String endpoint, String label, String extraArgs) { def namespace = env.giteeTargetNamespace ?: 'openkylin' def repo = env.giteeTargetRepoName ?: 'x-kernel' withCredentials([string(credentialsId: 'gitee-token-secret', variable: 'GITEE_TOKEN')]) { - sh(script: """#!/bin/bash + sh(label: "Update Gitee PR test ${label}", script: """#!/bin/bash resp=\$(curl -sS -w '\\n%{http_code}' --max-time 15 -X ${method} \ 'https://gitee.com/api/v5/repos/${namespace}/${repo}/pulls/${prNumber}/${endpoint}' \ --data-urlencode "access_token=\${GITEE_TOKEN}" ${extraArgs} 2>&1) || true @@ -778,7 +796,7 @@ def fixWorkspaceOwnership(String workspacePath) { } withEnv(["_FIX_WS_PATH=${workspacePath}"]) { - sh '''#!/bin/bash + sh label: 'Fix workspace ownership', script: '''#!/bin/bash set -euo pipefail workspace_path="${_FIX_WS_PATH}" reference_path="$(dirname "${workspace_path}")" @@ -823,7 +841,7 @@ def runTeeStorageTest(String arch) { withEnv(["TARGET_DIR=${teeTargetDir}", "HOSTFWD_PORT=${teeHostfwdPort}", "VSOCK_CID=${teeVsockCid}"]) { - sh """#!/bin/bash + sh label: "Run TEE storage test ${arch}", script: """#!/bin/bash set -euo pipefail ${stageLogTeeLine(stageName)} scripts/ci/run_tee_storage_test.sh '${arch}' @@ -883,7 +901,7 @@ def collectCoverageSummary() { try { def triple = targetTripleFor(arch) def covFile = "${targetDirForArch(arch)}/${triple}/release/coverage.txt" - def content = sh(script: "cat '${covFile}' 2>/dev/null || true", returnStdout: true).trim() + def content = sh(label: "Read coverage summary ${arch}", script: "cat '${covFile}' 2>/dev/null || true", returnStdout: true).trim() if (content) { def lines = content.split('\n') def totalLine = lines.find { it.contains('TOTAL') } @@ -937,7 +955,7 @@ def initStageLog(String stageName) { return } def logFile = stageLogFile(stageName) - sh """#!/bin/bash + sh label: "Prepare stage log: ${stageName}", script: """#!/bin/bash set -euo pipefail mkdir -p '${env.ROOT_WS}/stage-logs' : > '${logFile}' @@ -978,7 +996,7 @@ def archiveFailedStageLogs(Map ciResults) { return failedLogs } - sh "mkdir -p '${env.ROOT_WS}/stage-logs' stage-logs || true" + sh label: 'Prepare failed stage log archive', script: "mkdir -p '${env.ROOT_WS}/stage-logs' stage-logs || true" fixWorkspaceOwnership(env.WORKSPACE) failedStages.each { stageName -> -- Gitee