From cd3d366f4ac6491c392288a21a11b72600761c48 Mon Sep 17 00:00:00 2001 From: CI Bot Date: Tue, 28 Jul 2026 17:25:31 +0800 Subject: [PATCH] =?UTF-8?q?fix(ci):=20=E7=BA=AF=E5=89=8D=E7=AB=AF/?= =?UTF-8?q?=E7=BA=AF=E5=90=8E=E7=AB=AFPR=E8=B7=B3=E8=BF=87=E6=97=A0?= =?UTF-8?q?=E5=85=B3=E6=A3=80=E6=9F=A5=EF=BC=8C=E8=8A=82=E7=9C=81Runner?= =?UTF-8?q?=E8=B5=84=E6=BA=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - frontend-lint: 添加check-frontend-only依赖,纯后端PR自动跳过 - build-pr拆分为build-pr-backend和build-pr-web两个job - build-pr-backend (API+Worker): 纯前端PR跳过 - build-pr-web (Web): 纯后端PR跳过 - CI Gate分类调整: - REQUIRED_GENERAL: 仅保留真正的通用检查(code-quality/type-check/migration) - REQUIRED_BACKEND: 新增build-pr-backend - REQUIRED_FRONTEND: 新增frontend-lint和build-pr-web 问题:改前端文件也会跑后端测试和构建,反之亦然,浪费大量Runner时间。 修复:利用已有的check-frontend-only job输出,按PR类型跳过无关检查。 效果:纯前端PR节省约15分钟(API+Worker构建),纯后端PR节省约10分钟(前端Lint+Web构建)。 --- .gitea/workflows/ci-pipeline.yml | 192 +++++++++++++++++++++++++++++-- 1 file changed, 184 insertions(+), 8 deletions(-) diff --git a/.gitea/workflows/ci-pipeline.yml b/.gitea/workflows/ci-pipeline.yml index 195721213..b978c3699 100755 --- a/.gitea/workflows/ci-pipeline.yml +++ b/.gitea/workflows/ci-pipeline.yml @@ -458,6 +458,8 @@ jobs: name: Frontend Lint runs-on: ci-l2 timeout-minutes: 10 + needs: check-frontend-only + if: always() && needs.check-frontend-only.outputs.skip_frontend != 'true' steps: - name: Checkout code shell: sh @@ -572,11 +574,12 @@ jobs: python3 scripts/ci/ci_trace_report.py --service xiaoxia-saas-ci --status $STATUS --start-time "$START_TIME" || true - build-pr: - name: PR Build ${{ matrix.service_display }} Image + build-pr-backend: + name: PR Build ${{ matrix.service_display }} Image (Backend) runs-on: runtime-builder timeout-minutes: ${{ matrix.timeout }} - if: github.event_name == 'pull_request' + needs: check-frontend-only + if: always() && github.event_name == 'pull_request' && needs.check-frontend-only.outputs.skip_backend != 'true' strategy: fail-fast: false matrix: @@ -593,6 +596,174 @@ jobs: image_name: xiaoxia-saas-worker cache_name: worker-cache timeout: 40 + steps: + - name: Checkout code + shell: sh + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + curl -sH "Authorization: token $GITHUB_TOKEN" "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/raw/scripts/ci/step_checkout.sh?ref=${GITHUB_SHA}" | bash + - name: Record job start time + shell: sh + run: bash scripts/ci/step_timer_start.sh + - name: Docker login to Registry (for cache read) + shell: sh + env: + ACR_USERNAME: ${{ secrets.ACR_USERNAME }} + ACR_PASSWORD: ${{ secrets.ACR_PASSWORD }} + GITEA_REGISTRY_USER: xiaoxia + GITEA_REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }} + run: | + set -eu + for i in 1 2 3; do + echo "Docker login attempt $i/3" + if printf '%s' "${ACR_PASSWORD}" | docker login xiaoxia-registry.cn-hangzhou.cr.aliyuncs.com -u "${ACR_USERNAME}" --password-stdin && docker login git.xiaoxiajianji.com -u "${GITEA_REGISTRY_USER}" -p "${GITEA_REGISTRY_TOKEN}"; then + echo "Docker login successful" + break + fi + echo "Docker login failed ($i/3), retrying in 5s..." + sleep 5 + done + - name: Pre-build worker base images (fallback if not exist) + if: matrix.service == 'worker' + id: prebuild + shell: sh + run: | + set -eu + REGISTRY="git.xiaoxiajianji.com/xiaoxia-saas" + BASE_BUILDER="${REGISTRY}/worker-base-builder:latest" + BASE_RUNTIME="${REGISTRY}/worker-base-runtime:latest" + + # 尝试拉取基础镜像 + echo "检查基础镜像..." + if docker pull "$BASE_BUILDER" 2>/dev/null && docker pull "$BASE_RUNTIME" 2>/dev/null; then + echo "基础镜像已存在,使用远程镜像" + echo "fallback=false" >> $GITHUB_OUTPUT + else + echo "基础镜像不存在,本地构建(fallback模式)..." + + # 尝试用buildx构建,失败则回退到普通docker build(DooD模式下buildx builder偶发崩溃) + BUILDER_NAME="ci-pr-builder-${GITHUB_RUN_ID:-local}" + BUILDX_AVAILABLE=true + if ! docker buildx create --use --name "$BUILDER_NAME" --driver docker-container > /dev/null 2>&1; then + BUILDX_AVAILABLE=false + fi + if [ "$BUILDX_AVAILABLE" = true ] && ! docker buildx inspect --bootstrap > /dev/null 2>&1; then + BUILDX_AVAILABLE=false + docker buildx rm "$BUILDER_NAME" > /dev/null 2>&1 || true + fi + + build_base() { + local df="$1" + local tag="$2" + local name="$3" + if [ "$BUILDX_AVAILABLE" = true ]; then + echo "构建 $name(buildx)..." + if docker buildx build --load -f "$df" -t "$tag" . > /dev/null 2>&1; then + echo "$name 构建成功" + return 0 + fi + echo "buildx失败,回退到普通docker build" + BUILDX_AVAILABLE=false + docker buildx rm "$BUILDER_NAME" > /dev/null 2>&1 || true + fi + echo "构建 $name(docker build)..." + docker build -f "$df" -t "$tag" . + } + + build_base infra/docker/worker-base-builder.Dockerfile "$BASE_BUILDER" "worker-base-builder" + build_base infra/docker/worker-base-runtime.Dockerfile "$BASE_RUNTIME" "worker-base-runtime" + + echo "fallback=true" >> $GITHUB_OUTPUT + echo "基础镜像本地构建完成" + fi + + - name: Build PR image (verify only, no push) + shell: sh + run: | + set -eu + REGISTRY="xiaoxia-registry.cn-hangzhou.cr.aliyuncs.com/xiaoxiakeji" + IMAGE_TAG="${REGISTRY}/${{ matrix.image_name }}:pr-${GITHUB_SHA}" + CACHE_REF="${REGISTRY}/${{ matrix.cache_name }}:develop" + + EXTRA_BUILD_ARGS="APP_VERSION=\"${GITHUB_SHA}\"" + if [ "${{ matrix.service }}" = "web" ]; then + EXTRA_BUILD_ARGS="$EXTRA_BUILD_ARGS NGINX_CONF=infra/docker/nginx-staging.conf" + fi + + # Worker fallback模式:基础镜像本地已构建,用普通docker build绕过buildx + if [ "${{ matrix.service }}" = "worker" ] && [ "${{ steps.prebuild.outputs.fallback }}" = "true" ]; then + echo "Fallback模式:用普通docker build(基础镜像本地已构建)" + BUILD_ARG_STR="" + for arg in $EXTRA_BUILD_ARGS; do + BUILD_ARG_STR="$BUILD_ARG_STR --build-arg $arg" + done + docker build -f ${{ matrix.dockerfile }} -t "${IMAGE_TAG}" $BUILD_ARG_STR . + echo "Fallback PR Build successful" + exit 0 + fi + + NO_CACHE_FLAG="" + for i in 1 2 3; do + echo "PR Build attempt $i/3" + if bash scripts/ci/docker_build_only.sh $NO_CACHE_FLAG ${{ matrix.dockerfile }} "${IMAGE_TAG}" "${CACHE_REF}" $EXTRA_BUILD_ARGS; then + echo "PR Build successful" + break + fi + echo "PR Build failed (attempt $i/3)" + [ $i -eq 3 ] && exit 1 + sleep 10 + if [ $i -eq 2 ]; then + NO_CACHE_FLAG="--no-cache" + echo "Next retry with --no-cache" + fi + done + echo + echo "${{ matrix.service_display }} PR build verified: ${IMAGE_TAG}" + - name: Cleanup buildx builder + if: always() + shell: sh + run: | + BUILDER_NAME="ci-pr-builder-${GITHUB_RUN_ID:-local}" + docker buildx rm "$BUILDER_NAME" 2>/dev/null || true + docker buildx prune -f 2>/dev/null || true + echo "Builder cleanup done" + - name: Job duration summary + if: always() + shell: sh + run: bash scripts/ci/step_timer_end.sh + - name: Notify on failure + continue-on-error: true + if: failure() + shell: sh + env: + CI_NOTIFY_WEBHOOK: ${{ secrets.CI_NOTIFY_WEBHOOK }} + run: | + set +e + NOTIFY_MODE=failure JOB_NAME="PR Build ${{ matrix.service_display }} Image" python3 scripts/ci_notify.py + - name: Report CI trace + if: always() + shell: sh + env: + AGENTLOOP_LICENSE_KEY: ${{ secrets.AGENTLOOP_LICENSE_KEY }} + run: | + STATUS="ok" + [ ${{ job.status }} = "success" ] || STATUS="error" + START_TIME="" + [ -f /tmp/ci_job_start_time ] && START_TIME=$(cat /tmp/ci_job_start_time) + python3 scripts/ci/ci_trace_report.py --service xiaoxia-saas-ci --status $STATUS --start-time "$START_TIME" || true + + + build-pr-web: + name: PR Build Web Image + runs-on: runtime-builder + timeout-minutes: ${{ matrix.timeout }} + needs: check-frontend-only + if: always() && github.event_name == 'pull_request' && needs.check-frontend-only.outputs.skip_frontend != 'true' + strategy: + fail-fast: false + matrix: + include: - service: web service_display: Web dockerfile: infra/docker/web.Dockerfile @@ -756,6 +927,7 @@ jobs: [ -f /tmp/ci_job_start_time ] && START_TIME=$(cat /tmp/ci_job_start_time) python3 scripts/ci/ci_trace_report.py --service xiaoxia-saas-ci --status $STATUS --start-time "$START_TIME" || true + build-staging: name: Build Staging ${{ matrix.service_display }} Image runs-on: runtime-builder @@ -1684,7 +1856,8 @@ jobs: - integration-tests - frontend-lint - frontend-unit-test - - build-pr + - build-pr-backend + - build-pr-web timeout-minutes: 3 steps: - name: Checkout code @@ -1711,7 +1884,8 @@ jobs: RESULT_INTEGRATION: ${{ needs.integration-tests.result }} RESULT_FRONTEND_LINT: ${{ needs.frontend-lint.result }} RESULT_FRONTEND_UNIT: ${{ needs.frontend-unit-test.result }} - RESULT_BUILD_PR: ${{ needs.build-pr.result }} + RESULT_BUILD_PR_BACKEND: ${{ needs.build-pr-backend.result }} + RESULT_BUILD_PR_WEB: ${{ needs.build-pr-web.result }} run: | set -eu echo "=== CI Gate 评估 ===" @@ -1725,7 +1899,8 @@ jobs: echo " integration-tests: $RESULT_INTEGRATION" echo " frontend-lint: $RESULT_FRONTEND_LINT" echo " frontend-unit-test: $RESULT_FRONTEND_UNIT" - echo " build-pr: $RESULT_BUILD_PR" + echo " build-pr-backend: $RESULT_BUILD_PR_BACKEND" + echo " build-pr-web: $RESULT_BUILD_PR_WEB" echo "" # 判断PR类型 @@ -1739,18 +1914,19 @@ jobs: "validate-code-quality:$RESULT_CODE_QUALITY" "validate-type-check:$RESULT_TYPE_CHECK" "validate-migration:$RESULT_MIGRATION" - "frontend-lint:$RESULT_FRONTEND_LINT" - "build-pr:$RESULT_BUILD_PR" ) # 后端检查 REQUIRED_BACKEND=( "unit-tests:$RESULT_UNIT_TESTS" + "build-pr-backend:$RESULT_BUILD_PR_BACKEND" ) # 前端检查 REQUIRED_FRONTEND=( + "frontend-lint:$RESULT_FRONTEND_LINT" "frontend-unit-test:$RESULT_FRONTEND_UNIT" + "build-pr-web:$RESULT_BUILD_PR_WEB" ) ALL_PASSED=true