Cargoの使い方

この記事はRust Advent Calendar 2015 3日目の記事です。
Rustで小さなツールを作ってみる(後編)
次 szkttyさん インデックス構文によるアクセスを実装する

κeenです。Rustを使うにはCargoを使う必要がありますが、cargo helpを見てもあまり情報が載っていないので少しばかり書きましょう。

new

Create a new cargo package at <path>

Usage:
    cargo new [options] <path>
    cargo new -h | --help

Options:
    -h, --help          Print this message
    --vcs VCS           Initialize a new repository for the given version
                        control system (git or hg) or do not initialize any version
                        control at all (none) overriding a global configuration.
    --bin               Use a binary instead of a library template
    --name NAME         Set the resulting package name
    -v, --verbose       Use verbose output
    -q, --quiet         No output printed to stdout
    --color WHEN        Coloring: auto, always, never

これはみなさんよく使うのでほとんど説明不要でしょう。cargo newまたはcargo new --binの形でよく使います。 オプションを見て分かる通り、cargo new foo-rs --name fooのようにパスとcrateの名前を変えたりデフォルトのvcsにmercurialを使うことも出来ます。

build

Compile a local package and all of its dependencies

Usage:
    cargo build [options]

Options:
    -h, --help               Print this message
    -p SPEC, --package SPEC  Package to build
    -j N, --jobs N           The number of jobs to run in parallel
    --lib                    Build only this package's library
    --bin NAME               Build only the specified binary
    --example NAME           Build only the specified example
    --test NAME              Build only the specified test target
    --bench NAME             Build only the specified benchmark target
    --release                Build artifacts in release mode, with optimizations
    --features FEATURES      Space-separated list of features to also build
    --no-default-features    Do not build the `default` feature
    --target TRIPLE          Build for the target triple
    --manifest-path PATH     Path to the manifest to compile
    -v, --verbose            Use verbose output
    -q, --quiet              No output printed to stdout
    --color WHEN             Coloring: auto, always, never

If the --package argument is given, then SPEC is a package id specification
which indicates which package should be built. If it is not given, then the
current package is built. For more information on SPEC and its format, see the
`cargo help pkgid` command.

Compilation can be configured via the use of profiles which are configured in
the manifest. The default profile for this command is `dev`, but passing
the --release flag will use the `release` profile instead.

恐らく一番よく使うタスクですね。ビルド対象を色々指定できるのはいいとして、実は-jオプションがあります。並行ビルド出来るやつですね。体感速度は変わりませんが。

run

Run the main binary of the local package (src/main.rs)

Usage:
    cargo run [options] [--] [<args>...]

Options:
    -h, --help              Print this message
    --bin NAME              Name of the bin target to run
    --example NAME          Name of the example target to run
    -j N, --jobs N          The number of jobs to run in parallel
    --release               Build artifacts in release mode, with optimizations
    --features FEATURES     Space-separated list of features to also build
    --no-default-features   Do not build the `default` feature
    --target TRIPLE         Build for the target triple
    --manifest-path PATH    Path to the manifest to execute
    -v, --verbose           Use verbose output
    -q, --quiet             No output printed to stdout
    --color WHEN            Coloring: auto, always, never

If neither `--bin` nor `--example` are given, then if the project only has one
bin target it will be run. Otherwise `--bin` specifies the bin target to run,
and `--example` specifies the example target to run. At most one of `--bin` or
`--example` can be provided.

All of the trailing arguments are passed to the binary to run. If you're passing
arguments to both Cargo and the binary, the ones after `--` go to the binary,
the ones before go to Cargo.

実行可能ファイルのプロジェクトだった時に成果物を走らせます。あるいはexampleも走らせられます。とはいってもまだビルドしてなかったらビルドもするのでビルド向けのオプションがいっぱいありますね。

実行可能ファイルが複数あるならcargo run --bin xxxで指定して走らせます。

--releaseビルドした成果物を走らせたかったらcargo run --releaseしないといけません。

cargo run -- argsで成果物に引数を渡せます。

test

Execute all unit and integration tests of a local package

Usage:
    cargo test [options] [--] [<args>...]

Options:
    -h, --help               Print this message
    --lib                    Test only this package's library
    --bin NAME               Test only the specified binary
    --example NAME           Test only the specified example
    --test NAME              Test only the specified integration test target
    --bench NAME             Test only the specified benchmark target
    --no-run                 Compile, but don't run tests
    -p SPEC, --package SPEC  Package to run tests for
    -j N, --jobs N           The number of jobs to run in parallel
    --release                Build artifacts in release mode, with optimizations
    --features FEATURES      Space-separated list of features to also build
    --no-default-features    Do not build the `default` feature
    --target TRIPLE          Build for the target triple
    --manifest-path PATH     Path to the manifest to build tests for
    -v, --verbose            Use verbose output
    -q, --quiet              No output printed to stdout
    --color WHEN             Coloring: auto, always, never
    --no-fail-fast           Run all tests regardless of failure

All of the trailing arguments are passed to the test binaries generated for
filtering tests and generally providing options configuring how they run. For
example, this will run all tests with the name `foo` in their name:

    cargo test foo

If the --package argument is given, then SPEC is a package id specification
which indicates which package should be tested. If it is not given, then the
current package is tested. For more information on SPEC and its format, see the
`cargo help pkgid` command.

The --jobs argument affects the building of the test executable but does
not affect how many jobs are used when running the tests.

Compilation can be configured via the `test` profile in the manifest.

テスト走らせるやつです。

cargo testとだけすると全てのテストが走ります。地味にrustdocの中に書いたexampleも走ります。

search

Search packages in crates.io

Usage:
    cargo search [options] <query>
    cargo search [-h | --help]

Options:
    -h, --help               Print this message
    --host HOST              Host of a registry to search in
    -v, --verbose            Use verbose output
    -q, --quiet              No output printed to stdout
    --color WHEN             Coloring: auto, always, never

crates.ioからパッケージを捜してきてくれます。よく使いますね。インデックスのアップデートが地味に重い。

fetch

だんだんニッチなタスクを紹介していきます。

Fetch dependencies of a package from the network.

Usage:
    cargo fetch [options]

Options:
    -h, --help               Print this message
    --manifest-path PATH     Path to the manifest to fetch dependencies for
    -v, --verbose            Use verbose output
    -q, --quiet              No output printed to stdout
    --color WHEN             Coloring: auto, always, never

If a lockfile is available, this command will ensure that all of the git
dependencies and/or registries dependencies are downloaded and locally
available. The network is never touched after a `cargo fetch` unless
the lockfile changes.

If the lockfile is not available, then this is the equivalent of
`cargo generate-lockfile`. A lockfile is generated and dependencies are also
all updated.

dependenciesを全てローカルに持ってくるタスクです。

generate_lockfile

Generate the lockfile for a project

Usage:
    cargo generate-lockfile [options]

Options:
    -h, --help               Print this message
    --manifest-path PATH     Path to the manifest to generate a lockfile for
    -v, --verbose            Use verbose output
    -q, --quiet              No output printed to stdout
    --color WHEN             Coloring: auto, always, never

Cargo.lockの生成をします。cargo updateがロックファイルがないと怒ってくるのでそういう時に使うのでしょう。

package

Assemble the local package into a distributable tarball

Usage:
    cargo package [options]

Options:
    -h, --help              Print this message
    -l, --list              Print files included in a package without making one
    --no-verify             Don't verify the contents by building them
    --no-metadata           Ignore warnings about a lack of human-usable metadata
    --manifest-path PATH    Path to the manifest to compile
    -v, --verbose           Use verbose output
    -q, --quiet             No output printed to stdout
    --color WHEN            Coloring: auto, always, never

Cargoにはcrates.ioにデプロイする機能もあります。他にもpublish, login, yankも見ておきましょう。

install

Install a Rust binary
Usage:
    cargo install [options] [<crate>]
    cargo install [options] --list
Specifying what crate to install:
    --vers VERS               Specify a version to install from crates.io
    --git URL                 Git URL to install the specified crate from
    --branch BRANCH           Branch to use when installing from git
    --tag TAG                 Tag to use when installing from git
    --rev SHA                 Specific commit to use when installing from git
    --path PATH               Filesystem path to local crate to install
Build and install options:
    -h, --help                Print this message
    -j N, --jobs N            The number of jobs to run in parallel
    --features FEATURES       Space-separated list of features to activate
    --no-default-features     Do not build the `default` feature
    --debug                   Build in debug mode instead of release mode
    --bin NAME                Only install the binary NAME
    --example EXAMPLE         Install the example EXAMPLE instead of binaries
    --root DIR                Directory to install packages into
    -v, --verbose             Use verbose output
    -q, --quiet               Less output printed to stdout
    --color WHEN              Coloring: auto, always, never
This command manages Cargo's local set of install binary crates. Only packages
which have [[bin]] targets can be installed, and all binaries are installed into
the installation root's `bin` folder. The installation root is determined, in
order of precedence, by `--root`, `$CARGO_INSTALL_ROOT`, the `install.root`
configuration key, and finally the home directory (which is either
`$CARGO_HOME` if set or `$HOME/.cargo` by default).
There are multiple sources from which a crate can be installed. The default
location is crates.io but the `--git` and `--path` flags can change this source.
If the source contains more than one package (such as crates.io or a git
repository with multiple crates) the `<crate>` argument is required to indicate
which crate should be installed.
Crates from crates.io can optionally specify the version they wish to install
via the `--vers` flags, and similarly packages from git repositories can
optionally specify the branch, tag, or revision that should be installed. If a
crate has multiple binaries, the `--bin` argument can selectively install only
one of them, and if you'd rather install examples the `--example` argument can
be used as well.
The `--list` option will list all installed packages (and their versions).

まだリリースチャネルには来てませんが、installも入る予定です。binプロジェクトをソースを持ってきてそのままビルド、インストールまでするやつです。勿論、ローカルのものもインストール出来ますよ。 rustfmtのようにREADMEのインストール方法にcargo installを書いているものもあります。これが使えるようになると配布がぐっと楽になりますね。

プラグイン

Cargoのサブコマンドを自分で作るのは簡単です。cargo fooというタスクを作りたいのならcargo-fooという名前の実行可能ファイルをパスに置いておくだけです。

試してみましょう

$ cat <<EOF > ~/bin/cargo-foo
#!/bin/sh
echo args are: "\$@"
echo \\\$0 is: \$0
EOF
$ chmod +x  ~/bin/cargo-foo
$ cargo foo aa bb cc
args are: foo aa bb cc
$0 is: /home/kim/bin/cargo-foo
$ cargo help foo aa bb cc
args are: foo -h
$0 is: /home/kim/bin/cargo-foo

ちょっと独特な引数の渡り方をしてますね。しかし予め了解しておけば特に問題はなさそうです。1つサブコマンドを作ってみましょう。

指定した名前のパッケージをCargo.tomlのdependenciesに書き足してくれるサブコマンドです。

#!/bin/sh
usage(){
    cat <<EOF
Add the dependency of crate to Cargo.toml

Usage:
    cargo use <crate> [version]
    cargo use -h | --help

Description:
    Add the dependency of crate to Cargo.toml.
    If vension is omitted, adopt the latest version.

EOF
}

version(){
    cargo search "$1" | grep -Eo "^$1 \(.*?\)" | sed "s/^$1 (\(.*\))/\1/"
}

find_cargo(){
    # TODO: look up parent directories
    ls | grep '^Cargo.toml$'
}

ensure_dep_exists(){
    cargo="$1"
    if ! grep -F '[dependencies]' "$cargo" > /dev/null 2>&1; then
        echo '[dependencies]' >> "$cargo"
    fi
}

insert_dep(){
    cargo="$1"
    crate="$2"
    version="$3"
    # FIXME: Because Linux and Mac behaves defferently on null string argument, don't use it and adopt workaround.
    sed -i'' "/\[dependencies\]/{a\
$crate = \"$version\"
}" "$cargo"
}

run(){
    CARGO_TOML="$(find_cargo)"
    if [ $? != 0 ]
    then
        echo "Cargo.toml not found" 1>&2
        exit 1
    fi

    if [ -z "$1" ]; then
        usage
        exit 1
    else
        CRATE="$1"
    fi

    if [ -z "$2" ]; then
        VERSION="$(version $1)"
    else
        VERSION="$2"
    fi

    ensure_dep_exists "$CARGO_TOML"
    insert_dep "$CARGO_TOML" "$CRATE" "$VERSION"    
}

main(){
    # $1 is "use" when called as a cargo plugin
    if [ "$1" != use ]; then
        echo "Use this as a cargo plugin"
        usage
        exit 1
    fi
    shift
    if [ "$1" = -h ] || [ "$1" = --help ]; then
        usage
        exit
    else
        run "$@"
    fi
}


main "$@"

まだ洗練されていませんがお気に入りのタスクです。誰かCLIからCargo.tomlをいじれるツール作ってくれないかな。

Written by κeen
Later article
SML#でDBに接続