# ============================================================================= # libsmx CI/CD 工作流 # ============================================================================= # 触发条件: # - main 分支的 push / PR → lint + test + coverage # - v* tag 推送 → 上述全部 + 自动发布 # - PR → 额外运行 benchmark 并评论结果 # ============================================================================= name: CI on: push: branches: [main] tags: ["v*"] pull_request: branches: [main] # 同一 PR 的新提交自动取消旧的运行,节省 CI 资源 concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true env: CARGO_TERM_COLOR: always # 强制使用 sparse 协议,加速 crates.io 索引同步 CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse # ============================================================================= # Job 1: 代码质量检查(格式化 + Clippy + 依赖漏洞扫描) # ============================================================================= jobs: lint: name: Lint & Audit runs-on: ubuntu-latest steps: - name: 签出代码 uses: actions/checkout@v4 - name: 安装 Rust 工具链(stable + rustfmt + clippy) uses: dtolnay/rust-toolchain@stable with: components: rustfmt, clippy - name: 缓存 Cargo 编译产物 uses: Swatinem/rust-cache@v2 - name: 检查代码格式 run: cargo fmt --check - name: Clippy 检查(默认 features,所有警告视为错误) run: cargo clippy --all-targets -- -D warnings - name: Clippy 检查(no_std,无 alloc) # Reason: alloc-gated 函数在 no_std 模式下会产生 "unused" 假阳性, # 因此只做编译检查,不启用 -D warnings run: cargo check --no-default-features - name: 安装 cargo-audit run: cargo install cargo-audit --locked - name: 依赖漏洞扫描(CVE 检查) run: cargo audit - name: 安全性扫描(panic/unwrap 检查) run: bash .github/scripts/sanity_check.sh shell: bash # ============================================================================= # Job 2: 多平台测试矩阵 # ============================================================================= test-matrix: name: Test (${{ matrix.os }}) needs: lint strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} steps: - name: 签出代码 uses: actions/checkout@v4 - name: 安装 Rust 工具链(stable) uses: dtolnay/rust-toolchain@stable - name: 缓存 Cargo 编译产物 uses: Swatinem/rust-cache@v2 # Reason: 在所有平台上显式设置 bash 以统一 Windows/Linux/macOS 行为 - name: 运行测试(默认 features,全 workspace) run: cargo test --workspace --all-targets shell: bash - name: 运行测试(no_std,无 alloc) # Reason: 验证嵌入式 / WASM / 裸机兼容性 run: cargo test --workspace --no-default-features --lib shell: bash - name: 运行文档测试 run: cargo test --doc shell: bash - name: 构建文档(警告视为错误) run: cargo doc --no-deps env: RUSTDOCFLAGS: "-D warnings" shell: bash # ============================================================================= # Job 3: MSRV 验证(最低支持版本) # ============================================================================= msrv: name: MSRV (1.83.0) needs: lint runs-on: ubuntu-latest steps: - name: 签出代码 uses: actions/checkout@v4 - name: 安装 MSRV 工具链 uses: dtolnay/rust-toolchain@master with: toolchain: "1.83.0" - name: 缓存 Cargo 编译产物 uses: Swatinem/rust-cache@v2 # Reason: MSRV 只验证能编译,不运行测试(避免 dev-dependencies 兼容问题) - name: 检查编译(默认 features) run: cargo check --workspace - name: 检查编译(no_std) run: cargo check --workspace --no-default-features # ============================================================================= # Job 4: no_std 跨目标编译验证 # ============================================================================= no-std: name: no_std (${{ matrix.target }}) needs: lint runs-on: ubuntu-latest strategy: matrix: target: - thumbv7em-none-eabi # ARM Cortex-M4,典型嵌入式目标 - wasm32-unknown-unknown # WebAssembly steps: - name: 签出代码 uses: actions/checkout@v4 - name: 安装 Rust 工具链(stable + 目标平台) uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.target }} - name: 缓存 Cargo 编译产物 uses: Swatinem/rust-cache@v2 # Reason: --no-default-features 禁用 alloc,验证裸机 no_std 兼容性 - name: 检查 sm3(no_std,无 alloc) run: cargo check -p sm3 --target ${{ matrix.target }} --no-default-features - name: 检查 sm4(no_std,无 alloc) run: cargo check -p sm4 --target ${{ matrix.target }} --no-default-features - name: 检查 sm2(no_std,无 alloc) run: cargo check -p sm2 --target ${{ matrix.target }} --no-default-features # ============================================================================= # Job 5: Feature Powerset(验证所有 feature 组合均可编译) # ============================================================================= feature-powerset: name: Feature Powerset needs: lint runs-on: ubuntu-latest steps: - name: 签出代码 uses: actions/checkout@v4 - name: 安装 Rust 工具链(stable) uses: dtolnay/rust-toolchain@stable - name: 缓存 Cargo 编译产物 uses: Swatinem/rust-cache@v2 - name: 安装 cargo-hack uses: taiki-e/install-action@cargo-hack # Reason: 验证所有 feature 组合均能编译,防止 feature 互斥或遗漏依赖 - name: 检查 sm3 feature powerset run: cargo hack check -p sm3 --feature-powerset --no-dev-deps - name: 检查 sm4 feature powerset run: cargo hack check -p sm4 --feature-powerset --no-dev-deps - name: 检查 sm2 feature powerset run: cargo hack check -p sm2 --feature-powerset --no-dev-deps # ============================================================================= # Job 6: Miri 内存安全验证 # ============================================================================= miri: name: Miri needs: lint runs-on: ubuntu-latest steps: - name: 签出代码 uses: actions/checkout@v4 - name: 安装 Rust nightly + Miri 组件 uses: dtolnay/rust-toolchain@nightly with: components: miri - name: 缓存 Cargo 编译产物 uses: Swatinem/rust-cache@v2 # Reason: 只对纯算法 crate(sm3/sm4/sm2)运行 Miri, # 避免对系统调用密集的代码产生误报。 # -Zmiri-strict-provenance 开启严格指针来源检查。 - name: Miri 检查 sm3 run: cargo miri test -p sm3 env: MIRIFLAGS: "-Zmiri-strict-provenance" - name: Miri 检查 sm4 run: cargo miri test -p sm4 env: MIRIFLAGS: "-Zmiri-strict-provenance" - name: Miri 检查 sm2(仅单元测试,跳过 alloc 密集测试) run: cargo miri test -p sm2 --lib env: MIRIFLAGS: "-Zmiri-strict-provenance" # ============================================================================= # Job 7: 测试覆盖率(仅 Linux,上传 Codecov) # ============================================================================= coverage: name: Coverage needs: lint runs-on: ubuntu-latest steps: - name: 签出代码 uses: actions/checkout@v4 - name: 安装 Rust 工具链(stable) uses: dtolnay/rust-toolchain@stable - name: 缓存 Cargo 编译产物 uses: Swatinem/rust-cache@v2 - name: 安装 cargo-tarpaulin run: cargo install cargo-tarpaulin --locked - name: 生成覆盖率报告 run: cargo tarpaulin --out xml --output-dir coverage/ - name: 上传覆盖率到 Codecov uses: codecov/codecov-action@v4 with: files: coverage/cobertura.xml token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: false # Reason: fail_ci_if_error=false 避免 Codecov 暂时不可用时阻断 CI # ============================================================================= # Job 8: 性能回归检测(仅 PR 触发) # ============================================================================= benchmark: name: Benchmark if: github.event_name == 'pull_request' needs: lint runs-on: ubuntu-latest permissions: pull-requests: write steps: - name: 签出 PR 代码 uses: actions/checkout@v4 - name: 安装 Rust 工具链(stable) uses: dtolnay/rust-toolchain@stable - name: 缓存 Cargo 编译产物 uses: Swatinem/rust-cache@v2 # Reason: 先运行当前 PR 分支的 benchmark 并保存结果 - name: 运行 Benchmark run: cargo bench --bench sm3_bench --bench sm4_bench --bench sm2_bench -- --output-format bencher 2>&1 | tee bench_output.txt shell: bash # Reason: 将 benchmark 结果作为 PR 评论发布,方便审查性能变化 - name: 发布 Benchmark 结果到 PR uses: actions/github-script@v7 with: script: | const fs = require('fs'); const benchOutput = fs.readFileSync('bench_output.txt', 'utf8'); const body = `## Benchmark Results\n\n
\nClick to expand\n\n\`\`\`\n${benchOutput}\n\`\`\`\n\n
\n\n> Benchmarks ran on \`ubuntu-latest\`. Results may vary across runs.`; await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, body: body }); # ============================================================================= # Job 6: 自动发布到 crates.io(仅 v* Tag 触发) # ============================================================================= release: name: Release if: startsWith(github.ref, 'refs/tags/v') needs: [test-matrix, msrv] runs-on: ubuntu-latest permissions: contents: write steps: - name: 签出代码 uses: actions/checkout@v4 with: fetch-depth: 0 - name: 安装 Rust 工具链(stable) uses: dtolnay/rust-toolchain@stable - name: 缓存 Cargo 编译产物 uses: Swatinem/rust-cache@v2 # Reason: 确保 Git Tag 版本号与 Cargo.toml 中的 version 字段完全一致 - name: 验证 Tag 与 Cargo.toml 版本匹配 run: | TAG_VERSION="${GITHUB_REF_NAME#v}" CARGO_VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)".*/\1/') if [ "$TAG_VERSION" != "$CARGO_VERSION" ]; then echo "::error::Tag version ($TAG_VERSION) does not match Cargo.toml version ($CARGO_VERSION)" exit 1 fi echo "Version: $TAG_VERSION ✓" shell: bash # Reason: 发布前最后一轮完整测试,防止 tag 推送后才发现问题 - name: 发布前完整测试 run: cargo test --all-targets - name: 发布到 crates.io run: cargo publish env: CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} # Reason: 从 CHANGELOG.md 中提取当前版本的发布说明 - name: 提取 Changelog 条目 id: changelog run: | TAG_VERSION="${GITHUB_REF_NAME#v}" # 提取 ## [x.y.z] 到下一个 ## 之间的内容 NOTES=$(awk "/^## \\[${TAG_VERSION}\\]/{found=1; next} /^## \\[/{if(found) exit} found{print}" CHANGELOG.md) if [ -z "$NOTES" ]; then NOTES="Release v${TAG_VERSION}" fi # 写入多行输出 echo "notes<> $GITHUB_OUTPUT echo "$NOTES" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT shell: bash - name: 创建 GitHub Release uses: softprops/action-gh-release@v2 with: name: "v${{ github.ref_name }}" body: ${{ steps.changelog.outputs.notes }} draft: false prerelease: ${{ contains(github.ref_name, '-') }} # Reason: 如果 tag 包含 '-'(如 v0.4.0-rc1),标记为预发布