Как публиковать свои пакеты на Crates.io

• Михаил Панков • обучение • поддержите на Patreon

teaser

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

Это цикл статей:

Содержание

Настройка простейшего проекта

Создаём проект Cargo с нашей библиотекой.

1
$ cargo new rustycrate-ru

Появляется директория с нашим пакетом.

Точку в имени использовать нельзя:

1
2
3
$ cargo new rustycrate.ru
error: Invalid character `.` in crate name: `rustycrate.ru`
use --name to override crate name
1
2
$ ls -lad rustycrate-ru/
drwxrwxr-x 4 mkpankov mkpankov 4096 дек 25 15:36 rustycrate-ru/

Нам не важно содержимое библиотеки. Добавим одну функцию чтобы потом удостовериться, что библиотека действительно работает.

src/lib.rs:

1
2
3
4
5
6
7
8
9
10
fn hello() {
    println!("Привет от rustycrate.ru!");
}

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
    }
}

Регистрация на Crates.io

Кликаем «Log in with GitHub».

crates.io

В ответ открывается окно подтверждения доступа crates.io к вашему аккаунту на GitHub.

подтверждение доступа

Подтвердите, и Crates.io получит доступ к GitHub.

  1. Отключите блокировку кук и скриптов в браузере.
  2. Удалите куки.
  3. Отзовите у Crates.io доступ к GitHub на странице настроек (кнопка Revoke).
  4. Попробуйте ещё раз.

Вход в учётную запись Crates.io

Наведите мышь на название вашего аккаунта в правом верхнем углу. Появится всплывающее меню, где нужно выбрать «Dashboard».

как попасть в панель управления

Для аутентификации Cargo нужен токен.

панель управления

Нажмите New Token, чтобы получить его. Введите имя токена — оно поможет опознать токен среди всех, которые у вас есть. После нажатия на Create токен создаётся и появляется командная строка, которую можно просто скопировать.

1
cargo login <ваш токен>

В случае проблем проверьте соединение с интернетом и наличие этого токена в списке в панели управления.

В случае раскрытия токена посторонним, его нужно отозвать с помощью кнопки Revoke.

Упаковка пакета

Подготовим пакет для публикации.

1
2
3
4
5
6
7
8
$ cargo package
warning: manifest has no description, license, license-file, documentation, homepage or repository. See http://doc.crates.io/manifest.html#package-metadata for more info.
error: 2 dirty files found in the working directory:

Cargo.toml
src/lib.rs

to publish despite this, pass `--allow-dirty` to `cargo publish`

Как видите, только что созданный пакет упаковать не получается.

Во-первых, появляется предупреждение об отсутствующих в манифесте метаданных: описании, лицензии, файле лицензии и ссылках на документацию, домашнюю страницу и репозиторий.

Во-вторых, в рабочей директории 2 грязных файла — они не закоммичены. При создании пакета Cargo делает там репозиторий Git, но не создаёт коммит.

С предупреждением можно пока ничего не делать.

С ошибкой можно разобраться двумя способами:

  1. Передать флаг --allow-dirty команде cargo package. Это стоит делать, пока вы экспериментируете и проверяете, что манифест успешно парсится.
  2. Закоммитить файлы в репозиторий. Когда эксперименты завершены, файлы стоит закоммитить, чтобы после публикации пакета локальное состояние было согласовано с общедоступным. Например, номер версии на crates.io и локально в манифесте совпадали.

При успешном выполнении cargo package в директории target/package появляется файл *.crate.

На Crates.io ограничение на размер загружаемого пакета — 10 МБ.

При упаковке Cargo игнорирует файлы, которые игнорирует система контроля версий вашего репозитория.

Если вы хотите дополнительно игнорировать другие файлы, укажите их в манифесте:

1
2
3
4
5
6
[package]
# ...
exclude = [
    "public/assets/*",
    "videos/*",
]

Если вы хотите использовать список включения вместо списка исключения, это тоже можно сделать:

1
2
3
4
5
6
[package]
# ...
include = [
    "**/*.rs",
    "Cargo.toml",
]

Публикация изначальной версии

Попытаемся опубликовать пакет с нашим текущим манифестом:

Cargo.toml:

1
2
3
4
5
6
[package]
name = "rustycrate-ru"
version = "0.1.0"
authors = ["Michael Pankov <work@michaelpankov.com>"]

[dependencies]

Вот результат:

1
2
3
4
5
6
7
8
$ cargo publish
    Updating registry `https://github.com/rust-lang/crates.io-index`
warning: manifest has no description, license, license-file, documentation, homepage or repository. See http://doc.crates.io/manifest.html#package-metadata for more info.
   Packaging rustycrate-ru v0.1.0 (file:///tmp/rustycrate-ru)
   Verifying rustycrate-ru v0.1.0 (file:///tmp/rustycrate-ru)
   Compiling rustycrate-ru v0.1.0 (file:///tmp/rustycrate-ru/target/package/rustycrate-ru-0.1.0)
   Uploading rustycrate-ru v0.1.0 (file:///tmp/rustycrate-ru)
error: api errors: missing or empty metadata fields: description, license. Please see http://doc.crates.io/manifest.html#package-metadata for how to upload metadata

Как видите, ошибка возникает из-за отсутствия в манифесте полей описания пакета (description) и лицензии (license).

Давайте добавим эти поля:

Cargo.toml:

1
2
3
4
5
6
[package]
...
description = "Демо-библиотека для статьи о публикации пакетов на rustycrate.ru"
license = "BSD-3-Clause"

[dependencies]

Список идентификаторов стандартных лицензий можно найти на сайте opensource.org.

Также вместо license можно указать license-file — относительный путь к файлу с текстом лицензии.

Это позволяет успешно залить пакет на crates.io:

1
2
3
4
$ cargo publish --allow-dirty
    Updating registry `https://github.com/rust-lang/crates.io-index`
warning: manifest has no documentation, homepage or repository. See http://doc.crates.io/manifest.html#package-metadata for more info.
   Uploading rustycrate-ru v0.1.0 (file:///home/mkpankov/stream/rustycrate-ru)

Обратите внимание, что Cargo всё ещё выдаёт предупреждение об отсутствии ссылок на документацию (поле documentation), домашнюю страницу (homepage) и репозиторий (repository).

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

Для большинства пакетов достаточно указать ссылку на docs.rs. Этот сайт автоматически генерирует документацию. После того, как опубликуете пакет, зайдите на сайт и введите в поиске название вашего пакета. Возможно, нужно будет подождать какое-то время, т. к. обработка новых пакетов происходит не мгновенно.

Проверка опубликованной библиотеки

Создадим проект программы, чтобы воспользоваться в ней нашей библиотекой:

1
$ cargo new --bin rustycrate-ru-test

Напишем такой код:

main.rs:

1
2
3
4
5
extern crate rustycrate_ru;

fn main() {
    rustycrate_ru::hello();
}

Обратите внимание на имя пакета: дефис в нём заменён на нижнее подчёркивание. В манифесте нашей библиотеки имя пакета — rustycrate-ru.

Дефис не является допустимым символом в идентификаторе пакета в исходном коде. Его можно использовать только в названии, которое указано в манифесте. По этому названию пакет находит Cargo.

Попробуем собрать эту программу:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ cargo build
   Compiling rustycrate-ru-test v0.1.0 (file:///tmp/rustycrate-ru-test)
error[E0463]: can't find crate for `rustycrate_ru`
 --> src/main.rs:1:1
  |
1 | extern crate rustycrate_ru;
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ can't find crate

error: aborting due to previous error

error: Could not compile `rustycrate-ru-test`.

To learn more, run the command again with --verbose.

Здесь проблема в том, что мы сказали подключить пакет rustycrate_ru, но его нет в стандартной поставке Rust. Некоторые пакеты — например, core и std — там есть.

Чтобы Cargo смог найти нашу библиотеку, её нужно потребовать в манифесте:

Cargo.toml:

1
2
3
4
...

[dependencies]
rustycrate-ru = "0.1.0"

Мы требуем библиотеку версии 0.1.0, или любой совместимой по правилам SemVer. Изменение последнего компонента версии считается совместимым по умолчанию, поэтому под данную спецификацию версии подойдёт и версия 0.1.1.

Попробуем собрать ещё раз:

1
2
3
4
5
6
7
8
9
$ cargo build
    Updating registry `https://github.com/rust-lang/crates.io-index`
   Compiling rustycrate-ru v0.1.1
   Compiling rustycrate-ru-test v0.1.0 (file:///tmp/rustycrate-ru-test)
error[E0603]: function `hello` is private
 --> src/main.rs:4:5
  |
4 |     rustycrate_ru::hello();
  |     ^^^^^^^^^^^^^^^^^^^^

Теперь проблема в том, что в библиотеке мы не сделали функцию hello() общедоступной. Её могут вызывать только изнутри библиотеки.

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

Сокрытие опубликованной версии (yank)

С помощью команды cargo yank можно «убрать» версию из репозитория crates.io. В кавычках — потому что на самом деле она никуда не денется. Т.е. если вы зашили в код пароль к чему-то, то вам нужно срочно поменять этот пароль, т. к. код не будет удалён из репозитория crates.io.

Когда пользователи будут указывать вашу библиотеку в зависимостях, на их стороне Cargo не будет рассматривать версии, которые убраны (yanked).

Таким образом, если у библиотеки есть версии 0.1.0, 0.1.1 и 0.1.2, но 0.1.2 убрана автором, то будет использоваться версия 0.1.1. Однако те, кто уже однажды собрал свой проект с 0.1.2, смогут и дальше использовать её, т. к. Cargo уже закэшировал конкретную версию.

Конкретный синтаксис команды таков:

1
2
$ cargo yank --vers 1.0.1
$ cargo yank --vers 1.0.1 --undo

К сожалению, в нашем случае нет ни одной версии, где бы не было ошибки. И в 0.1.0, и в 0.1.1 функция hello() не является общедоступной. Поэтому убирать версии бессмысленно. Давайте исправим ошибку.

Исправляем видимость функции

В проекте нашей библиотеки rustycrate-ru делаем такие изменения:

1
2
3
4
5
6
7
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,4 +1,4 @@
-fn hello() {
+pub fn hello() {
     println!("Привет от rustycrate.ru!");
 }

Коммитим их.

Публикация новой версии

Изменяем номер версии в манифесте на 0.1.2, коммитим это изменение, и публикуем новую версию библиотеки.

Здесь важно, что не получится заново опубликовать библиотеку с тем же номером версии. При каждой новой публикации номер версии должен увеличиваться.

Ваша ответственность, как автора библиотеки — изменять номер версии по правилам SemVer.

Проверка исправленной версии

Теперь можно заново попытаться собрать тестовую программу.

Простое выполнение cargo build нам не поможет, т. к. Cargo уже сохранил конкретную версию библиотеки в файле Cargo.lock.

Обновим используемую версию библиотеки:

1
2
3
$ cargo update
    Updating registry `https://github.com/rust-lang/crates.io-index`
    Updating rustycrate-ru v0.1.1 -> v0.1.2

Теперь проверяем опять:

1
2
3
4
5
6
7
8
9
$ cargo build
   Compiling rustycrate-ru v0.1.2
   Compiling rustycrate-ru-test v0.1.0 (file:///tmp/rustycrate-ru-test)
    Finished dev [unoptimized + debuginfo] target(s) in 0.58 secs

$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
     Running `target/debug/rustycrate-ru-test`
Привет от rustycrate.ru!

Всё работает!

Репозиторий с результатом

Репозиторий демонстрационной библиотеки.


На этом всё. Успешной публикации!


Другие статьи цикла:

Задавайте вопросы на форуме.