Введение: Почему CI/CD важен для Rust-проектов
Разработка на Rust — это не только про безопасность памяти и высокую производительность. В современном мире любой серьёзный проект требует автоматизации процессов сборки, тестирования и развёртывания. CI/CD (Continuous Integration / Continuous Deployment) позволяет командам быстрее выпускать обновления, снижая риск человеческих ошибок и обеспечивая стабильность кодовой базы.
GitHub Actions — это встроенный инструмент GitHub для автоматизации рабочих процессов. Он идеально подходит для Rust-проектов, так как предоставляет готовые экшены для установки компилятора, кэширования зависимостей и публикации крейтов в crates.io. В этой статье мы разберём, как настроить полноценный CI/CD пайплайн для Rust-приложения с нуля.
1. Основы GitHub Actions для Rust
Перед тем как погрузиться в код, давайте разберёмся с базовой структурой GitHub Actions. Каждый workflow определяется YAML-файлом, который лежит в директории .github/workflows/. Workflow состоит из одного или нескольких job (задач), которые выполняются последовательно или параллельно на виртуальных машинах (раннерах).
Для Rust-проектов минимальный workflow включает следующие шаги:
- Checkout — получение последней версии кода из репозитория.
- Установка Rust — через экшн
actions-rs/toolchainили официальныйdtolnay/rust-toolchain. - Кэширование зависимостей — ускорение сборки за счёт сохранения
target/и~/.cargo/. - Сборка и тестирование — выполнение
cargo buildиcargo test.
Пример базового workflow (файл .github/workflows/ci.yml):
name: Rust CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
env:
CARGO_TERM_COLOR: always
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
components: clippy, rustfmt
- name: Cache dependencies
uses: actions/cache@v3
with:
path: |
~/.cargo/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose
- name: Lint with clippy
run: cargo clippy -- -D warnings
- name: Check formatting
run: cargo fmt --check
Этот workflow запускается при каждом push в ветку main и при создании pull request. Он проверяет, что код компилируется, проходит тесты, не содержит ошибок линтера и соответствует стилю форматирования.
2. Продвинутые стратегии: матричная сборка и кэширование
Rust — кроссплатформенный язык, и часто требуется проверять сборку на нескольких операционных системах или архитектурах. Для этого используется матричная сборка (matrix build).
Добавим в наш workflow поддержку Windows и macOS:
jobs:
build:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
rust: [stable, beta]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- name: Install Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.rust }}
components: clippy
- name: Cache
uses: actions/cache@v3
with:
path: |
~/.cargo/registry/
~/.cargo/git/
target/
key: ${{ matrix.os }}-cargo-${{ matrix.rust }}-${{ hashFiles('**/Cargo.lock') }}
- name: Build
run: cargo build --release
- name: Test
run: cargo test --release
Кэширование target/ и ~/.cargo/ — критически важный шаг для Rust. Без него каждый запуск CI будет скачивать все зависимости заново, что может занимать 5-10 минут. Ключ кэша включает хеш от Cargo.lock, поэтому при изменении зависимостей кэш автоматически инвалидируется.
Также полезно добавить проверку на наличие неиспользуемого кода с помощью cargo deadlinks или cargo udeps (для nightly Rust):
- name: Check for unused dependencies
run: cargo +nightly udeps
continue-on-error: true
3. Публикация крейта в crates.io
Одна из самых частых задач — автоматическая публикация новой версии крейта при создании git-тега. Для этого используется экшн actions-rs/cargo или прямое выполнение команды cargo publish.
Важно: для публикации потребуется API-токен от crates.io. Его нужно добавить в Secrets вашего репозитория (Settings → Secrets and variables → Actions) как CRATES_IO_TOKEN.
Пример workflow для публикации:
name: Publish to crates.io
on:
push:
tags:
- 'v*.*.*'
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Verify version
run: |
VERSION=$(cargo metadata --format-version=1 --no-deps | jq -r '.packages[0].version')
TAG=${GITHUB_REF#refs/tags/v}
if [ "$VERSION" != "$TAG" ]; then
echo "Version mismatch: Cargo.toml has $VERSION, tag has $TAG"
exit 1
fi
- name: Publish
run: cargo publish --token ${{ secrets.CRATES_IO_TOKEN }}
Этот workflow делает следующее:
- Запускается только при создании тега вида
v1.2.3. - Проверяет, что версия в
Cargo.tomlсовпадает с тегом — это защита от случайной публикации. - Публикует крейт с использованием секретного токена.
Для дополнительной безопасности можно добавить шаг с запуском всех тестов перед публикацией:
- name: Run tests before publish
run: cargo test --release