Введение
Добро пожаловать в Cargo Workspaces. Эта лаборатория является частью Rust Book. Вы можете практиковать свои навыки Rust в LabEx.
В этой лаборатории мы исследуем функцию рабочих пространств Cargo, которая помогает управлять несколькими связанными пакетами, разрабатываемыми одновременно, разделяя пакет на несколько библиотечных ящиков.
Cargo Workspaces
В главе 12 мы создали пакет, который включал бинарный ящик и библиотечный ящик. При развитии вашего проекта вы можете обнаружить, что библиотечный ящик продолжает расти, и вы хотите разделить пакет на несколько библиотечных ящиков. Cargo предлагает функцию под названием workspaces, которая может помочь управлять несколькими связанными пакетами, которые разрабатываются одновременно.
Создание рабочего пространства
Рабочее пространство — это набор пакетов, которые используют один и тот же Cargo.lock и директорию вывода. Создадим проект с использованием рабочего пространства — мы будем использовать простой код, чтобы сосредоточиться на структуре рабочего пространства. Существует несколько способов структурировать рабочее пространство, поэтому мы покажем только один распространенный способ. У нас будет рабочее пространство, содержащее бинарный файл и две библиотеки. Бинарный файл, который обеспечит основную функциональность, будет зависеть от двух библиотек. Одна библиотека будет предоставлять функцию add_one, а другая библиотека — функцию add_two. Эти три ящика будут частью одного рабочего пространства. Мы начнем с создания новой директории для рабочего пространства:
mkdir add
cd add
Далее, в директории add создаем файл Cargo.toml, который будет настраивать целое рабочее пространство. Этот файл не будет иметь секции [package]. Вместо этого он будет начинаться с секции [workspace], которая позволит нам добавить члены в рабочее пространство, указав путь к пакету с нашим бинарным ящиком; в этом случае путь — это adder:
Имя файла: Cargo.toml
[workspace]
members = [
"adder",
]
Далее, мы создадим бинарный ящик adder, выполнив cargo new внутри директории add:
$ cargo new adder
Created binary (application) `adder` package
На этом этапе мы можем собрать рабочее пространство, выполнив cargo build. Файлы в директории add должны выглядеть так:
├── Cargo.lock
├── Cargo.toml
├── adder
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── target
Рабочее пространство имеет одну директорию target на верхнем уровне, в которую будут помещены скомпилированные артефакты; пакет adder не имеет собственной директории target. Даже если мы выполним cargo build из директории adder, скомпилированные артефакты по-прежнему окажутся в add/target, а не в add/adder/target. Cargo структурирует директорию target в рабочем пространстве именно так, потому что ящики в рабочем пространстве должны зависеть друг от друга. Если каждый ящик имел свою собственную директорию target, каждый ящик должен был бы пересобрать каждый из других ящиков в рабочем пространстве, чтобы поместить артефакты в свою собственную директорию target. С помощью разделения одной директории target ящики могут избежать ненужной перекомпиляции.
Создание второго пакета в рабочем пространстве
Далее, давайте создадим еще один пакет-член в рабочем пространстве и назовем его add_one. Измените верхнеуровневый Cargo.toml, чтобы указать путь к add_one в списке members:
Имя файла: Cargo.toml
[workspace]
members = [
"adder",
"add_one",
]
Затем сгенерируйте новый библиотечный ящик под названием add_one:
$ cargo new add_one --lib
Created library $(add_one) package
Теперь в директории add должны быть следующие директории и файлы:
├── Cargo.lock
├── Cargo.toml
├── add_one
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
├── adder
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── target
В файле add_one/src/lib.rs давайте добавим функцию add_one:
Имя файла: add_one/src/lib.rs
pub fn add_one(x: i32) -> i32 {
x + 1
}
Теперь в пакете adder с нашим бинарным файлом можно сделать его зависящим от пакета add_one, в котором находится наша библиотека. Во - первых, нам нужно добавить зависимость по пути на add_one в adder/Cargo.toml:
Имя файла: adder/Cargo.toml
[dependencies]
add_one = { path = "../add_one" }
Cargo не предполагает, что ящики в рабочем пространстве будут зависеть друг от друга, поэтому мы должны явно указывать зависимости.
Далее, давайте используем функцию add_one (из ящика add_one) в ящике adder. Откройте файл adder/src/main.rs и добавьте строку use в начале, чтобы включить новый библиотечный ящик add_one в область видимости. Затем измените функцию main, чтобы вызвать функцию add_one, как показано в Листинге 14-7.
Имя файла: adder/src/main.rs
use add_one;
fn main() {
let num = 10;
println!(
"Hello, world! {num} plus one is {}!",
add_one::add_one(num)
);
}
Листинг 14-7: Использование библиотеки add_one из ящика adder
Построим рабочее пространство, выполнив cargo build в верхнеуровневой директории add!
$ cargo build
Compiling add_one v0.1.0 (file:///projects/add/add_one)
Compiling adder v0.1.0 (file:///projects/add/adder)
Finished dev [unoptimized + debuginfo] target(s) in 0.68s
Чтобы запустить бинарный ящик из директории add, мы можем указать, какой пакет в рабочем пространстве мы хотим запустить, используя аргумент -p и имя пакета с cargo run:
$ cargo run -p adder
Finished dev [unoptimized + debuginfo] target(s) in 0.0s
Running `target/debug/adder`
Hello, world! 10 plus one is 11!
Это запускает код в adder/src/main.rs, который зависит от ящика add_one.
Зависимость от внешнего пакета в рабочем пространстве
Заметим, что рабочее пространство имеет только один файл Cargo.lock на верхнем уровне, а не отдельный Cargo.lock в каждой директории ящика. Это гарантирует, что все ящики используют одну и ту же версию всех зависимостей. Если мы добавим пакет rand в файлы adder/Cargo.toml и add_one/Cargo.toml, Cargo будет разрешать обе эти зависимости на одну версию rand и записывать это в единственный Cargo.lock. Тому, что все ящики в рабочем пространстве используют одинаковые зависимости, означает, что ящики всегда будут совместимы друг с другом. Добавим пакет rand в раздел [dependencies] в файле add_one/Cargo.toml, чтобы мы могли использовать пакет rand в ящике add_one:
Имя файла: add_one/Cargo.toml
[dependencies]
rand = "0.8.5"
Теперь мы можем добавить use rand; в файл add_one/src/lib.rs, и сборка всего рабочего пространства с помощью cargo build в директории add будет включать и компилировать пакет rand. Мы получим одно предупреждение, потому что мы не обращаемся к rand, который мы добавили в область видимости:
$ cargo build
Updating crates.io index
Downloaded rand v0.8.5
--snip--
Compiling rand v0.8.5
Compiling add_one v0.1.0 (file:///projects/add/add_one)
Compiling adder v0.1.0 (file:///projects/add/adder)
Finished dev [unoptimized + debuginfo] target(s) in 10.18s
Теперь верхнеуровневый Cargo.lock содержит информацию о зависимости add_one от rand. Однако, даже если rand используется где-то в рабочем пространстве, мы не можем использовать его в других ящиках в рабочем пространстве, если не добавим rand в их файлы Cargo.toml также. Например, если мы добавим use rand; в файл adder/src/main.rs для пакета adder, мы получим ошибку:
$ cargo build
--snip--
Compiling adder v0.1.0 (file:///projects/add/adder)
error[E0432]: unresolved import `rand`
--> adder/src/main.rs:2:5
|
2 | use rand;
| ^^^^ no external crate `rand`
Чтобы исправить это, отредактируйте файл Cargo.toml для пакета adder и укажите, что rand также является зависимостью для него. Сборка пакета adder добавит rand в список зависимостей для adder в Cargo.lock, но дополнительные копии rand не будут загружены. Cargo гарантирует, что каждый ящик в каждом пакете в рабочем пространстве, использующем пакет rand, будет использовать одну и ту же версию, экономя таким образом место и обеспечивая совместимость ящиков в рабочем пространстве.
Добавление теста в рабочее пространство
Для следующего улучшения давайте добавим тест функции add_one::add_one внутри ящика add_one:
Имя файла: add_one/src/lib.rs
pub fn add_one(x: i32) -> i32 {
x + 1
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
assert_eq!(3, add_one(2));
}
}
Теперь запустите cargo test в верхнеуровневой директории add. Запуск cargo test в рабочем пространстве, структурированном так, как это одно, запустит тесты для всех ящиков в рабочем пространстве:
[object Object]
Первая часть вывода показывает, что тест it_works в ящике add_one прошел. Следующая часть показывает, что в ящике adder не было найдено ни одного теста, а затем последняя часть показывает, что в ящике add_one не было найдено ни одного документационного теста.
Мы также можем запускать тесты для одного конкретного ящика в рабочем пространстве из верхнеуровневой директории, используя флаг -p и указывая имя ящика, который мы хотим протестировать:
[object Object]
Этот вывод показывает, что cargo test запустил только тесты для ящика add_one и не запустил тесты для ящика adder.
Если вы публикуете ящики в рабочем пространстве на https://crates.io, каждый ящик в рабочем пространстве должен быть опубликован отдельно. Как и cargo test, мы можем опубликовать конкретный ящик в нашем рабочем пространстве, используя флаг -p и указывая имя ящика, который мы хотим опубликовать.
Для дополнительной практики добавьте ящик add_two в это рабочее пространство аналогичным образом, как и ящик add_one!
По мере роста вашего проекта рассмотрите использование рабочего пространства: оно предоставляет более понятные, более мелкие отдельные компоненты, чем один большой кусок кода. Кроме того, хранение ящиков в рабочем пространстве может облегчить координацию между ящиками, если они часто изменяются одновременно.
Резюме
Поздравляем! Вы завершили лабораторную работу по Cargo Workspaces. Вы можете выполнить больше лабораторных работ в LabEx, чтобы улучшить свои навыки.