Перейти к основному содержимому

Знакомство с системой сборки Cargo

Cargo — это официальная система сборки и пакетный менеджер для Rust. В этой главе мы глубоко изучим возможности Cargo: от создания проектов до управления зависимостями и настройки сложных сборочных процессов.

Что такое Cargo?

Cargo выполняет множество задач в экосистеме Rust:

📦 Пакетный менеджер

Управление внешними библиотеками и зависимостями

🔧 Система сборки

Компиляция, тестирование и создание исполняемых файлов

📋 Менеджер проектов

Создание и организация структуры Rust-проектов

📚 Генератор документации

Автоматическое создание документации из кода

Философия Cargo

Cargo следует принципу "конвенция вместо конфигурации" — большинство стандартных задач работают "из коробки" без дополнительной настройки.

Типы проектов Cargo

Бинарные проекты (приложения)

Создание приложения:

Создание бинарного проекта
cargo new my_app
cd my_app

Структура:

my_app/
├── Cargo.toml
├── src/
│ └── main.rs # Точка входа приложения
└── .gitignore

Библиотечные проекты (крейты)

Создание библиотеки:

Создание библиотечного проекта
cargo new my_lib --lib
cd my_lib

Структура:

my_lib/
├── Cargo.toml
├── src/
│ └── lib.rs # Корень библиотеки
└── .gitignore

Смешанные проекты

Проект с библиотекой и приложением:

my_project/
├── Cargo.toml
├── src/
│ ├── main.rs # Бинарное приложение
│ └── lib.rs # Библиотека
├── examples/ # Примеры использования
├── tests/ # Интеграционные тесты
└── benches/ # Бенчмарки производительности

Глубокое изучение Cargo.toml

Файл Cargo.toml — это сердце любого Rust-проекта. Рассмотрим все его возможности:

Секция [package]

Базовая информация о пакете
[package]
name = "my_awesome_app" # Имя пакета (обязательно)
version = "0.1.0" # Версия (обязательно)
edition = "2021" # Версия языка (обязательно)
authors = ["Ваше Имя <email@example.com>"]
description = "Описание проекта"
license = "MIT" # Лицензия
repository = "https://github.com/user/repo"
homepage = "https://example.com"
documentation = "https://docs.example.com"
readme = "README.md"
keywords = ["web", "api", "server"] # До 5 ключевых слов
categories = ["web-programming"] # Категории на crates.io

Секция [dependencies]

Зависимости — это внешние библиотеки, которые использует ваш проект:

Основные способы указания зависимостей
[dependencies]
# Простая зависимость из crates.io
serde = "1.0"

# Точная версия
rand = "=0.8.5"

# Диапазон версий
tokio = ">=1.0, <2.0"

# Совместимые версии (рекомендуется)
reqwest = "^0.11" # эквивалентно ">=0.11.0, <0.12.0"
clap = "~4.1.6" # эквивалентно ">=4.1.6, <4.2.0"

Другие типы зависимостей

Специальные типы зависимостей
[dev-dependencies]
# Зависимости только для тестов и разработки
assert_cmd = "2.0"
tempfile = "3.0"
criterion = "0.4"

[build-dependencies]
# Зависимости для скриптов сборки (build.rs)
cc = "1.0"
pkg-config = "0.3"

[target.'cfg(windows)'.dependencies]
# Зависимости для конкретных платформ
winapi = { version = "0.3", features = ["winuser"] }

[target.'cfg(unix)'.dependencies]
libc = "0.2"

Управление функциональностью (Features)

Features позволяют условно включать код и зависимости:

Определение функций

Cargo.toml - определение features
[package]
name = "my_lib"
version = "0.1.0"
edition = "2021"

[features]
default = ["json"] # Функции по умолчанию
json = ["dep:serde_json"] # Функция включает зависимость
xml = ["dep:serde_xml_rs"] # Альтернативная функция
full = ["json", "xml", "extra"] # Мета-функция
extra = [] # Пустая функция для условной компиляции

[dependencies]
serde = "1.0"
serde_json = { version = "1.0", optional = true }
serde_xml_rs = { version = "0.5", optional = true }

Использование в коде

src/lib.rs - условная компиляция
// Код включается только если активна функция "json"
#[cfg(feature = "json")]
pub fn parse_json(input: &str) -> Result<serde_json::Value, serde_json::Error> {
serde_json::from_str(input)
}

// Код включается только если активна функция "xml"
#[cfg(feature = "xml")]
pub fn parse_xml(input: &str) -> Result<serde_xml_rs::Value, serde_xml_rs::Error> {
serde_xml_rs::from_str(input)
}

// Код включается если активна любая из функций
#[cfg(any(feature = "json", feature = "xml"))]
pub fn supported_formats() -> Vec<&'static str> {
let mut formats = Vec::new();

#[cfg(feature = "json")]
formats.push("json");

#[cfg(feature = "xml")]
formats.push("xml");

formats
}

Сборка с функциями

Команды для работы с функциями
# Сборка с функциями по умолчанию
cargo build

# Сборка без функций по умолчанию
cargo build --no-default-features

# Сборка с конкретными функциями
cargo build --features="json xml"

# Сборка со всеми функциями
cargo build --all-features

# Тестирование разных комбинаций функций
cargo test --features="json"
cargo test --features="xml"
cargo test --all-features

Рабочие области (Workspaces)

Workspaces позволяют управлять несколькими связанными пакетами:

Создание workspace

Cargo.toml в корне workspace
[workspace]
members = [
"server",
"client",
"shared",
"tools/*", # Wildcard для подпапок
]

exclude = [
"legacy", # Исключить из workspace
"experimental/*"
]

resolver = "2" # Использовать новый resolver

[workspace.dependencies]
# Общие зависимости для всех пакетов
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.0", features = ["full"] }
anyhow = "1.0"

[workspace.metadata]
# Метаданные workspace
rust-version = "1.70"

Структура workspace

my_workspace/
├── Cargo.toml # Корневой манифест workspace
├── Cargo.lock # Единый lock-файл для всего workspace
├── server/ # Пакет сервера
│ ├── Cargo.toml
│ └── src/
├── client/ # Пакет клиента
│ ├── Cargo.toml
│ └── src/
├── shared/ # Общая библиотека
│ ├── Cargo.toml
│ └── src/
└── target/ # Общая папка сборки

Использование в пакетах workspace

server/Cargo.toml
[package]
name = "server"
version = "0.1.0"
edition = "2021"

[dependencies]
# Использование общих зависимостей
serde = { workspace = true }
tokio = { workspace = true }

# Локальная зависимость из workspace
shared = { path = "../shared" }

# Дополнительные зависимости
axum = "0.6"

Команды для workspace

Работа с workspace
# Сборка всех пакетов
cargo build --workspace

# Сборка конкретного пакета
cargo build -p server

# Тестирование всех пакетов
cargo test --workspace

# Выпуск всех пакетов в release
cargo build --workspace --release

# Просмотр дерева зависимостей
cargo tree

# Обновление всех зависимостей
cargo update

Профили сборки

Профили позволяют настроить параметры компиляции:

Настройка встроенных профилей
# Отладочный профиль (cargo build)
[profile.dev]
opt-level = 0 # Без оптимизаций (быстрая компиляция)
debug = true # Включить отладочную информацию
split-debuginfo = 'unpacked' # Формат отладочной информации
debug-assertions = true # Включить проверки assert!
overflow-checks = true # Проверка переполнения целых чисел
lto = false # Отключить Link Time Optimization
panic = 'unwind' # Стратегия обработки panic
incremental = true # Включить инкрементальную компиляцию
codegen-units = 256 # Количество единиц кодогенерации

# Релизный профиль (cargo build --release)
[profile.release]
opt-level = 3 # Максимальные оптимизации
debug = false # Отключить отладочную информацию
debug-assertions = false # Отключить проверки assert!
overflow-checks = false # Отключить проверки переполнения
lto = false # Link Time Optimization
panic = 'unwind' # Стратегия обработки panic
incremental = false # Отключить инкрементальную компиляцию
codegen-units = 16 # Меньше единиц для лучшей оптимизации

Скрипты сборки (build.rs)

Скрипты сборки выполняются перед компиляцией основного кода:

Создание build.rs

build.rs в корне проекта
use std::env;
use std::fs::File;
use std::io::Write;
use std::path::Path;

fn main() {
// Переменные окружения от Cargo
let out_dir = env::var("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("hello.rs");

// Генерация кода
let mut f = File::create(&dest_path).unwrap();
f.write_all(b"
pub fn generated_hello() {
println!(\"Hello from generated code!\");
}
").unwrap();

// Указываем Cargo перекомпилировать при изменении файлов
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=src/");

// Устанавливаем переменные окружения для главного кода
println!("cargo:rustc-env=BUILD_TIME={}", chrono::Utc::now());

// Передаём флаги компилятору
println!("cargo:rustc-link-lib=static=mylib");
println!("cargo:rustc-link-search=native=/path/to/lib");
}

Использование в main.rs

src/main.rs
// Подключение сгенерированного кода
include!(concat!(env!("OUT_DIR"), "/hello.rs"));

fn main() {
generated_hello();

// Использование переменной из build.rs
println!("Собрано в: {}", env!("BUILD_TIME"));
}

Зависимости для build.rs

Cargo.toml с build-зависимостями
[package]
name = "my_app"
version = "0.1.0"
edition = "2021"
build = "build.rs" # Указываем скрипт сборки

[build-dependencies]
chrono = { version = "0.4", features = ["serde"] }
cc = "1.0" # Для компиляции C кода
bindgen = "0.60" # Для генерации биндингов

Продвинутые команды Cargo

Информационные команды

Получение информации о проекте
# Информация о проекте и зависимостях
cargo metadata --format-version=1 | jq

# Дерево зависимостей
cargo tree

# Дерево с конкретными функциями
cargo tree --features="json xml"

# Обратные зависимости
cargo tree --invert serde

# Дублирующиеся зависимости
cargo tree --duplicates

# Размеры в скомпилированном файле
cargo bloat --release --crates

# Время компиляции зависимостей
cargo build --timings

Команды очистки и обслуживания

Очистка и обслуживание проекта
# Очистка папки target
cargo clean

# Очистка только release артефактов
cargo clean --release

# Очистка конкретного пакета в workspace
cargo clean -p my_package

# Проверка неиспользуемых зависимостей
cargo machete

# Обновление зависимостей
cargo update

# Обновление конкретной зависимости
cargo update -p serde

# Аудит безопасности зависимостей
cargo audit

Команды тестирования

Расширенное тестирование
# Тестирование с выводом результатов
cargo test -- --nocapture

# Параллельное выполнение тестов
cargo test -- --test-threads=4

# Тестирование конкретного теста
cargo test test_name

# Тестирование с конкретными функциями
cargo test --features="json xml"

# Тестирование документации
cargo test --doc

# Бенчмарки (только на nightly)
cargo +nightly bench

Публикация пакетов

Подготовка к публикации

Проверка перед публикацией
# Сухой запуск публикации
cargo publish --dry-run

# Проверка пакета
cargo package

# Просмотр содержимого пакета
cargo package --list

# Локальная установка для тестирования
cargo install --path .

Настройка публикации

Cargo.toml для публикации
[package]
name = "my_awesome_lib"
version = "1.0.0"
edition = "2021"
authors = ["Your Name <email@example.com>"]
description = "Краткое описание библиотеки"
license = "MIT OR Apache-2.0"
repository = "https://github.com/username/my_awesome_lib"
documentation = "https://docs.rs/my_awesome_lib"
homepage = "https://my-awesome-lib.example.com"
readme = "README.md"
keywords = ["parser", "json", "data"] # До 5 ключевых слов
categories = ["parsing", "data-structures"] # Категории с crates.io

# Исключить ненужные файлы из публикации
exclude = [
"tests/fixtures/*",
"*.tmp",
"docs/internal/*"
]

# Или включить только нужные
include = [
"src/**/*",
"Cargo.toml",
"README.md",
"LICENSE*"
]

Публикация

Процесс публикации
# Авторизация в crates.io (один раз)
cargo login YOUR_API_TOKEN

# Публикация
cargo publish

# Отзыв версии (только в течение 72 часов)
cargo yank --vers 1.0.0

# Отмена отзыва
cargo yank --vers 1.0.0 --undo

Заключение

В этой главе мы детально изучили Cargo:

Типы проектов — бинарные, библиотечные, смешанные ✅ Глубокую структуру Cargo.toml — все секции и возможности ✅ Управление зависимостями — источники, версии, функции ✅ Рабочие области — организацию больших проектов ✅ Профили сборки — оптимизацию компиляции ✅ Скрипты сборки — кодогенерацию и интеграцию ✅ Продвинутые команды — для профессиональной разработки

Cargo — это мощный инструмент, который делает разработку на Rust продуктивной и приятной. Понимание его возможностей критически важно для эффективной работы с языком.

Что дальше?

В следующей главе: "Переменные и мутабельность" — мы начнём изучать основы синтаксиса Rust, начиная с переменных и понятия изменяемости.


Практические задания

  1. Создайте workspace с библиотекой и двумя приложениями, использующими эту библиотеку
  2. Настройте пользовательские features для условной компиляции разных функций
  3. Создайте build.rs скрипт, генерирующий код на основе переменных окружения
  4. Оптимизируйте профили сборки для минимального размера исполняемого файла

Вопросы для самопроверки

  1. В чём разница между [dependencies], [dev-dependencies] и [build-dependencies]?
  2. Как работают workspace и какие преимущества они дают?
  3. Что такое features и как они влияют на размер скомпилированного кода?
  4. Когда нужны скрипты сборки (build.rs)?

Полезные ссылки