
Привет!
Cargo - это хороший менеджер пакетов Rust, который берет на себя тяжелую работу по управлению зависимостями, сборке проекта, тестированию и многому другому.
Cargo
Cargo – это официальный менеджер пакетов и система сборки для Rust. Он был разработан для упрощения работы с кодом на Rust, обеспечивая единообразную сборку проектов, управление зависимостями, ну и плюсом тестирование. Как npm для Node.js или Maven для Java – Cargo играет аналогичную роль для Rust.
Основные команды Cargo:
cargo new
Создает новый проект Rust.
Пример:
cargo new my_project
создает новый проект с именемmy_project
.
cargo build
Компилирует текущий проект и все его зависимости.
Варианты:
cargo build --release
для создания оптимизированной сборки.
cargo run
Компилирует и запускает исполняемый файл проекта.
Можно передать аргументы:
cargo run -- arg1 arg2
.
cargo test
Запускает тесты, определенные в проекте.
Можно запускать определенные тесты:
cargo test test_name
.
cargo check
Быстро проверяет код проекта на наличие ошибок, не производя полной компиляции.
cargo clean
Удаляет сгенерированные артефакты сборки.
cargo doc
Генерирует документацию для текущего проекта и его зависимостей.
cargo update
Обновляет зависимости проекта, указанные в
Cargo.lock
, до последних версий, совместимых сCargo.toml
.
cargo publish
Публикует пакет в crates.io, официальном реестре пакетов Rust.
Итак, основные возможности:
Cargo автоматически скачивает нужные библиотеки (которые называются "crates" в Rust) и их зависимости. Cargo компилирует код, используя правильные версии зависимостей и параметры сборки.
Cargo позволяет публиковать свои crates на crates.io.
Установим и настроим
Cargo поставляется вместе с Rust, поэтому, если уже установлен Rust, то Cargo уже установлен:
rustc --version
Если видно версию Rust, значит, все ок
Настроим рабочее окружение:
Создадим каталог, в котором будем работать (папку). Переходим в рабочий каталог с помощью терминала и выполняем команду инициализации проекта:
[dependencies]
my_library = "^1.2.3"
Это крейтит файл Cargo.toml
и каталог src
с файлом main.rs
, где в будущем будем писать код. Через консоль можно создать каталог таким образом:
$ cargo new hello_world --bin
Открываем файл Cargo.toml
в текстовом редакторе. В этом файле будем указывать зависимости и настройки проекта.
Файл Cargo.toml
Секция [dependencies]
используется для определения основных зависимостей проекта. Эти зависимости будут установлены при сборке проекта для всех конфигураций:
[package]
name = "my_project"
version = "0.1.0"
edition = "2018"
[dependencies]
my_library = "1.2.3"
Секция [dev-dependencies]
используется для зависимостей, которые нужны только во время разработки, например, для юнит-тестирования. Они не будут включены в финальную сборку проекта:
[dev-dependencies]
test_framework = "0.5.0"
Секция [build-dependencies]
используется для зависимостей, которые нужны только во время сборки проекта:
[build-dependencies]
codegen_tool = "0.1.0"
Можно так же определить зависимость как optional
. Например:
[dependencies]
serde = { version = "1.0", optional = true }
serde
- это сериализационная библиотека, которую мы хотим использовать только в определенных условиях.
Features представляют собой именованный набор зависимостей и настроек. В Cargo.toml
можно определить features следующим образом:
[features]
json-support = ["serde"]
feature, названная json-support
, включает в себя опциональную зависимость serde
.
Когда определена feature, можно использовать атрибуты условной компиляции в коде, чтобы включать или исключать части кода.
#[cfg(feature = "json-support")]
fn parse_json(data: &str) -> Result<MyStruct, serde_json::Error> {
serde_json::from_str(data)
}
#[cfg(not(feature = "json-support"))]
fn parse_json(_data: &str) -> Result<MyStruct, ()> {
Err(())
}
Функция parse_json
будет работать с сериализацией JSON только если включена feature json-support
.
Можно создавать features, которые зависят от других features:
[features]
better-logging = ["log", "json-support"]
better-logging
включает себя зависимость log
и feature json-support
.
Иногда нужно, чтобы определенные features исключали друг друга:
#[cfg(all(feature = "use-mysql", not(feature = "use-postgres")))]
fn setup_database() {
// Настройка для MySQL
}
#[cfg(all(feature = "use-postgres", not(feature = "use-mysql")))]
fn setup_database() {
// Настройка для PostgreSQL
}
Этот код обеспечивает, что будет использоваться только одна из баз данных.
Cargo с CI/CD
В CI основной акцент на автоматизацию тестирования и сборки. CD фокусируется на автоматизации процесса развертывания после тестирования.
Файл Cargo.lock
должен быть включен репозиторий, особенно для исполняемых проектов, чтобы гарантировать согласованность зависимостей на всех этапах CI/CD.
Настройка .gitlab-ci.yml
для GitLab CI:
stages:
- build
- test
build_job:
stage: build
script:
- cargo build --release
test_job:
stage: test
script:
- cargo test
Настройка GitHub Actions
name: Rust CI
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose
Интеграция с Docker в GitLab CI
deploy_job:
stage: deploy
script:
- cargo build --release
- docker build -t my-rust-app .
- docker push my-rust-app
Можно настроить автоматические проверки кода с использованием cargo clippy
и cargo fmt
, например файл будет определять процесс CI, который запускается при каждом push и pull request в master ветку:
name: Rust CI
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
- name: Cache Cargo dependencies
uses: actions/cache@v2
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Check Code Style with cargo fmt
uses: actions-rs/cargo@v1
with:
command: fmt
args: -- --check
- name: Lint with cargo clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: -- -D warnings
- name: Build
uses: actions-rs/cargo@v1
with:
command: build
args: --verbose
- name: Run tests
uses: actions-rs/cargo@v1
with:
command: test
args: --verbose
actions/checkout@v2
позволяет workflow использовать код. actions-rs/toolchain@v1
: устанавливает определенную версию Rust toolchain. actions-rs/cargo@v1
шаги используют cargo для выполнения различных команд: cargo fmt -- --check
проверяет стиль кода. cargo clippy -- -D warnings
запускает linter clippy
для выявления предупреждений и ошибок в коде. cargo build
и cargo test
собирают проект и выполняют тесты соответственно.
Ограничения на версии
Ограничения на версии позволяют указать диапазон версий, которые хотим использовать. Примеры ограничений:
=
: Использовать только конкретную версию.Пример:
my_library = "1.2.3"
>=
: Использовать версии, равные или новее указанной.Пример:
my_library >= "1.2.3"
<=
:Использовать версии, равные или старше указанной.Пример:
my_library <= "2.0.0"
~
: Использовать версии, которые совместимы с указанной версией, но не изменяют MAJOR версию.Пример:
my_library = "~1.2.0"
^
: Использовать версии, которые совместимы с указанной версией, но не изменяют MAJOR и MINOR версии.Пример:
my_library = "^1.2.0"
!=
: Исключить определенные версии.Пример:
my_library != "1.0.0"
SemVer - это соглашение о формате версий библиотек и зависимостей, которое позволяет определить, какие изменения были внесены в новой версии и какие возможности могут быть нарушены для разработчиков, использующих эту зависимость. SemVer включает в себя три компонента версии: MAJOR, MINOR и PATCH.
MAJOR (главный номер): увеличивается, когда вносятся обратно несовместимые изменения API.
MINOR (минорный номер): увеличивается, когда добавляются новые функциональные возможности, но с сохранением обратной совместимости. Новые функции могут быть добавлены, но существующий код должен продолжать работать.
PATCH (патч-номер): увеличивается, когда вносятся исправления ошибок или изменения, которые сохраняют обратную совместимость, означает, что внесенные изменения исправляют ошибки, но не меняют функциональность.
Предположим, у нас есть зависимость в файле Cargo.toml
следующего вида:
[dependencies]
my_library = "1.2.3"
MAJOR (главный номер) равен 1. Это означает, что мы используем главную версию этой библиотеки.
MINOR (минорный номер) равен 2. Это указывает на минорные обновления с новыми функциональными возможностями, но с обратной совместимостью.
PATCH (патч-номер) равен 3. Это обновление исправлений и изменений с обратной совместимостью.
Примеры ограничений:
Использовать версии от 1.2.3 до 2.0.0 (включительно):
my_library >= "1.2.3, <= 2.0.0"
Использовать версии совместимые с 1.2.0, но не изменяющие MAJOR версию:
my_library = "~1.2.0"
Использовать версии совместимые с 1.2.0, но не изменяющие MAJOR и MINOR версии:
my_library = "^1.2.0"
После указания зависимостей и их ограничений в Cargo.toml
, можно выпнолить команду cargo update
в командной строке в корневой директории проекта. Cargo обновит зависимости в соответствии с указанными ограничениями и загрузит подходящие версии.
В продолжение темы хочу напомнить про бесплатные вебинары от экспертов рынка про безопасный unsafe Rust и про то, как Rust побуждает использовать композицию.
А больше курсов от экспертов OTUS можно найти в полном каталоге.