Chapter 09

CI/CD 与 GitHub Actions

把 Ruff 接入持续集成流水线——每次 PR 自动跑,失败即阻塞合并;输出 GitHub 原生注释。

9.1 为什么 CI 一定要跑 Ruff

pre-commit 钩子在本地跑,但依赖开发者不主动 --no-verify 绕过。CI 是最后一道强制防线——代码进主干前,必须在 Pull Request 上通过 Ruff 检查。CI 层面的好处:

9.2 官方 GitHub Action

Astral 官方维护 astral-sh/ruff-action,一行能搞定:

# .github/workflows/lint.yml
name: Lint

on:
  push:
    branches: [main]
  pull_request:

jobs:
  ruff:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/ruff-action@v3
        with:
          version: "0.11.3"    # 锁定版本
          args: "check --output-format=github"

9.3 --output-format=github:原生注释

在 PR 页面,Ruff 会把每个违规显示成 代码行上的 annotation——点击直接定位到源码位置。实现靠一个特殊的输出格式:

$ ruff check --output-format=github .
# ::error file=app.py,line=12,col=5,title=F401::`os` imported but unused

这种 ::error ... 注释 GitHub Actions 会自动识别并转换成 PR 代码行评论。其他格式:

text
默认,人类可读。
full
带完整诊断上下文(多行)。
json / json-lines
机器可读,自定义 dashboard 时用。
junit
Jenkins / Azure DevOps 的 JUnit XML 格式。
gitlab
GitLab Code Quality 报告格式。
pylint
兼容 pylint 输出(便于老工具链接入)。
sarif
Static Analysis Results Interchange Format,GitHub Advanced Security 可读。

9.4 完整 workflow:Lint + Format + 测试

name: CI
on: [push, pull_request]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/ruff-action@v3
        with:
          args: "check --output-format=github"
      - uses: astral-sh/ruff-action@v3
        with:
          args: "format --check"

  test:
    needs: lint          # lint 过了再跑测试
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/setup-uv@v5
        with:
          version: "0.5.x"
      - run: uv sync --frozen
      - run: uv run pytest

9.5 --statistics:规则分布

大仓初次接入 Ruff 想知道"到底哪些规则最高频":

$ ruff check --statistics .
   12  F401  [*] unused-import
   45  E501       line-too-long
    8  B006  [*] mutable-argument-default
    3  F841  [*] unused-variable

把这个输出存成 CI artifact,可以跟踪"技术债"随 commit 的下降曲线。

9.6 缓存 Ruff 二进制

ruff-action 默认会缓存 ruff 二进制。若手动用 pip install ruff 方式,建议加缓存:

- uses: actions/cache@v4
  with:
    path: ~/.cache/ruff
    key: ruff-${{ hashFiles('pyproject.toml') }}

9.7 只检查变更文件(大仓提速)

几十万行的仓库 Ruff 也只要几秒,但如果想更快,可以只检查 PR 变更的文件:

- name: Get changed files
  id: changed
  uses: tj-actions/changed-files@v45
  with:
    files: "**/*.py"

- uses: astral-sh/ruff-action@v3
  if: steps.changed.outputs.any_changed == 'true'
  with:
    args: "check --output-format=github ${{ steps.changed.outputs.all_changed_files }}"

9.8 GitLab CI

# .gitlab-ci.yml
ruff:
  image: ghcr.io/astral-sh/ruff:0.11.3
  script:
    - ruff check --output-format=gitlab . > code-quality.json
    - ruff format --check .
  artifacts:
    reports:
      codequality: code-quality.json

Ruff 官方发布 Docker 镜像 ghcr.io/astral-sh/ruff,镜像很小(几 MB,纯静态二进制)。

9.9 Jenkins / 其他 CI

# Jenkinsfile
stage('Ruff') {
  steps {
    sh 'pipx install ruff==0.11.3'
    sh 'ruff check --output-format=junit . > ruff.xml || true'
    junit 'ruff.xml'
  }
}

9.10 Required status checks(强制合入前通过)

GitHub 仓库 → Settings → Branches → 添加 main 分支保护规则 → 勾选 Require status checks to pass before merging → 选中 "ruff" job。这样任何未跑过 Ruff 的 PR 都无法合并。

分层防御

本地 pre-commit(第一道,防低级错误)+ CI Ruff(第二道,不可绕过)+ Branch Protection(第三道,必须通过)。三层下来,垃圾代码基本进不了主干。

9.11 小结