LoginSignup
44
19

crates.ioにクレートを公開するまで

Last updated at Posted at 2019-12-03

この記事はRust Advent Calendar 2019の4日目の記事です。

こんにちは、ピクシブの福岡オフィスでエンジニアをしている@tasshiです。
最近はNintendo Switchのリングフィットアドベンチャーがマイブームです。1

概要

Rustのライブラリクレートを作って、crates.ioに公開するまでの手順を紹介します。
基本的にはthe bookのPublishing a Crate to Crates.ioに従って進めていきます。2

背景

今年の11月にcrates.ioにfittingという曲線近似ライブラリを公開しました。
実際にクレートを公開してみて得られた知見があったので、共有しようと思い記事を書いてみました。

fitting-rs.png

crates.ioについて

crates.ioはRustにおけるパッケージのデフォルトリポジトリです。
the bookのチュートリアルで使用するrandクレートなどもここに公開されています。

Rustのパッケージマネージャ(兼ビルドツール)のCargoは、デフォルトでcrates.ioを参照するようになっています。
そのため、Cargo.tomlに依存関係を書くときや、cargo installでコマンドラインツールをインストールするときなど、様々な場面で必ずと言っていいほどお世話になるサイトがこのcrates.ioです。

crates.io.png

手順

ざっくりと次のような順番で進めていきます。

  1. クレートを実装する
  2. ドキュメンテーションコメントを書く
  3. メタデータを追加する
  4. crates.ioのアカウントをセットアップする
  5. クレートを公開する
  6. [おまけ](#おまけ1: CIを設定する)

クレートを実装する

まずはライブラリのコードを実装します。
とは言ったものの、実際の開発だとクレートを作ろうと思った時点で既に素体となるコードはあるので、ここでは公開するための修正・リファクタリングを行います。

バイナリクレートとライブラリクレート

Rustのクレートにはバイナリクレートとライブラリクレートとがあります。

クレートのタイプ クレートのルート 説明
バイナリクレート src/main.rs main関数が含まれる(プログラムのエントリーポイント)
ライブラリクレート src/lib.rs 機能群が含まれる

バイナリクレートは実行可能なバイナリを提供するためのクレートで、cargo installでインストールして実行したり、cargo runでローカルでコンパイルして実行したりすることができます。
ライブラリクレートは他のプログラムで取り込むことに適した機能群を提供するためのクレートで、Cargo.tomldependenciesフィールドに追加して使用することができます。

公開するクレートはこれらのどちらか、もしくは両方を含むことができます。

エラーハンドリングを行う

試験的に使用しているunwrap()expect()などのpanicするメソッドを消し、ResultOptionを返すように置き換えます。
特にライブラリクレートではpanicするのはなるべく避けます。

panicするべき状態についてはthe bookのGuidelines for Error Handlingを参照します。

そのほかに注意すること

そのほかに注意する点として、以下のようなものがあります。

  • 単体テスト、結合テストを書く
  • Cargo.tomlの依存関係のうち、クレート本体に不要な物を[dev-dependencies]に移動する

ドキュメンテーションコメントを書く

コードの実装が終わったらドキュメンテーションコメントを書きます。

ドキュメンテーションコメントはdocs.rsに公開されるクレートのドキュメントのために書きます。
crates.ioにクレートを公開すると、ドキュメンテーションコメントを元にdocs.rsにドキュメントが自動生成されます。

ドキュメンテーションコメントの記法

詳しくはWhat is rustdoc? -を参照してください。

  • 以下に対するドキュメントは3連スラッシュ(///)で始める
  • 親に対するドキュメントは2連スラッシュ+!(//!)で始める
  • マークダウンをサポート
  • いくつかのマークダウンタイトルは特別な意味をもつ

例えば# Exampleタイトルで始まるセクションは関数などの使い方をコードを載せて例示することができます。

ドキュメンテーションコメントの例

sample.rs
/// Returns a value of gaussian function.
///
/// # Examples
/// Returns a value of gaussian function.
///
/// ```
/// use fitting::gaussian::val;
///
/// let (mu, sigma, a): (f64, f64, f64) = (5., 3., 1.);
/// let x = 5.;
/// let y = val(x, mu, sigma, a);
/// assert_eq!(y, a);
///
/// ```
pub fn val(x: f64, mu: f64, sigma: f64, a: f64) -> f64 {
    a * (-(x - mu).powi(2) / (2. * sigma.powi(2))).exp()
}

生成されるドキュメントの例

documentation_comment_sample.png

メタデータを追加する

次にCargo.tomlにクレートのメタ情報を記入していきます。
元々記入済のフィールドに加えて、descriptionlicenseとの2つは最低限必要になります。

主要なフィールド

フィールド名 説明
name crates.ioにおけるクレートの名前
description 検索結果に表示されるクレートの名前
keywords 自由入力で5つまで設定可能
categories category_slugsの中から、5つまで設定可能
license ライセンス

その他にもたくさんフィールドはあるので公式ページを参照して適宜追加してください。

公式ページ:The Manifest Format - The Cargo Book

クレートの名前について

Cargo.tomlnameフィールドはcrates.ioにおけるそのクレートの名前です。
この名前はcrates.ioにおいては、ユニークである必要があり、かつ確保は早い物勝ちになっています。
そのため、事前にcrates.ioで被っていないか確認してからクレートの名前を決定するのが、公開直前の名前変更などのトラブルを避ける意味でも良さそうです。

備考:crates.ioにはSquattingのポリシーが存在しないので、放置された名前の開放はないそうです。3

ライセンスについて

クレートのライセンスですが、Rust APIガイドラインによればMIT/Apache2.0のデュアルライセンスが最も望ましく、そうでない場合もMITライセンスやBSDライセンスなどの寛容な(permissive)ライセンスを使用することを推奨しています。

Necessities - Rust API Guidelines

サンプル

以下にサンプルとして、fittingクレートのメタ情報を載せておきます。

Cargo.toml(一部抜粋)
[package]
name = "fitting"
version = "0.1.0"
authors = ["Rustacean Smith <rustacean@example.com>"]
edition = "2018"

description = "Pure Rust curve fitting library"

documentation = "https://docs.rs/fitting/"
homepage = "https://crates.io/crates/fitting"
repository = "https://github.com/tasshi-me/fitting-rs"

readme = "README.md"

keywords = ["fitting","statistics","probability","distribution","math"]
categories = ["science"]
license = "MIT"

[badges]
maintenance = { status = "actively-developed" }

[dependencies]
ndarray = { version = "0.13.0", features = ["approx"] }
approx = "0.3.2"
failure = "0.1.6"

crates.ioのアカウントをセットアップする

いよいよ公開まであと少しです。
crates.ioのアカウントを作成して、ローカルPCのターミナルでログインします。

手順

crates.io_startup.png crates.io_new_api_key.png
  • ローカルでターミナルを開いて以下のコマンドを実行します
cargo login <Your Access Token>

クレートを公開する

以上で準備は完了です。
公開したいプロジェクトのルートで下記のコマンドを実行しましょう。

❯ cargo publish

ドキュメントの生成、テストに問題がなければパッケージがアップロードされて公開完了です。
https://crates.io/crates/{crate_name} にアクセスしてクレートが公開されていることを確認しましょう。
docs.rsへの反映には5~10分程度時間がかかるのでしばらくしてから確認してみてください。

おまけ1: CIを設定する

クレートは公開できましたが、やっぱりCIを使ってテストを自動化したいですよね。
というわけでCIの設定をしていきます。

CIサービスの選定について

目視で確認した限りだとTravis CIかAppveyorを利用しているクレートが多いです。
crates.ioがビルドステータスの表示に対応しているのは以下の6サービス(2019年11月)
| Appveyor | CircleCI | Cirrus CI | GitLab | Azure DevOps | TravisCI |

今回はTravis CIを使っていきます。

Travis CIの設定

Travis CIでは設定ファイルでlanguage: rustを設定するとbuildやtestは勝手に実行してくれます。
そのため設定ファイルでは

  • Rustの実行環境の指定
  • キャッシュの設定
  • コードフォーマッタやLintの実行

を設定していきます

Rustの実行環境の指定

stable, beta, nightlyそれぞれでビルドします。
nightlyではビルドの失敗を許容します

rust:
  - stable
  - beta
  - nightly

jobs:
  allow_failures:
    - rust: nightly

キャッシュの設定

キャッシュを使ってビルド時間の高速化を図ります。
キャッシュの設定についてはTravis CI公式の方法よりも、Qiitaで見た指定方法のほうが良さそうだったのでそちらを使用しています。

参考記事:Travis CIでのRust cacheとうまく付き合う - Qiita

cache:
  directories:
    - ${HOME}/.cargo
    - ${HOME}/.rustup
before_cache:
  - rm -rf /home/travis/.cargo/registry

コードフォーマッタやLintの実行

before_scriptタグで、ビルド直前に処理を追加します。
今回はコードフォーマッタ(cargo fmt)とLint(cargo clippy)を実行させています。

before_script: |
  cargo fmt --version
  cargo fmt -- --check
  cargo clippy --version
  cargo clippy
  cargo clean

最終的なTravis CIの設定サンプル

.travis.yml(一部抜粋)
language: rust

rust:
  - stable
  - beta
  - nightly

cache:
  directories:
    - ${HOME}/.cargo
    - ${HOME}/.rustup
before_cache:
  - rm -rf /home/travis/.cargo/registry

jobs:
  allow_failures:
    - rust: nightly
  fast_finish: true

before_script: |
  cargo fmt --version
  cargo fmt -- --check
  cargo clippy --version
  cargo clippy
  cargo clean

CIの確認

.travis.ymlをコミットしたら、Travis CIにいってビルドが正常に動いているのかを確認します。

travis.png

おまけ2: コードカバレッジを計測する

コードカバレッジも計測します。

カバレッジサービスはCodecovCoverallsを使います。

計測ツールについては現在主流なものは以下の3つです。それぞれ使える環境に違いがあるので自分に適した物を選びます。

name os comment
tarpaulin Linux nightly推奨
cargo-cov FreeBSD, Linux, macOS, Windows nightly推奨4
cargo-kcov Linux, macOS PersonalityというDockerコンテナ内で呼び出せないシステムコールを使っている56

僕はnightlyをあまり使いたくなかったのでcargo-kcov + Travis CI + Codecovでカバレッジを計測しました。

cargo-kcov + Travis CI + Codecovの設定サンプル

cargo-kcovではバックエンドとしてkcovを使う必要があります。

Codecovの提供するサンプルコードでは、毎回kcovのソースをmakeしていて、あまりビルドが速くなかったため、キャッシュを使うように変更しています。

.travi.yml

cache:
  directories:
    - ${HOME}/kcov/ # kcovのインストールディレクトリ

env:
  - PATH=$PATH:${HOME}/kcov/bin # 環境変数にkcovのディレクトリを加える

before_install: | # もし${HOME}/kcov/binがなかったら、kcovをmake installする
  if [ ! -d "${HOME}/kcov/bin" ]; 
  then 
  echo "Install kcov" &&
  wget https://github.com/SimonKagstrom/kcov/archive/master.tar.gz &&
  tar xzf master.tar.gz &&
  cd kcov-master &&
  mkdir build &&
  cd build &&
  cmake -DCMAKE_INSTALL_PREFIX=${HOME}/kcov .. &&
  make &&
  make install &&
  cd ../.. &&
  rm -rf kcov-master;
  fi

after_success: | # カバレッジ計測の実行 & Codecovへのアップロード
  cargo kcov --no-clean-rebuild --verbose --all --no-fail-fast \
  --output ./target/cov -- --verify --include-path=. --exclude-path=./target \
  --exclude-region=kcov-ignore-begin:kcov-ignore-end --exclude-line=kcov-ignore-line &&
  bash <(curl -s https://codecov.io/bash) &&
  echo "Uploaded code coverage"

cargo-kcovのインストールについて

cargo installはビルドシステムであってパッケージのバージョン管理ツールではないため、同名のクレートのinstallはエラーになってしまいます。
そのためこの設定ファイルでは、ローカルのcargo-kcovとリモートのcargo-kcovのバージョンを比較して、バージョンが違う場合のみ再インストールを実行するような処理を実装しました。

補足:cargo-updateというクレートがあるらしく、これをインストールすればいいらしいです。

before_install: |
  if ! type cargo-kcov > /dev/null;
  then
  echo "Install cargo-kcov" &&
  cargo install cargo-kcov -f;
  else
  echo "Check cargo-kcov is latest"
  REMOTE_KCOV_VERSION=`cargo search cargo-kcov --limit 1 | sed -e 's/^cargo-kcov = "\(.*\)".*/\1/'`
  LOCAL_KCOV_VERSION=`cargo-kcov --version | sed -e 's/^cargo-kcov \(.*\).*/\1/'`
  if [ ! $REMOTE_KCOV_VERSION = $LOCAL_KCOV_VERSION ];
  then
  echo "Upgrade cargo-kcov" &&
  cargo install cargo-kcov -f;
  fi
  fi

カバレッジの確認

ここまででカバレッジ計測の設定も完了です。
あとはCodecovにいってカバレッジが取れているかを確認します。

codecov.png

まとめ

クレートを公開してみた感想ですが、公式のドキュメントがしっかりしていたので公開までは意外と簡単にできました。
一方で、CIやカバレッジ計測に関しては自分で調査しないといけないようなハマりどころも多く、納得して動かせるようになるまでにかなり苦戦しました。

また、crates.ioについて、ユーザで名前空間が別れていないので強い名前をつけてしまうとかなり目立ってしまうという印象を受けました。名前空間を占拠しているという自覚を持ってメンテナンスに励みたいと思います。

公開自体は簡単なので、「欲しいライブラリがcrates.ioで見つからない、、」という方は、ぜひ自分で実装して公開してみてはいかがでしょうか?

ここまで読んでいただきありがとうございました。

  1. リングフィット アドベンチャー | Nintendo Switch | 任天堂

  2. Publishing a Crate to Crates.io - The Rust Programming Language

  3. Crates.io Package Policies - crates.io

  4. kennytm/cov: LLVM-GCOV Source coverage for Rust

  5. Executing kcov in a docker image fails · Issue #151 · SimonKagstrom/kcov

  6. Seccomp security profiles for Docker | Docker Documentation

44
19
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
44
19