Способы указания зависимостей

Ваши контейнеры (crates) могут зависеть от других библиотек с crates.io, git репозиториев, или поддиректорий в локальной файловой системе. Вы также можете временно перезаписать расположение зависимости, например, чтобы проверить исправление в пакете, с которым вы работаете локально. Вы можете указать зависимости для различных платформ, и зависимости, используемые только при разработке. Давайте посмотрим, как этим управлять.

Указание зависимостей с crates.io

Cargo по-умолчанию настроен на поиск зависимостей по crates.io. В этом случае требуется только название и строка, содержащая номер версии. В руководстве по Cargo, мы указали зависимость от контейнера time:

[dependencies]
time = "0.1.12"

Строка "0.1.12" является строкой указания семантической версии, также известной как SemVer. Поскольку эта строка не содержит каких-либо операторов, то эта строка интерпретируется также, как если бы мы указали строку "^0.1.12", которую называют мажорным ограничением. Подробнее о семантическом версионировании можно прочитать на сайте semver.org.

Мажорные ограничения

Мажорное ограничение разрешает совместимые обновления указанной версии. Обновление допускается, если в номере новой версии не изменяется самая левая ненулевая цифра в группе мажорная.минорная.патч. В этом случае, если мы запустим cargo update -p time, cargo обновит пакет до 0.1.13, если он доступен, но не обновит до 0.2.0. Иначе, если указать строку версии как ^1.0, cargo обновит до 1.1, но не 2.0. 0.0.x считается несовместимым с любой другой версией.

Вот несколько примеров мажорных ограничений и версий, которые оно разрешает:

^1.2.3 := >=1.2.3 <2.0.0
^1.2 := >=1.2.0 <2.0.0
^1 := >=1.0.0 <2.0.0
^0.2.3 := >=0.2.3 <0.3.0
^0.0.3 := >=0.0.3 <0.0.4
^0.0 := >=0.0.0 <0.1.0
^0 := >=0.0.0 <1.0.0

Хотя семантическое версионирование подразумевает, что нет совместимости до 1.0.0, многие программисты относятся к релизам 0.x.y также, как к 1.x.y релизам: то есть y увеличивается для обратно совместимых баг-фиксов, а x увеличивается при добавлении новых возможностей. Таким образом, Cargo считает 0.x.y и 0.x.z версии совместимыми, еслиz > y.

Минорные ограничения

Минорное ограничение указывает минимальную версию с некоторой возможностью обновления. Если вы указываете мажорную версию, минорную и номер патча или только мажорную и минорную версию, то допускаются изменения только на уровне версии патча. Если вы указываете только мажорный номер версии, то разрешается изменение на уровне минорной версии и версии патча.

~1.2.3 является примером минорного ограничения.

~1.2.3 := >=1.2.3 <1.3.0
~1.2 := >=1.2.0 <1.3.0
~1 := >=1.0.0 <2.0.0

Позиционные ограничения

Позиционное ограничение разрешает любую версию в соответствии с шаблоном.

*, 1.* и 1.2.* являются примерами позиционного ограничения.

* := >=0.0.0
1.* := >=1.0.0 <2.0.0
1.2.* := >=1.2.0 <1.3.0

Ограничения неравенством

Ограничение неравенством позволяет вручную указывать допустимый диапазон версий или конкретную версию, которая требуется.

Ниже несколько примеров ограничения неравенством:

>= 1.2.0
> 1
< 2
= 1.2.3

Несколько ограничений

Несколько ограничений версии могут быть разделены через запятую, например, >= 1.2, < 1.5.

Указание зависимостей из git репозиториев

Для указания зависимости от библиотеки, расположенной в git репозитории, вы должны, как минимум, указать расположение репозитория с помощью ключа git:

[dependencies]
rand = { git = "https://github.com/rust-lang-nursery/rand" }

Cargo загрузит git репозиторий, а затем будет искать Cargo.toml для запрошенного контейнера в любом месте git репозитория (не обязательно в корне).

Поскольку мы не указали никакой другой информации, Cargo предполагает, что мы намерены использовать последний коммит в ветке master для сборки нашего проекта. Вы можете комбинировать ключ git с ключами rev, tag или branch, чтобы указать что-то ещё. Вот пример указания того, что вы хотите использовать последний коммит из ветки next:

[dependencies]
rand = { git = "https://github.com/rust-lang-nursery/rand", branch = "next" }

Указание локальных зависимостей

Со временем наш проект hello_world из руководства значительно вырос! Понятно, что мы, вероятно, хотим разделить контейнер на несколько других. Для этого Cargo поддерживает пути до зависимостей, которые обычно являются под-контейнерами, которые живут в одном репозитории. Давайте начнем с создания нового контейнера внутри нашего проекта hello_world:

# в папке hello_world/
$ cargo new hello_utils

Это создаст новую папку hello_utils, внутри которой Cargo.toml и папка src будут готовы для настройки. Чтобы сообщить Cargo об этом, откройте hello_world/Cargo.toml и добавьте hello_utils в ваши зависимости:

[dependencies]
hello_utils = { path = "hello_utils" }

Это укажет Cargo, что наш проект зависит от контейнера с именем hello_utils, который находится в папке hello_utils (относительно Cargo.toml, в котором это указано).

Вот и всё! Следующий cargo build автоматически соберёт hello_utils и все свои зависимости, и другие контейнеры могут использовать под-контейнеры тем же образом. Однако, контейнеры, которые используют зависимости, указанные по локальному пути, не допускаются на crates.io. Если мы хотим опубликовать наш контейнер hello_world, мы должны сперва опубликовать hello_utils на crates.io (или указать адрес git репозитория) и указать версию в строке зависимости:

[dependencies]
hello_utils = { path = "hello_utils", version = "0.1.0" }

Переопределение зависимостей

Иногда вам может понадобиться переопределить одну из Cargo зависимостей. Допустим, вы работаете над проектом, используя контейнер uuid, который зависит от rand. Вы обнаружили ошибку в rand, и она уже исправлена, по пока не опубликована. Вы хотите проверить это исправление, поэтому давайте сначала посмотрим, как будет выглядеть ваш Cargo.toml:

[package]
name = "my-awesome-crate"
version = "0.2.0"
authors = ["The Rust Project Developers"]

[dependencies]
uuid = "0.2"

Чтобы переопределить зависимость rand контейнера uuid, мы будем использовать [секцию [replace]] replace-section в Cargo.toml, добавив это в конце:

[replace]
"rand:0.3.14" = { git = 'https://github.com/rust-lang-nursery/rand' }

Это означает, что rand версии 0.3.14, которую мы сейчас используем, будет заменена веткой master репозитория rand на GitHub. В следующий раз, когда вы выполните cargo build, Cargo возьмёт на себя проверку этого репозитория и сборку uuid с обновлённой версией.

Обратите внимание, что в секции [replace] переопределяемый контейнер должен иметь не только такое же имя, но и ту же версию, что и оригинальный. Это означает, что если в master ветке версия rand была обновлена до, скажем, 0.4.3, то вам необходимо выполнить несколько дополнительных шагов для тестирования контейнера:

  1. Создайте форк оригинального репозитория
  2. Создайте ветку, начинающуюся с релиза версии 0.3.14 (вероятно, отмечена тегом 0.3.14)
  3. Найдите исправление и отправьте его в вашу ветку
  4. В секции [replace] укажите ваш git репозиторий и ветку

Этот метод также может быть полезен при тестировании новых функций зависимости. Используя этот метод вы можете использовать ветку, в которой вы будете добавлять фичи, а затем, когда она будет готова, вы можете отправить pull-request в основной репозиторий. Пока вы будете ожидать одобрения pull-request, вы можете работать локально с использованием [replace], а затем, когда pull-request будет принят и опубликован, вы можете удалить секцию [remove] и использовать недавно опубликованную версию.

Примечание: в файле Cargo.lock будут перечислены две версии переопределённого контейнера: один для оригинального контейнера, а другой для версии, указанной в [replace]. С помощью cargo build -v можно проверить, что только одна версия используется при сборке.

Переопределение локальными зависимостями

Иногда вы только временно работаете над контейнером, и вы не хотите изменять Cargo.toml с помощью секции [replace], описанной выше. Для этого случая Cargo предлагает ограниченную версию переопределений, называемую путевыми переопределениями.

Как и раньше, предположим, вы работаете над проектом, использующем uuid, который зависит от rand. На этот раз вы тот, кто нашёл ошибку в контейнере rand и вы хотите написать патч и проверить его, используя вашу версию rand в контейнере uuid. Вот, как выглядит Cargo.toml в контейнере uuid:

[package]
name = "uuid"
version = "0.2.2"
authors = ["The Rust Project Developers"]

[dependencies]
rand = { version = "0.3", optional = true }

Вы тестируете локальную копию rand, скажем, в каталоге ~/src:

$ cd ~/src
$ git clone https://github.com/rust-lang-nursery/rand

Переопределение пути передаётся в Cargo через механизм конфигурации .cargo/config. Если Cargo обнаружит эту конфигурацию при сборке вашего пакета, он будет использовать переопределённый на вашей локальной машине путь вместо источника, указанного в Cargo.toml.

Cargo ищет каталог с именем .cargo вверх по иерархии каталогов вашего проекта. Если ваш проект находится в /path/to/project/uuid, он будет искать .cargo в:

Это позволяет вам указать свои переопределения в родительском каталоге, который включает в себя пакеты, которые вы обычно используете на локальном компьютере, и использовать их во всех проектах.

Чтобы указать переопределения, создайте файл .cargo/config у некоторого предка каталога вашего проекта (обычно его размещают в корневой директории вашего кода или в вашем домашнем каталоге).

Поместите это внутрь файла:

paths = ["/path/to/project/rand"]

Этот массив должен заполняться каталогами, содержащими Cargo.toml. В этом случае мы добавляем переопределение rand, поэтому этот контейнер будет единственным, который будет переопределен. Указанный путь должен быть абсолютным.

Однако переопределения пути более ограничены, чем секция [replace], тем более, что они не могут изменить структуру графика зависимостей. В заменяемом контейнере набор зависимостей должен быть точно таким же, как и в оригинальном. Например, это означает, что переопределения пути не могут использоваться для проверки добавления зависимостей к контейнеру, в этой ситуации следует использовать секцию [replace].

Примечание: использование локальной конфигурации для переопределения путей работает только для контейнеров, которые были опубликованы на crates.io. Вы не можете использовать эту функцию для локальных неопубликованных контейнеров.

Более подробную информацию о локальной конфигурации можно найти в документации по конфигурации.

Платформо-специфичные зависимости

Специфичные для платформы зависимости указываются в том же формате, как и обычные, но указаны под секцией target. Для определения этих секций используется обычный Rust-like синтаксис #[cfg]:

[target.'cfg(windows)'.dependencies]
winhttp = "0.4.0"

[target.'cfg(unix)'.dependencies]
openssl = "1.0.1"

[target.'cfg(target_arch = "x86")'.dependencies]
native = { path = "native/i686" }

[target.'cfg(target_arch = "x86_64")'.dependencies]
native = { path = "native/x86_64" }

Как и в случае с Rust, здесь поддерживаются операторы not,any и all для объединения различных пар имя/значение. Обратите внимание, что синтаксис cfg доступен только с Cargo 0.9.0 (Rust 1.8.0).

В дополнение к синтаксису #[cfg] Cargo также поддерживает перечисление полной цели, к которой будут относиться зависимости:

[target.x86_64-pc-windows-gnu.dependencies]
winhttp = "0.4.0"

[target.i686-unknown-linux-gnu.dependencies]
openssl = "1.0.1"

Если вы используете кастомную спецификацию целевой платформы, укажите полный путь и имя файла:

[target."x86_64/windows.json".dependencies]
winhttp = "0.4.0"

[target."i686/linux.json".dependencies]
openssl = "1.0.1"
native = { path = "native/i686" }

[target."x86_64/linux.json".dependencies]
openssl = "1.0.1"
native = { path = "native/x86_64" }

Зависимости для режима разработки

Вы можете добавить секцию [dev-dependencies] в свой Cargo.toml, формат которого эквивалентен [dependencies]:

[dev-dependencies]
tempdir = "0.3"

Dev-зависимости используются не при компиляции сборки, а при компиляции тестов, примеров и бенчмарков.

Эти зависимости не распространяются на другие пакеты, которые зависят от этого пакета.

Вы также можете указать платформо-специфичные зависимости, используя dev-dependencies вместо dependencies в секции target. Например:

[target.'cfg(unix)'.dev-dependencies]
mio = "0.0.1"

Зависимости для сборки

Вы можете объявить зависимость от других контейнеров на основе Cargo для их использования в сценариях сборки. Зависимости объявляются через раздел build-dependencies манифеста:

[build-dependencies]
gcc = "0.3"

Сценарий сборки не имеет доступа к зависимостям, указанным в секции dependencies или dev-dependencies. Зависимости сборки также не будут доступны для самого пакета, если они не указаны в разделе dependencies. Сам пакет и сценарий его сборки собираются отдельно, поэтому их зависимости могут не совпадать. Cargo.toml проще и чище, используя независимые зависимости для независимых целей.

Выбор возможностей (features)

Если пакет зависит от некоторых возможностей, вы можете выбрать, какие из них использовать:

[dependencies.awesome]
version = "1.3.5"
default-features = false # не включать стандартные возможности, и, опционально,
                         # включить указанные возможности
features = ["secure-password", "civet"]

Больше информации о возможностях можно найти в документации по manifest.