全量代码检查方案

本指南面向在 GitCode 平台落地分支全量代码检查、并期望使用 openLiBing 实现问题可视化的团队。方案覆盖检查场景定义、openLiBing 结果可视化、按语言的工具选型、以及每个工具在 pre-commit 集成与工具单独使用(workflow)两种接入方式之间如何选择的对比分析;已落地的工具使用文档见 下级 tools 目录。


1. 方案背景与目标

1.1 三种检查场景

团队代码检查通常存在三种场景,本方案聚焦"分支全量检查":

场景 触发方式 现状 时限要求
本地检查 开发者提交前(git hook) 已通过 pre-commit 框架集成各类工具 ≤ 10 分钟(针对 pre-commit)
PR 检查 Pull Request 事件 已通过 pre-commit 框架集成 ≤ 10 分钟(针对 pre-commit)
分支全量 代码仓 action 流水线(push / 定时 / 手动) 本方案建设目标 原则上无整体硬性时限,尽量控制在小时级

时限语义:10 分钟时限仅针对本地/PR 场景的 pre-commit 执行;分支全量场景无整体硬性时限,目标是尽量控制在小时级、尽可能短,通过并行、缓存等手段优化。

1.2 本方案目标

  1. 覆盖 C / C++ / Python / Go / Java / Rust / JS / TS 等主流语言的静态检查与安全扫描;
  2. 分支全量检查依托 GitCode CI 流水线执行,支持 push、定时、手动等事件触发;
  3. 工具范围包括 pre-commit 内已集成的工具 + 因耗时/环境原因移出 pre-commit 的额外工具(接入方式的选择见 4.1 方案对比与选择指引);
  4. 各工具扫描结果统一转换为 SARIF 格式,上传 openLiBing 平台可视化;
  5. 同时适配 monorepo(单仓多语言) 与 多仓(各仓单语言) 两种形态。

2. 结果可视化:openLiBing 代码检查页面说明

所有工具(无论 pre-commit 集成还是 workflow 单独使用)的扫描结果统一归一为 SARIF v2.1.0 并上传 openLiBing 平台,最终在「开发管理 - 代码检查」模块按工具名区分展示。

为什么选 SARIF v2.1.0:SARIF(Static Analysis Results Interchange Format,静态分析结果交换格式)是由 OASIS 标准组织维护的开放标准,用于在不同静态分析工具与平台之间交换扫描结果。各工具原生输出格式各异(文本 / XML / 自定义 JSON 等),统一归一为 SARIF 后可获得:

  • 格式统一:openLiBing 只需实现一种解析器即可接入全部工具,避免逐工具定制适配;
  • 信息完整:结构化承载规则(rule)、级别(level)、位置(file / line / column)与 fingerprint 等字段,支持问题去重与追踪;
  • 生态互通:GitHub Code Scanning、VS Code 等主流平台原生支持 SARIF,结果可平滑复用与对接。

版本说明:SARIF v2.1.0 于 2020 年 3 月被批准为 OASIS 标准,是当前主流工具(SpotBugs、ESLint、gosec 等)及转换器(如 eslint-formatter-sarif、clang-tidy-sarif、clippy-sarif)默认输出/解析的基准版本;本方案(含 openLiBing 解析端)均以 v2.1.0 为契约基准。

官方文档:

2.1 查看问题列表

查看可视化看板

2.2 查看问题详情

查看问题详情

说明:openLiBing 页面为各工具共用,SpotBugs 等不同工具的结果均在该页面按工具名区分展示,页面操作方式一致。


3. 工具选型:不同语言的检查工具

3.1 语言 × 工具选型矩阵

下表为主流语言的推荐工具及其典型特征(前置条件、SARIF 能力),用于新仓库接入时的初始选型预判:

语言 工具 前置条件 SARIF 能力 使用文档
密钥 gitleaks 无 原生:--report-format sarif 使用文档
Python bandit pip 安装 原生:-f sarif 使用文档
Go gosec Go 工具链 原生:-fmt sarif 使用文档
JS/TS ESLint(含 typescript-eslint) npm 安装 转换器:@microsoft/eslint-formatter-sarif 使用文档
C/C++ clang-tidy compile_commands.json(cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON) 转换器:clang-tidy-sarif(psastras/sarif-rs) 使用文档
Rust rust-clippy Rust 工具链 + cargo clippy;clippy-sarif 转换器(cargo install clippy-sarif) 转换器:clippy-sarif(cargo clippy --message-format=json | clippy-sarif) 使用文档
Java SpotBugs 编译字节码(maven/gradle 构建先行) 原生:SARIF 输出参数 使用文档
多语言 CodeArts Check(云端服务) 华为云 CodeArts 项目与检查任务 + AK/SK Secrets 插件:CodeArts Check API 拉取问题明细转 SARIF 使用文档

📌 已落地的工具使用文档见 下级 tools 目录,其余工具文档陆续补充中。


4. 全量检查配置方案

4.1 方案对比与选择指引

本方案提供两种接入方式:pre-commit 集成(通过 .pre-commit-config.yaml 统一声明工具,本地提交前拦截,流水线可用 pre-commit-action 复用同一配置)与 workflow 单独使用(每个/每组工具配置独立 job,流水线直接扫描并上传)。两者不是二选一的对立关系,可并存互补:

维度 方案一:pre-commit 集成 方案二:workflow 单独使用
性能 ≤ 10 分钟时限,快速反馈 无整体硬性时限,可并行 + 缓存,目标小时级
运行环境 开发者本机需安装 pre-commit 及工具链;流水线执行机需能访问 hook 仓库(GitHub 或 GitCode 镜像)与工具依赖 统一在流水线执行机上运行,工具链通过 setup-* action 安装指定版本,环境可控
编译依赖 以轻量工具为主,需编译的工具仅小仓库可容纳 可承载需编译产物 / 构建的工具(clang-tidy、SpotBugs、rust-clippy)
适合工具 轻量、支持按文件扫描的工具(gitleaks、bandit、gosec、ESLint 等) 需编译产物 / 构建 / 耗时长而移出 pre-commit 的工具

选择指引:核心判定依据是该工具在你仓库中的实测检查耗时与环境依赖:

  1. 耗时预算:本地/PR 场景 pre-commit 总时限 10 分钟。某工具全量执行明显挤占预算(单工具超过总预算 1/3,或拖累整体逼近 10 分钟)→ 移出 pre-commit,改为 workflow 单独执行;
  2. 环境依赖:工具链版本、编译数据库等条件在本机 / 流水线执行机无法稳定满足,pre-commit 内无法可靠运行 → 改为 workflow 单独执行;
  3. 其余能在时限内稳定完成的工具 → 留在 pre-commit。

💡 接入方式归属是动态的,可随仓库规模变化迁移:调整 .pre-commit-config.yaml(移入/移出钩子)+ 增删对应流水线 job 即可。迁移不改变全量检查范围——无论工具走哪种接入方式,其结果都应归一为 SARIF 上传 openLiBing,避免遗漏。

4.2 方案一:pre-commit 集成(全量配置样例)

通过 .pre-commit-config.yaml 统一声明检查工具,本地 git commit 前触发拦截;在流水线中由 pre-commit-action 插件执行同一套配置(非 PR 事件检查全量)并生成/收集 SARIF,再由 upload-sarif-action 插件上传 openLiBing。

完整配置见 pre-commit 落地指南。

4.2.1 全量 .pre-commit-config.yaml 样例(含全部工具,按需删减)

以下是一份包含本方案全部 7 个工具的完整 .pre-commit-config.yaml。接入时按你的仓库语言保留对应 repos/hooks 段,删除其余即可;也可在保留段内微调参数。各工具的参数说明与 FAQ 见对应工具文档。

# .pre-commit-config.yaml —— 全量代码检查(方案一:pre-commit 集成)
# 用法:按仓库语言保留需要的 hooks 段,删除其余;每段顶部注释标明适用语言与依赖
# 通用钩子(trailing-whitespace 等)已由 GitCode CI 流水线自带或本地 git hook 覆盖,无需重复配置
repos:
  # ===================== 敏感信息扫描(全语言) =====================
  # gitleaks:检测硬编码密钥、Token、密码等敏感信息(GitCode 自建仓 wrapper 自动下载二进制,无需装 Go)
  - repo: https://gitcode.com/GitHub_Trending/gi/gitleaks
    rev: v8.30.1
    hooks:
      - id: gitleaks

  # ===================== Python =====================
  # bandit:Python 安全扫描(检出所有级别问题;正式启用可收紧为 args: ["-ll"])
  - repo: https://gitcode.com/gh_mirrors/ba/bandit
    rev: 1.9.4
    hooks:
      - id: bandit

  # ===================== Go =====================
  # gosec:Go 安全扫描(local hook,依赖流水线/本机预装 .gitcode/bin/gosec)
  - repo: local
    hooks:
      - id: gosec
        name: gosec (sarif report)
        entry: .gitcode/bin/gosec
        language: system
        types: [go]
        pass_filenames: false
        require_serial: true
        args:
          - -conf=gosec.json
          - -fmt=sarif
          - -out=/tmp/pre-commit-reports/gosec.sarif
          - -exclude-dir=vendor
          - -exclude-generated
          - ./...

  # ===================== JS/TS =====================
  # ESLint:JS/TS Lint(local hook,依赖 node_modules 已安装 + SARIF formatter)
  - repo: local
    hooks:
      - id: eslint
        name: eslint (sarif report)
        entry: ./node_modules/.bin/eslint
        language: system
        files: ^src/.*\.(js|ts)$
        require_serial: true
        args:
          - --format
          - "@microsoft/eslint-formatter-sarif"
          - --output-file
          - /tmp/pre-commit-reports/eslint.sarif
          - --no-error-on-unmatched-pattern

  # ===================== C/C++ =====================
  # clang-tidy:C/C++ 静态检查(local hook,依赖本机 clang-tidy + build/compile_commands.json + scripts/run-clang-tidy.py)
  - repo: local
    hooks:
      - id: clang-tidy
        name: clang-tidy check
        language: system
        entry: python3 scripts/run-clang-tidy.py
        pass_filenames: false
        types_or: [c, c++]

  # ===================== Rust =====================
  # cargo-clippy:Rust 静态检查(local hook,扫描与 SARIF 转换内联在 entry,依赖本机 Rust 工具链 + clippy-sarif)
  # - 两次执行 cargo clippy:第一次人类可读输出 + 捕获退出码 rc 决定拦截;第二次 --message-format=json | clippy-sarif 生成 SARIF
  - repo: local
    hooks:
      - id: cargo-clippy
        name: cargo-clippy check
        language: system
        entry: bash -c 'set -o pipefail; mkdir -p /tmp/clippy-result; cargo clippy ; rc=$?; cargo clippy --message-format=json  | clippy-sarif > /tmp/clippy-result/clippy-results.sarif; exit $rc'
        pass_filenames: false # cargo 不接受文件名参数,只在工作区根目录整体运行
        files: '(^|/)(Cargo\.toml|Cargo\.lock|[^/]+\.rs)$' # 仅 Rust 相关文件变更时触发
        require_serial: true # 串行运行,避免 cargo 锁冲突
        verbose: true # 通过时也输出人类可读的 clippy 结果,否则 pre-commit 只显示 Passed

  # ===================== Java =====================
  # SpotBugs:Java 字节码分析(local hook,依赖 mvn + pom.xml 插件配置,见工具文档 2.3)
  - repo: local
    hooks:
      - id: mvn-spotbugs
        name: Maven SpotBugs Check
        language: system
        entry: mvn compile spotbugs:check
        pass_filenames: false

📌 工具文档中「场景一 .pre-commit-config.yaml 配置」小节给出的是单工具片段,本文档 4.2.1 是全量合并版;两者任一形式都可直接使用。各工具的规则配置文件(gosec.json / .clang-tidy / .gitleaks.toml / eslint.config.mjs 等)仍需按工具文档放置在仓库对应位置。

4.2.2 配套流水线样例(pre-commit-action 执行全量钩子 + upload-sarif-action 上传)

本地提交前由 pre-commit 拦截;如需在流水线中执行同一份配置并上传 SARIF,分两步:先由 openlibing/pre-commit-action@v1.0.0 执行钩子并生成/收集 SARIF(输出 sarif-files),再由 openlibing/upload-sarif-action@v1.0.0 读取报告上传 OBS + 回调 openLiBing:

name: pre-commit-report
on:
  workflow_dispatch:
  push:
    branches: ["main"]

permissions:
  repository: read
  id-token: write # pre-commit-action 插件 OIDC 免密换证必需

# 工具链版本统一固定,保证 CI 环境可复现(cargo-clippy 钩子依赖)
env:
  RUST_TOOLCHAIN: "1.98.1" # 含 rustc / cargo / clippy(clippy 随工具链版本发布)
  CLIPPY_SARIF: "0.8.0" # clippy JSON -> SARIF 转换工具

jobs:
  pre-commit-report:
    name: pre-commit-report
    runs-on: ["codearts-hosted", "ubuntu-latest", "x64", "slim"]
    steps:
      - name: Checkout repository
        uses: checkout

      - name: setup-python
        uses: setup-python
        with:
          version: 3.14
          cache: true

      # ---- 工具链准备:与 .pre-commit-config.yaml 保留的钩子一一对应,按需删减 ----
      # gosec:安装到仓库 .gitcode/bin/
      - name: setup-go
        uses: setup-go
        with:
          version: "1.23"
          cache: true
      - name: Install gosec
        env:
          GOPROXY: https://goproxy.cn,https://goproxy.io,direct
          GOSUMDB: sum.golang.google.cn
        run: |
          mkdir -p .gitcode/bin /tmp/pre-commit-reports/
          GOBIN="$(pwd)/.gitcode/bin" go install github.com/securego/gosec/v2/cmd/gosec@v2.22.4
          chmod +x .gitcode/bin/gosec
          .gitcode/bin/gosec -version

      # ESLint:装 node 依赖 + SARIF formatter
      - name: setup-node
        uses: setup-node
        with:
          node-version: "20"
          cache: true
      - name: Install npm dependencies
        run: npm install
      - name: Install SARIF formatter
        run: npm install --no-save --no-package-lock @microsoft/eslint-formatter-sarif@3.1.0

      # clang-tidy:安装工具 + 生成 compile_commands.json
      - name: Install clang-tidy
        run: |
          sudo apt-get update
          sudo apt-get install -y clang-tidy
          clang-tidy --version
      - name: Build CMake compile_commands.json
        run: |
          cmake -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_BUILD_TYPE=Release
          test -f build/compile_commands.json || { echo "ERROR: compile_commands.json not generated"; exit 1; }

      # cargo-clippy:安装 Rust 工具链 + clippy 组件 + clippy-sarif(版本固定,幂等)
      - name: setup-rust
        run: |
          export RUSTUP_DIST_SERVER="https://rsproxy.cn"
          export RUSTUP_UPDATE_ROOT="https://rsproxy.cn/rustup"
          if ! command -v rustup >/dev/null 2>&1 && [ ! -x "$HOME/.cargo/bin/rustup" ]; then
            echo "rustup not found, installing..."
            curl --proto '=https' --tlsv1.2 -sSf https://rsproxy.cn/rustup-init.sh | \
              sh -s -- -y --default-toolchain "$RUST_TOOLCHAIN"
          else
            echo "rustup already exists, ensure toolchain $RUST_TOOLCHAIN ..."
            rustup toolchain install "$RUST_TOOLCHAIN" --no-self-update
            rustup default "$RUST_TOOLCHAIN"
          fi
          . "$HOME/.cargo/env"
          rustup --version && cargo --version && rustc --version
          echo "$HOME/.cargo/bin" >> "$ATOMGIT_PATH"
      - name: install clippy component
        run: |
          . "$HOME/.cargo/env"
          export RUSTUP_DIST_SERVER="https://rsproxy.cn"
          export RUSTUP_UPDATE_ROOT="https://rsproxy.cn/rustup"
          if rustup component list --installed --toolchain "$RUST_TOOLCHAIN" | grep -q "^clippy"; then
            echo "clippy component already installed, skip"
          else
            rustup component add clippy --toolchain "$RUST_TOOLCHAIN"
          fi
      - name: install clippy-sarif
        run: |
          . "$HOME/.cargo/env"
          mkdir -p ~/.cargo
          cat > ~/.cargo/config.toml <<'EOF'
          [source.crates-io]
          replace-with = "rsproxy-sparse"
          [source.rsproxy-sparse]
          registry = "sparse+https://rsproxy.cn/index/"
          EOF
          if command -v clippy-sarif >/dev/null 2>&1; then
            echo "clippy-sarif already installed, skip"
          else
            cargo install clippy-sarif --version "$CLIPPY_SARIF" --locked
          fi

      # SpotBugs:JDK + Maven 环境(pom.xml 已声明 spotbugs 插件配置)
      - name: setup-jdk
        uses: setup-jdk
        with:
          jdk-version: 21
          maven-version: "3.8.9"

      # 步骤 1:运行 pre-commit(读取 .pre-commit-config.yaml 执行全部钩子,生成/收集 SARIF)
      - name: run pre-commit (generate sarif)
        id: pre-commit
        uses: openlibing/pre-commit-action@v1.0.0
        with:
          report-dir: /tmp/pre-commit-reports/

      # 步骤 2:上传 SARIF 并回调 openLiBing(OIDC 免密换证)
      - name: upload sarif reports
        if: ${{ always() }}
        uses: openlibing/upload-sarif-action@v1.0.0
        with:
          sarif_file: ${{ steps.pre-commit.outputs.sarif-files }}
          category: "pre-commit-report"

📌 各钩子的 SARIF 产出约定:gitleaks/bandit 由插件日志兜底解析生成 <tool>.sarif;gosec/ESLint 通过钩子 args 直接输出到 report-dir;clang-tidy 由 wrapper 脚本输出;cargo-clippy 由钩子 entry 内联的 clippy-sarif 输出到 /tmp/clippy-result/;SpotBugs 由 pom.xml 配置输出。PR 类型触发只做检查、不上传报告。

4.3 方案二:workflow 集成(全量配置样例)

在 .gitcode/workflows/ 中为每个(或每组)工具配置独立 job,流水线直接执行扫描命令并产出 SARIF,由 openlibing/upload-sarif-action@v1.0.0 插件统一上传 OBS 并回调 openLiBing。

  • 执行时机:流水线(push / 定时 / 手动);
  • 检查范围:全量;
  • 性能:无整体硬性时限,可并行 + 缓存,目标小时级;需构建的工具(clang-tidy、SpotBugs、clippy)耗时是主要长板;
  • 环境支持:统一在流水线执行机上运行,工具链通过 setup-* action 安装指定版本,环境可控;
  • 适合工具:需编译产物 / 构建 / 耗时长而移出 pre-commit 的工具;
  • 结果去向:SARIF 上传 OBS + 回调 openLiBing 可视化。

示例见 SpotBugs workflow 集成 等工具文档。

4.3.1 全量 workflow 样例(含全部工具 job,按需删减)

以下是一份包含全部 7 个工具 job 的完整 workflow(一个文件、多 job 并行)。接入时按仓库语言保留需要的 job,删除其余即可。每个 job 内也可单独调整触发事件、扫描参数、上传 category。各工具 job 的步骤详解见对应工具文档「场景二」章节。

name: full-code-analysis
on:
  workflow_dispatch: # 支持在流水线页面手动触发(功能验证用)
  push:
    branches: ["main"] # 默认分支推送触发
  pull_request_target:
    branches: ["main"] # 防 fork PR 篡改 workflow

permissions:
  repository: read
  id-token: write # openlibing/upload-sarif-action@v1.0.0 插件 OIDC 免密换证必需

jobs:
  # ===================== 敏感信息扫描(全语言) =====================
  gitleaks-scan:
    name: Gitleaks 扫描
    runs-on: ["codearts-hosted", "ubuntu-latest", "x64", "slim"]
    steps:
      - name: Checkout
        uses: checkout
        with:
          ref: ${{ atomgit.event.pull_request.merge_commit_sha || atomgit.event.pull_request.head.sha || atomgit.ref }}
      - name: setup-go
        uses: setup-go
        with:
          version: 1.26
          cache: true
      # 从 GitCode 镜像仓克隆 gitleaks 源码并编译安装(已安装则跳过)
      - name: Install gitleaks
        run: |
          if command -v gitleaks &> /dev/null; then
            echo "gitleaks already installed, skip build"
          else
            if [ ! -d /tmp/gitleaks-src ]; then
              git clone https://gitcode.com/GitHub_Trending/gi/gitleaks.git /tmp/gitleaks-src
            else
              echo "/tmp/gitleaks-src already exists, skip clone"
            fi
            cd /tmp/gitleaks-src
            go build -o gitleaks ./cmd/gitleaks
            chmod +x gitleaks
            mv gitleaks /usr/local/bin/gitleaks
          fi
          gitleaks version
      # 扫描:原生输出 SARIF v2.1.0;检出敏感信息不阻断(失败语义三分离)
      - name: 运行 Gitleaks 扫描
        run: |
          mkdir -p sarif
          gitleaks dir . --verbose \
            --report-format sarif \
            --report-path sarif/gitleaks.sarif \
            || echo "gitleaks found leaks, continuing to upload SARIF"
      - name: Upload SARIF to openLiBing
        if: ${{ always() }}
        uses: openlibing/upload-sarif-action@v1.0.0
        with:
          sarif_file: sarif/gitleaks.sarif
          source_path: .
          category: "gitleaks-scan"

  # ===================== Python =====================
  bandit-scan:
    name: Bandit 扫描
    runs-on: ["codearts-hosted", "ubuntu-latest", "x64", "slim"]
    steps:
      - name: Checkout repository
        uses: checkout
        with:
          token: ${{ secrets.ROBOT_TOKEN }}
      - name: Install bandit
        run: python3 -m pip install "bandit[sarif]" # sarif extra 支持 SARIF 报告格式
      # 扫描:不加 -ll 检出所有级别;正式启用可收紧为 -ll(仅 MEDIUM 及以上)
      - name: Run bandit scan
        run: |
          rc=0
          bandit -r . -x .gitcode -f sarif -o bandit-report.sarif || rc=$?
          if [ "$rc" -ne 0 ]; then
            echo "[bandit] scan found issues (exit code $rc); pipeline continues (non-blocking)"
          fi
          exit 0
      - name: Upload SARIF
        if: ${{ always() }}
        uses: openlibing/upload-sarif-action@v1.0.0
        with:
          sarif_file: bandit-report.sarif
          category: "bandit"

  # ===================== Go =====================
  gosec-scan:
    name: Gosec 扫描
    runs-on: ["codearts-hosted", "ubuntu-latest", "x64", "slim"]
    steps:
      - name: Checkout
        uses: checkout
        with:
          ref: ${{ atomgit.event.pull_request.merge_commit_sha || atomgit.event.pull_request.head.sha || atomgit.ref }}
      - name: setup-go
        uses: setup-go
        with:
          version: "1.23"
          cache: true
      # 安装与扫描同一步,避免 go install 的 PATH 修改无法跨 step 继承
      - name: 安装 gosec 并扫描
        env:
          GOPROXY: https://goproxy.cn,https://goproxy.io,direct
          GOSUMDB: sum.golang.google.cn
        run: |
          export PATH="$PATH:$(go env GOPATH)/bin"
          go install github.com/securego/gosec/v2/cmd/gosec@v2.22.4
          gosec -version
          mkdir -p reports
          gosec -no-fail -conf gosec.json -fmt sarif -out reports/gosec.sarif \
            -exclude-dir vendor -exclude-generated ./...
      - name: Upload SARIF to openLiBing
        if: ${{ always() }}
        uses: openlibing/upload-sarif-action@v1.0.0
        with:
          sarif_file: reports/gosec.sarif
          source_path: .
          category: "gosec-scan"

  # ===================== JS/TS =====================
  eslint-scan:
    name: ESLint 扫描
    runs-on: ["codearts-hosted", "ubuntu-latest", "x64", "slim"]
    steps:
      - name: Checkout
        uses: checkout
        with:
          ref: ${{ atomgit.event.pull_request.merge_commit_sha || atomgit.event.pull_request.head.sha || atomgit.ref }}
      - name: setup-node
        uses: setup-node
        with:
          node-version: "20"
          cache: true
      # 安装仓库依赖 + 单独安装 SARIF formatter(不写 package.json,避免业务仓引入 lint 工具依赖)
      - name: Install dependencies
        run: npm install
      - name: Install SARIF formatter
        run: npm install --no-save --no-package-lock @microsoft/eslint-formatter-sarif@3.1.0
      # ESLint 扫描:formatter 输出 SARIF v2.1.0;检出违规不阻断(失败语义三分离)
      - name: 运行 ESLint 扫描
        run: |
          mkdir -p reports
          npx eslint src/ \
            --format @microsoft/eslint-formatter-sarif \
            --output-file reports/eslint.sarif \
            --no-error-on-unmatched-pattern || true
      - name: Upload SARIF to openLiBing
        if: ${{ always() }}
        uses: openlibing/upload-sarif-action@v1.0.0
        with:
          sarif_file: reports/eslint.sarif
          source_path: src # 源码根目录,用于补齐代码片段
          category: "eslint-scan"

  # ===================== C/C++ =====================
  clang-tidy-scan:
    name: clang-tidy 扫描
    runs-on: ["codearts-hosted", "ubuntu-latest", "x64", "slim"]
    steps:
      - name: Checkout
        uses: checkout
        with:
          ref: ${{ atomgit.event.pull_request.merge_commit_sha || atomgit.event.pull_request.head.sha || atomgit.ref }}
      - name: Install clang-tidy
        run: |
          sudo apt-get update
          sudo apt-get install -y clang-tidy
          clang-tidy --version
      # 预置二进制 clang-tidy-sarif 随本仓 .gitcode/bin/ 提交(见工具文档 §5.1)
      - name: Ensure clang-tidy-sarif executable
        run: |
          chmod +x .gitcode/bin/clang-tidy-sarif
          .gitcode/bin/clang-tidy-sarif --version || true
      # 仅配置阶段产出 compile_commands.json,不执行编译(节省时间)
      - name: Build CMake compile_commands.json
        run: |
          cmake -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_BUILD_TYPE=Release
          test -f build/compile_commands.json || { echo "ERROR: compile_commands.json not generated"; exit 1; }
      # 全量扫描 + 转换 SARIF(wrapper 脚本提取 compile_commands.json 全部源文件)
      - name: clang-tidy scan and convert to SARIF
        run: |
          mkdir -p sarif
          python3 scripts/run-clang-tidy.py \
            --_cdb build/compile_commands.json \
            --_clang-tidy-sarif .gitcode/bin/clang-tidy-sarif \
            --_sarif-output sarif/clang-tidy.sarif \
            || echo "clang-tidy exited non-zero (issues found), continuing"
          test -s sarif/clang-tidy.sarif || { echo "ERROR: SARIF empty"; exit 1; }
      - name: Upload SARIF to openLiBing
        if: ${{ always() }}
        uses: openlibing/upload-sarif-action@v1.0.0
        with:
          sarif_file: sarif/clang-tidy.sarif
          source_path: .
          category: "clang-tidy-scan"

  # ===================== Rust =====================
  cargo-clippy-scan:
    name: cargo-clippy 扫描
    runs-on: ["codearts-hosted", "ubuntu-latest", "x64", "slim"]
    # 工具链版本统一固定,保证 CI 环境可复现(clippy 随工具链版本发布)
    env:
      RUST_TOOLCHAIN: "1.98.1"
      CLIPPY_SARIF: "0.8.0"
    steps:
      - name: Checkout
        uses: checkout
        with:
          ref: ${{ atomgit.event.pull_request.merge_commit_sha || atomgit.event.pull_request.head.sha || atomgit.ref }}
      # 安装 Rust 工具链:RsProxy 国内镜像加速,版本由 env.RUST_TOOLCHAIN 固定,幂等
      - name: setup-rust
        run: |
          export RUSTUP_DIST_SERVER="https://rsproxy.cn"
          export RUSTUP_UPDATE_ROOT="https://rsproxy.cn/rustup"
          if ! command -v rustup >/dev/null 2>&1 && [ ! -x "$HOME/.cargo/bin/rustup" ]; then
            echo "rustup not found, installing..."
            curl --proto '=https' --tlsv1.2 -sSf https://rsproxy.cn/rustup-init.sh | \
              sh -s -- -y --default-toolchain "$RUST_TOOLCHAIN"
          else
            echo "rustup already exists, ensure toolchain $RUST_TOOLCHAIN ..."
            rustup toolchain install "$RUST_TOOLCHAIN" --no-self-update
            rustup default "$RUST_TOOLCHAIN"
          fi
          . "$HOME/.cargo/env"
          rustup --version && cargo --version && rustc --version
          echo "$HOME/.cargo/bin" >> "$ATOMGIT_PATH"
      - name: install clippy component
        run: |
          . "$HOME/.cargo/env"
          export RUSTUP_DIST_SERVER="https://rsproxy.cn"
          export RUSTUP_UPDATE_ROOT="https://rsproxy.cn/rustup"
          if rustup component list --installed --toolchain "$RUST_TOOLCHAIN" | grep -q "^clippy"; then
            echo "clippy component already installed, skip"
          else
            rustup component add clippy --toolchain "$RUST_TOOLCHAIN"
          fi
      - name: install clippy-sarif
        run: |
          . "$HOME/.cargo/env"
          mkdir -p ~/.cargo
          cat > ~/.cargo/config.toml <<'EOF'
          [source.crates-io]
          replace-with = "rsproxy-sparse"
          [source.rsproxy-sparse]
          registry = "sparse+https://rsproxy.cn/index/"
          EOF
          if command -v clippy-sarif >/dev/null 2>&1; then
            echo "clippy-sarif already installed, skip"
          else
            cargo install clippy-sarif --version "$CLIPPY_SARIF" --locked
          fi
      # 全量扫描 + 转换 SARIF:cargo clippy JSON 诊断流 pipe 给 clippy-sarif(只出报告不拦截)
      - name: cargo-clippy scan and convert to SARIF
        run: |
          mkdir -p /tmp/clippy-result
          cargo clippy --message-format=json | clippy-sarif > /tmp/clippy-result/clippy-results.sarif
          cat /tmp/clippy-result/clippy-results.sarif
      - name: Upload SARIF to openLiBing
        if: ${{ always() }}
        uses: openlibing/upload-sarif-action@v1.0.0
        with:
          sarif_file: /tmp/clippy-result/clippy-results.sarif
          source_path: src # 源码根目录,用于补齐代码片段(workspace 多 crate 建议传仓库根 .)
          category: "cargo-clippy-scan"

  # ===================== Java =====================
  spotbugs-scan:
    name: SpotBugs 扫描
    runs-on: ["codearts-hosted", "ubuntu-latest", "x64", "slim"]
    steps:
      - name: Checkout
        uses: checkout
      - name: setup-jdk
        uses: setup-jdk
        with:
          jdk-version: 21
          maven-version: "3.8.9"
      # SpotBugs 分析字节码,需先编译产出 target/classes
      - name: 编译
        run: mvn -B clean package -DskipTests
      # SpotBugs 扫描:版本/规则过滤/SARIF 输出等配置在目标仓 pom.xml 声明(单一事实源)
      - name: SpotBugs 扫描
        run: mvn -B spotbugs:spotbugs
      # 上传 SARIF:if: always() 保证扫描失败也上传(只要有 SARIF 文件)
      - name: upload sarif to openlibing
        if: ${{ always() }}
        uses: openlibing/upload-sarif-action@v1.0.0
        with:
          sarif_file: target/reports/spotbugs.sarif
          source_path: src/main/java
          category: "business"

📌 工具文档中「场景二 完整 workflow 示例」小节给出的是单工具 job,本文档 4.3.1 是全部 job 合并版。多 job 在同一 workflow 内并行执行;如需为某工具配置独立触发/定时,可拆分为单独 workflow 文件。category 建议按工具固定(如 gitleaks-scan),便于平台按扫描范围区分结果。


5. 目录结构

solutions/
├── full-analysis/           <- 本方案:分支全量代码检查(总览 + 工具落地文档)
│   ├── README.md            <- 方案总览(背景 / 页面说明 / 工具选型 / 方案择优 / 全量配置样例)
│   ├── images/              <- 页面说明截图
│   └── tools/               <- 本方案工具的使用文档(配置样例、参数说明、demo)
│       ├── bandit/          <- Bandit 代码检查指南(pre-commit + workflow 两种用法)
│       ├── cargo-clippy/    <- cargo-clippy 代码检查指南(pre-commit + workflow 两种用法)
│       ├── clang-tidy/      <- clang-tidy 代码检查指南(pre-commit + workflow 两种用法)
│       ├── codearts-check/  <- CodeArts Check 代码检查指南(华为云云端托管服务,仅 workflow 集成)
│       ├── eslint/          <- ESLint 代码检查指南(pre-commit + workflow 两种用法)
│       ├── gitleaks/        <- Gitleaks 敏感信息扫描指南(pre-commit + workflow 两种用法)
│       ├── gosec/           <- Gosec 代码检查指南(pre-commit + workflow 两种用法)
│       └── spotbugs/        <- SpotBugs 代码检查指南(pre-commit + workflow 两种用法)
├── pre-commit/              <- 独立方案:pre-commit 落地指南(方案一核心载体,不在本方案内)