CI/CD — GitHub Actions
Автоматическая проверка кода в каждом pull request и каждом коммите.
Зачем Ruff в CI/CD
Локальный запуск Ruff — это отлично, но настоящая ценность линтера раскрывается, когда он запускается автоматически в CI/CD. Без автоматизации проверки кода человеческий фактор неизбежен: кто-то забудет запустить Ruff перед коммитом, кто-то проигнорирует предупреждения, а кто-то вообще не будет использовать линтер. CI/CD решает эту проблему: каждый коммит и каждый pull request проходят автоматическую проверку, и код с ошибками просто не может попасть в основную ветку.
Преимущества автоматической проверки кода в CI/CD:
- Гарантия качества — ни один коммит с синтаксическими ошибками или нарушением стиля не попадёт в main.
- Скорость ревью — код-ревьюеры тратят меньше времени на стилистические замечания и больше на логику.
- Единый стандарт — вся команда автоматически следует одним правилам, независимо от личных предпочтений.
- Экономия времени — Ruff делает проверку за секунды, а не минуты, как другие инструменты.
- Автоматические аннотации — ошибки отображаются прямо в diff Pull Request на GitHub.
Основные подходы к интеграции
Есть три основных способа запустить Ruff в GitHub Actions, и выбор между ними зависит от ваших потребностей:
| Подход | Способ установки | Скорость | Когда использовать |
|---|---|---|---|
astral-sh/ruff-action |
Официальный Action | Высокая | Рекомендуемый способ |
pip install ruff |
pip в виртуальном окружении | Средняя | Уже есть Python setup |
uvx ruff |
Через astral-sh/setup-uv | Очень высокая | Проект уже использует uv |
Базовый workflow с ruff-action
astral-sh/ruff-action — официальный GitHub Action от команды Ruff. Он автоматически загружает последнюю версию Ruff, устанавливает её и запускает проверку. Это самый простой и рекомендуемый способ интеграции.
Создай файл .github/workflows/lint.yml в корне репозитория:
name: Lint
on:
push:
branches: [main, master]
pull_request:
branches: [main, master]
jobs:
ruff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/ruff-action@v3
Этот минимальный workflow:
- Запускается на push в main/master и на каждый pull request
- Выполняет
ruff checkна всём проекте - Если найдены ошибки — workflow завершается с кодом 1 (failure)
astral-sh/ruff-action@v3 — официальный Action от команды Ruff. Устанавливает Ruff и запускает проверку автоматически. Версия v3 — актуальная стабильная версия Action.
Вот что делает Action «под капотом»:
- Проверяет, установлен ли уже Ruff в кеше GitHub Actions
- Если нет — скачивает готовый бинарник с GitHub Releases
- Сохраняет бинарник в кеш для последующих запусков
- Запускает
ruff checkс переданными аргументами
Параметры ruff-action
Action поддерживает ряд параметров, которые позволяют тонко настроить его поведение:
| Параметр | Описание | По умолчанию |
|---|---|---|
version |
Версия Ruff для установки (например "0.9.0") | latest |
args |
Дополнительные аргументы командной строки | — |
src |
Путь к файлам для проверки |
Пример с указанием версии и дополнительных аргументов:
- uses: astral-sh/ruff-action@v3
with:
version: "0.9.0"
args: --output-format=github --line-length=120 --target-version=py311
Аннотации в Pull Request
Одна из самых полезных возможностей Ruff в GitHub Actions — автоматические аннотации ошибок в Pull Request. Когда Ruff запускается с флагом --output-format=github, он выводит результаты в формате, который GitHub Actions автоматически преобразует в аннотации прямо в diff затронутых файлов.
--output-format=github ошибки Ruff будут видны только в логах workflow. С этим флагом они появляются прямо в diff PR — напротив тех строк, где найдены ошибки, с указанием правила и описания.
Пример workflow с аннотациями через astral-sh/ruff-action:
name: Lint with Annotations
on: [push, pull_request]
jobs:
ruff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/ruff-action@v3
with:
args: --output-format=github
Если вы предпочитаете устанавливать Ruff через pip (например, если вы хотите контролировать версию через зависимости проекта):
name: Lint
on: [push, pull_request]
jobs:
ruff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install Ruff
run: pip install ruff
- name: Lint
run: ruff check --output-format=github .
- name: Format check
run: ruff format --check .
Разберём этот workflow по шагам:
- actions/checkout@v4 — клонирует репозиторий на runner
- actions/setup-python@v5 — устанавливает Python 3.12
- pip install ruff — устанавливает Ruff через pip
- ruff check --output-format=github . — запускает линтинг с аннотациями
- ruff format --check . — проверяет форматирование (без изменений, только проверка)
ruff format --check . завершается с ошибкой (exit code 1), если файлы не отформатированы. Используйте ruff format . (без --check) только если вы хотите, чтобы CI автоматически форматировал код и коммитил изменения.
Форматы вывода для CI
Ruff поддерживает несколько форматов вывода, каждый из которых имеет свои преимущества в CI/CD:
| Формат | Команда | Когда использовать |
|---|---|---|
| GitHub | --output-format=github |
Аннотации в PR на GitHub |
| JSON | --output-format=json |
Программная обработка результатов |
| Concise | По умолчанию | Читаемый вывод в логах |
| Full | --output-format=full |
Максимально подробный вывод |
| JUnit | --output-format=junit |
Интеграция с CI-панелями |
GitHub-формат особенно полезен: он выводит ошибки в формате ::warning file={file},line={line},col={col},title={code}::{message}, который GitHub Actions автоматически парсит и показывает как аннотации прямо в diff Pull Request. Ревьюеру не нужно открывать логи workflow — ошибки видны прямо в интерфейсе просмотра кода.
Быстрый вариант через uv
Если ваш проект уже использует uv (быстрый менеджер пакетов от Astral), вы можете запускать Ruff через uvx — утилиту, которая запускает инструменты без их явной установки. uvx скачивает и кеширует бинарник Ruff при первом запуске, а при повторных использует локальный кеш.
Преимущества uv-подхода в CI:
- Минимальное время установки — uv написан на Rust и работает намного быстрее pip
- Автоматическое кеширование — uv кеширует бинарники между запусками
- Не нужен Python — uvx может запускать Ruff без установленного Python
- Единый инструментарий — если вы уже используете uv для управления проектом
name: Lint
on: [push, pull_request]
jobs:
ruff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v4
- name: Lint
run: uvx ruff check --output-format=github .
- name: Format check
run: uvx ruff format --check .
Что происходит на каждом шаге:
- astral-sh/setup-uv@v4 — устанавливает uv (занимает ~1 секунду)
- uvx ruff check — uvx проверяет, есть ли ruff в кеше. Если нет — скачивает и кеширует. Затем запускает.
- uvx ruff format --check — то же для проверки форматирования. Бинарник уже закеширован.
Полный рабочий процесс занимает 5–10 секунд на типичном проекте, что значительно быстрее альтернатив с pip.
uvx — это аналог npx из мира Node.js. Он позволяет запускать любой Python-инструмент одной командой без предварительной установки. Если инструмент уже есть в кеше uv — запуск происходит мгновенно.
Матричный запуск (matrix)
GitHub Actions поддерживает матричные сборки — запуск одного и того же workflow с разными параметрами. Это полезно, если вы хотите проверить код на нескольких версиях Python, на разных ОС или с разными настройками Ruff.
Пример матричного запуска Ruff на нескольких версиях Python и ОС:
name: Ruff Matrix
on: [push, pull_request]
jobs:
ruff:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ["3.10", "3.11", "3.12"]
fail-fast: false
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Install Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install Ruff
run: pip install ruff
- name: Run Ruff
run: ruff check --output-format=github .
Параметры матрицы:
- os — три операционные системы (Ubuntu, macOS, Windows)
- python-version — три версии Python (3.10, 3.11, 3.12)
- fail-fast: false — если одна комбинация упала, остальные продолжают работу
Итого будет запущено 3 × 3 = 9 параллельных задач. Это может быть избыточно для Ruff (который проверяет статический анализ, не зависящий от версии Python), но полезно, если вы хотите убедиться, что код работает во всех окружениях.
Более практичный пример — матрица с разными наборами правил Ruff:
name: Ruff with Rule Sets
on: [pull_request]
jobs:
ruff:
strategy:
matrix:
ruleset:
- "E,F" # pycodestyle + Pyflakes
- "I" # isort
- "N" # pep8-naming
- "UP,B,S" # pyupgrade + bugbear + bandit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/ruff-action@v3
with:
args: --select ${{ matrix.ruleset }} --output-format=github
Такой подход позволяет:
- Изолировать ошибки по категориям правил
- Быстро понять, какая группа правил вызывает проблемы
- Параллельно запускать проверки — общее время меньше
Раздельные workflow для lint и format
Линтинг и форматирование — разные задачи, и их можно (а иногда и нужно) запускать в отдельных workflow. Вот несколько причин для разделения:
- Разные триггеры — проверку форматирования можно запускать реже
- Разные требования к версиям — lint может требовать определенного набора правил
- Параллелизм — задачи выполняются одновременно, сокращая общее время
- Читаемость логов — проще найти проблему в отдельном workflow
Workflow для линтинга (.github/workflows/lint.yml)
name: Lint
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
ruff-lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/ruff-action@v3
with:
args: check --output-format=github
Workflow для форматирования (.github/workflows/format.yml)
name: Format
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
ruff-format:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/ruff-action@v3
with:
args: format --check
Также можно объединить обе задачи в одном workflow, но в разных jobs — это даёт параллельный запуск и чистый интерфейс:
name: Ruff
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
format:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/ruff-action@v3
with:
args: format --check
ruff format --check не изменяет файлы — он только проверяет, что код уже отформатирован. Если вы используете ruff format (без --check), CI изменит файлы, но не закоммитит их — проверка пройдёт, но локальный код останется неотформатированным. Лучше использовать --check и требовать форматирования локально.
Exit коды и их значение в CI
GitHub Actions определяет успешность шага по exit code — коду завершения процесса. Ruff использует следующие exit коды:
| Exit Code | Значение | Результат CI |
|---|---|---|
0 |
Успех — ошибок не найдено | ✅ Прошёл |
1 |
Найдены ошибки линтинга | ❌ Провал |
2 |
Внутренняя ошибка Ruff | ❌ Провал |
Как это работает в CI:
- Exit 0 — всё чисто, workflow помечается зелёной галочкой
- Exit 1 — найдены ошибки, workflow помечается красным крестом. PR нельзя смержить (если настроены branch protection rules)
Если вам нужно игнорировать exit code Ruff (например, в переходный период, когда вы постепенно внедряете правила), используйте continue-on-error: true:
- name: Lint
continue-on-error: true
run: ruff check --output-format=github .
- name: Format check
continue-on-error: true
run: ruff format --check .
С continue-on-error: true workflow продолжит работу, даже если Ruff найдёт ошибки. Шаг будет отмечен жёлтым предупреждением, но не заблокирует PR. Это полезно на этапе внедрения Ruff в существующий проект.
Кеширование Ruff в CI
Кеширование позволяет ускорить повторные запуски workflow, сохраняя бинарник Ruff или виртуальное окружение между запусками. GitHub Actions предоставляет встроенное кеширование через actions/cache.
Кеширование бинарника Ruff через actions/cache
name: Lint with Caching
on: [push, pull_request]
jobs:
ruff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Cache Ruff
uses: actions/cache@v4
id: cache-ruff
with:
path: ~/.cache/ruff
key: ${{ runner.os }}-ruff-${{ hashFiles('**/ruff.toml', '**/pyproject.toml') }}
restore-keys: |
${{ runner.os }}-ruff-
- uses: astral-sh/ruff-action@v3
Как работает кеширование Ruff:
- Ruff хранит кеш анализа в
~/.cache/ruff - При повторном запуске Ruff проверяет, изменились ли файлы
- Если файлы не изменились — Ruff использует кеш и завершается за миллисекунды
- Ключ кеша включает хеш конфигурационных файлов — при изменении настроек кеш сбрасывается
Кеширование виртуального окружения с pip
Если вы устанавливаете Ruff через pip, можно кешировать виртуальное окружение:
- name: Cache pip
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements*.txt', '**/pyproject.toml') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Install Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install Ruff
run: pip install ruff
Кеширование uv
Если вы используете uv, кеширование выполняется автоматически через astral-sh/setup-uv, но можно настроить и вручную:
- name: Cache uv
uses: actions/cache@v4
with:
path: ~/.cache/uv
key: ${{ runner.os }}-uv-${{ hashFiles('**/pyproject.toml') }}
restore-keys: |
${{ runner.os }}-uv-
- uses: astral-sh/setup-uv@v4
Продвинутые сценарии
Запуск только на изменённых файлах
На больших проектах может быть полезно запускать Ruff только на изменённых файлах в PR, а не на всём проекте. Это ускоряет CI. Можно использовать tj-actions/changed-files для получения списка изменённых файлов:
name: Lint Changed Files
on: [pull_request]
jobs:
ruff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get changed Python files
id: changed-files
uses: tj-actions/changed-files@v45
with:
files: |
**/*.py
- uses: astral-sh/ruff-action@v3
with:
src: ${{ steps.changed-files.outputs.all_changed_files }}
Проверка с фиксацией ошибок
Можно настроить workflow так, чтобы Ruff автоматически исправлял ошибки и коммитил изменения:
name: Auto-fix with Ruff
on:
pull_request:
branches: [main]
jobs:
auto-fix:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
- uses: astral-sh/ruff-action@v3
with:
args: check --fix
- uses: astral-sh/ruff-action@v3
with:
args: format
- name: Commit changes
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "style: auto-fix with Ruff"
branch: ${{ github.head_ref }}
contents: write), иначе коммит не будет создан. Также не забудьте, что --fix исправляет только безопасные ошибки — некоторые требуют ручного вмешательства.
Отдельный workflow для разных веток
Можно настроить разные правила для разных веток — например, на main строгие правила, а на develop — более мягкие:
name: Strict Lint
on:
push:
branches: [main]
jobs:
strict:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/ruff-action@v3
with:
args: check --output-format=github --select ALL
name: Basic Lint
on:
push:
branches: [develop]
jobs:
basic:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/ruff-action@v3
with:
args: check --output-format=github --select E,F
На main запускаются все правила (--select ALL), а на develop — только базовые (--select E,F). Это позволяет постепенно наводить порядок.
Лучшие практики CI/CD с Ruff
📋 Рекомендации:
- Используйте astral-sh/ruff-action — это официальный, поддерживаемый и наиболее простой способ интеграции.
- Всегда включайте --output-format=github — аннотации в PR значительно упрощают код-ревью.
- Запускайте lint и format в отдельных jobs — для параллелизма и чистоты логов.
- Проверяйте только изменённые файлы на больших проектах — это ускоряет CI.
- Используйте branch protection rules — настройте GitHub так, чтобы PR нельзя было смержить без прохождения Ruff.
- Кешируйте бинарник Ruff и его кеш анализа — это ускоряет повторные запуски.
- Не используйте ruff format без --check — CI не должен изменять код, это задача разработчика.
📌 Что важно запомнить:
- Ruff в CI — финальная линия обороны от некачественного кода
astral-sh/ruff-action@v3— официальный Action--output-format=github— аннотации в Pull Request- uvx ускоряет запуск через astral-sh/setup-uv
- Матричный запуск полезен для кросс-платформенной проверки
- Exit code 0 = успех, exit code 1 = найдены ошибки
- Раздельные workflow для lint и format упрощают отладку
- Кеширование ускоряет повторные запуски на 50-90%
Пошаговая настройка: от нуля до production
Давайте пройдём полный путь настройки Ruff в GitHub Actions — от создания файла до production-готового workflow.
Шаг 1: Создайте структуру директорий
mkdir -p .github/workflows
touch .github/workflows/lint.yml
Шаг 2: Напишите базовый workflow
name: Ruff
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
format:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/ruff-action@v3
with:
args: format --check
Шаг 3: Закоммитьте и отправьте в GitHub
git add .github/workflows/lint.yml
git commit -m "ci: add Ruff linter and formatter"
git push
Шаг 4: Проверьте результат
Откройте вкладку Actions в вашем репозитории на GitHub. Вы должны увидеть запущенный workflow «Ruff». Если код чист — он будет зелёным. Если есть ошибки — красным.
Шаг 5: Настройте branch protection (опционально)
Чтобы запретить мёрж PR с ошибками Ruff, перейдите в Settings → Branches → Add branch protection rule. Включите «Require status checks to pass before merging» и выберите «Ruff / lint» и «Ruff / format».
Сравнение подходов к установке
| Критерий | ruff-action | pip install | uvx |
|---|---|---|---|
| Время установки | ~2 с | ~10-15 с | ~3 с |
| Кеширование бинарника | ✅ Встроенное | ⚠️ Через actions/cache | ✅ Встроенное |
| Зависимость от Python | Нет | Да | Нет |
| Контроль версии | Через параметр version | Через requirements.txt | Через параметр uvx |
| Простота настройки | Максимальная | Средняя | Высокая |
Типичные проблемы и их решение
❌ Workflow не запускается
Убедитесь, что файл находится в .github/workflows/lint.yml (правильный путь и расширение .yml, не .yaml если настроено иначе). Проверьте синтаксис YAML через валидатор.
❌ Ruff не устанавливается через ruff-action
Проверьте версию Action — используйте @v3. Убедитесь, что runner имеет доступ к GitHub API (не корпоративный прокси без доступа).
❌ Аннотации не появляются в PR
Убедитесь, что вы используете --output-format=github. Если workflow запускается от fork PR, аннотации могут не работать из-за ограничений GitHub Actions на токены от fork-репозиториев.
❌ ruff format --check всегда зелёный
Проверьте, что вы передаёте правильный путь к файлам. ruff format --check . проверяет все Python-файлы в текущей директории. Если файлы скрыты exclude — они не проверяются.
❌ Матричный запуск слишком долгий
Ruff не зависит от версии Python, поэтому матрица по версиям Python для Ruff избыточна. Оставьте одну версию Python для Ruff, а матрицу используйте только для тестов.
Что дальше
Теперь вы знаете, как добавить Ruff в GitHub Actions. В следующем модуле мы рассмотрим настройку Ruff для больших проектов, работу с монорепозиториями и стратегии постепенного внедрения.
📌 Ключевые выводы:
- Используйте
astral-sh/ruff-action@v3для максимальной простоты - Флаг
--output-format=githubдаёт аннотации в Pull Request - Для проектов на uv используйте
astral-sh/setup-uvиuvx ruff - Разделяйте lint и format на отдельные jobs
- Матрица полезна для ОС, но не для версий Python (Ruff статический)
- Exit code 0 = успех, 1 = ошибки
- Кеширование ускоряет повторные запуски
- Branch protection rules защищают main от плохого кода
Урок 6.3: GitHub Actions
5 вопросов