cargo-*系ツールの紹介

More than 1 year has passed since last update.

Rustのビルドシステムであるcargoは、PATHにcargo-なんとかというバイナリが存在するとサブコマンドとして使用できます。よくあるやつですね。便利なものがcrates.io上に公開されているので、いくつかご紹介したいと思います。


cargo install

その前に、cargo組み込みのコマンドであるcargo installの説明です。Rust製バイナリの導入が簡単に行えるコマンドです。cargoパッケージ1のディレクトリ内で

cargo install

と実行すると、そのパッケージをコンパイルし、成果物のバイナリを~/.cargo/binにインストールします。cargo uninstall バイナリ名でアンインストールできます。

crates.io上のパッケージをコンパイル&インストールするにはパッケージ名を指定します。

cargo install ripgrep

Gitリポジトリから取得することも可能です。

cargo install --git https://github.com/BurntSushi/ripgrep.git

注意点として、cargo installコマンドでは既に同名のバイナリが存在する場合-fオプションを付けない限り上書きされません。

この記事では項目毎にcrates.io上のパッケージ名を括弧の中に書いているので、気になるものがあればcargo install パッケージ名でインストールしてみてください。


cargo install-update (cargo-update)

さて、cargo installは便利なのですが、インストールしたものを一括でアップデートしてくれるような機能はありません。個々のパッケージが更新される度に手動で

cargo install -f なんとか

と打たなければなりません。cargoはパッケージマネージャではなくビルドシステムなので、あくまでmake install的なコマンドということのようです(更新機能を追加するissueは立てられていますが)。

しかしパッケージの更新を自分で調べてアップデートするのは不便なので、自動化するツールが存在します。cargo-updateです。パッケージ名と異なり、cargo-install-updateというバイナリがインストールされます。

使い方は簡単です。

cargo install-update -a

これだけでcrates.ioからインストールしたパッケージが更新されます。gitリポジトリからインストールしたものの自動更新は今の所できないようです。


cargo outdated (cargo-outdated)

Cargoのバージョン指定はSemVerなのですが、デフォルト(比較演算子を省略した場合)がnpmでいう^になっています。


Cargo.toml

[dependencies]

clap = "1.0.0" # >=1.0.0 <2.0.0
toml = "0.1.0" # >=0.1.0 <0.2.0

cargo updateを行うと、この制約に合致する範囲でcrates.io上の最新バージョンが選択されます。しかし、crates.io上にこの範囲を越える新しいバージョンが公開されていたとしてもcargoは教えてくれません。なので気が付いたら古いバージョンのライブラリを使い続けていたなんてことがあります。

そこで役立つのがcargo-outdatedです。cargo updateを実行すると、現在のプロジェクトが依存しているパッケージの新しいバージョンが公開されている場合に教えてくれます。

> cat Cargo.toml

[package]
name = "hello"
version = "0.1.0"

[dependencies]
clap = "1.0.0"
toml = "0.1.0"

> cargo update
Updating registry `https://github.com/rust-lang/crates.io-index`

> cargo outdated
Checking for SemVer compatible updates...Done
Checking for the latest updates...Done
The following dependencies have newer versions available:

Name Project Ver SemVer Compat Latest Ver
clap 1.5.5 -- 2.19.3
clap->ansi_term 0.7.5 -- 0.9.0
clap->bitflags 0.3.3 -- 0.7.0
clap->strsim 0.4.1 -- 0.6.0
clap->vec_map 0.4.0 -- 0.6.0
toml 0.1.30 -- 0.2.1

Project Verが現在使用されているバージョンで、SemVer Compatcargo updateを実行することで更新できるバージョン、Latest VerはCargo.tomlを書き換えないと更新されないバージョンを示しています。


cargo tree (cargo-tree)

Rust界隈では小さなクレートをたくさん作る傾向があるようです。なので、ちょっとしたプロジェクトのつもりでも間接的に多数のクレートに依存していたりします。この依存クレートをツリー形式で表示してくれるのがcargo-treeです。

racerパッケージでcargo treeを実行してみた結果を示します。


racerのレポジトリ内

> cargo tree

racer v2.0.3
├── clap v2.19.1
│ ├── ansi_term v0.9.0
│ ├── bitflags v0.7.0
│ ├── libc v0.2.18
│ ├── strsim v0.5.2
│ ├── term_size v0.2.1
│ │ └── libc v0.2.18 (*)
│ ├── unicode-segmentation v0.1.3
│ ├── unicode-width v0.1.3
│ └── vec_map v0.6.0
├── env_logger v0.3.5
│ ├── log v0.3.6
│ └── regex v0.1.80
│ ├── aho-corasick v0.5.3
│ │ └── memchr v0.1.11
│ │ └── libc v0.2.18 (*)
│ ├── memchr v0.1.11 (*)
│ ├── regex-syntax v0.3.9
│ ├── thread_local v0.2.7
│ │ └── thread-id v2.0.0
│ │ ├── kernel32-sys v0.2.2
│ │ │ └── winapi v0.2.8
│ │ └── libc v0.2.18 (*)
│ └── utf8-ranges v0.1.3
├── log v0.3.6 (*)
├── syntex_errors v0.52.0
│ ├── libc v0.2.18 (*)
│ ├── log v0.3.6 (*)
│ ├── rustc-serialize v0.3.22
│ ├── syntex_pos v0.52.0
│ │ └── rustc-serialize v0.3.22 (*)
│ ├── term v0.4.4
│ │ ├── kernel32-sys v0.2.2 (*)
│ │ └── winapi v0.2.8 (*)
│ └── unicode-xid v0.0.3
├── syntex_syntax v0.52.0
│ ├── bitflags v0.7.0 (*)
│ ├── libc v0.2.18 (*)
│ ├── log v0.3.6 (*)
│ ├── rustc-serialize v0.3.22 (*)
│ ├── syntex_errors v0.52.0 (*)
│ ├── syntex_pos v0.52.0 (*)
│ ├── term v0.4.4 (*)
│ └── unicode-xid v0.0.3 (*)
├── toml v0.2.1
│ └── rustc-serialize v0.3.22 (*)
└── typed-arena v1.2.0

各クレートは最大一回だけ依存ツリーが展開され、二回目の出現からは(*)という印が付き省略されます。


cargo graph (cargo-graph)

cargo-treeはターミナル上にツリーを描画するものでしたが、こちらは依存クレートのグラフを描画するコマンドです。cargo graphを実行するとdot言語でグラフが出力されるので、煮るなり焼くなりできます。

また例はracerです。


racerのレポジトリ内

cargo graph | dot -Gsplines=ortho -Earrowhead=open -Earrowsize=0.5 -Tpng -Ograph.png


graph.png


cargo modules (cargo-modules)

コードのモジュール構造をツリー形式で表示するものです。各モジュールの可視性(pubか否か)や条件コンパイル属性を表示してくれます。

ripgrepのrgバイナリを構成するモジュールを見てみましょう。


ripgrepのレポジトリ内

> cargo modules -b rg

rg : crate
├── app : private
├── args : private
├── atty : private
├── pathutil : private
├── printer : private
│ └── tests : private @ #[cfg(test)]
├── search_buffer : private
│ └── tests : private @ #[cfg(test)]
├── search_stream : private
│ └── tests : private @ #[cfg(test)]
├── unescape : private
│ └── tests : private @ #[cfg(test)]
└── worker : private



cargo clippy (clippy)

lintツールです。必須です。各lintの説明が並んでいるhttps://github.com/Manishearth/rust-clippy/wiki をざっと眺めるだけでもかなり役に立ちます。

Nightlyコンパイラが必要なので、clippyの為にローカルではNightlyで開発・CIでstableで動くかチェックみたいな感じにしている人も多そうですが、そろそろ標準添付されそうな雰囲気ですね。

以前はちょっと試してみるだけでもCargo.tomlを弄ったり#![plugin]属性を入れたりと面倒な作業が必要だったような気がしますが、最近では一度実行するだけならパッケージのディレクトリ内で

cargo clippy

と打つだけになったようです。注意点として、clippyをコンパイルするのに使ったのと同じコンパイラでないと動作しません。rustup updateでNightlyコンパイラを更新したらcargo install -f clippyとしてclippyを再コンパイルする必要があります。

参考記事: rust-clippyの使い方


cargo benchcmp (cargo-benchcmp)

Rustには言語組み込みのベンチマーク機能があります(unstable)。cargo-benchcmpはそのベンチマークを簡単に比較できるツールです。

benches/moji.rsというベンチマークコードがあるとします。次のようにして、コード変更前と変更後でベンチマーク結果を書き出します。

> cargo bench --bench moji > old

> ...
> cargo bench --bench moji > new

これらをbenchcmpコマンドに引数として渡すことで比較が行われます。実行時間の変化がパーセンテージで表示されます。もし新しい方のベンチマークで性能が低下した場合、赤くハイライトされます(この例では13%の性能低下が起きているので、ターミナル上の文字は赤くなっていました)。

> cargo benchcmp old new

name old ns/iter new ns/iter diff ns/iter diff %
bench_moji_count 38 43 5 13.16%


cargo expand (cargo-expand)

Rustはマクロトレイトのderiveといったコード生成機構を備えています。マクロ呼び出しや#[derive]属性はコンパイル時に実際のソースコードへ展開されるのですが、生成されたコードに問題がある場合、コンパイラのエラーメッセージからは問題の箇所がよく分からない場合があります。

cargo expandはコンパイラによって展開された後のコードを表示してくれるツールです。実際のコードを確認することで生成されたコードの問題点が見つけやすくなります。

cargo expandの出力例を示します。マクロやderiveだけでなく、stdのインポートなどの定型コードも追加されていることが分かりますね。

元コード:

#[derive(Debug, Clone)]

struct Foo<T>(T);

fn bar() {
println!("{}", 3.14);
}

cargo expandの出力:

#![feature(prelude_import)]

#![no_std]
#[prelude_import]
use std::prelude::v1::*;
#[macro_use]
extern crate std as std;
struct Foo<T>(T);
#[automatically_derived]
#[allow(unused_qualifications)]
impl<T: ::std::fmt::Debug> ::std::fmt::Debug for Foo<T> {
fn fmt(&self, __arg_0: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
match *self {
Foo(ref __self_0_0) => {
let mut builder = __arg_0.debug_tuple("Foo");
let _ = builder.field(&&(*__self_0_0));
builder.finish()
}
}
}
}
#[automatically_derived]
#[allow(unused_qualifications)]
impl<T: ::std::clone::Clone> ::std::clone::Clone for Foo<T> {
#[inline]
fn clone(&self) -> Foo<T> {
match *self {
Foo(ref __self_0_0) => Foo(::std::clone::Clone::clone(&(*__self_0_0))),
}
}
}

fn bar() {

::io::_print(::std::fmt::Arguments::new_v1(
{
static __STATIC_FMTSTR: &'static [&'static str] = &["", "\n"];
__STATIC_FMTSTR
},
&match (&3.14,) {
(__arg0,) => [::std::fmt::ArgumentV1::new(__arg0, ::std::fmt::Display::fmt)],
},
));
}


おわりに

他にもまだこういった便利なツールが存在します。ここなどを参考に探してみて下さい。





  1. 1つのパッケージには1つ以上のクレートが含まれます。あまり厳格に区別する必要はなく、パッケージのことをクレートと呼んだりします。