Rust
cargo

Cargo.lockはcargo installで参照されない


はじめに

RustのCargo.lockファイルは実際にビルドしたときに使った全ライブラリのバージョンを列挙したものです。

このCargo.lockの存在を意識することはあまりないと思いますが、バイナリクレートを作る場合(すなわちcargo new --binした場合)デフォルトでgitにコミットされるよう.gitignoreが生成されます。

これによって、git cloneしたリポジトリでcargo buildするとコミットされたCargo.lockが参照されて、そこに書かれたバージョンのライブラリが使われることで確実にビルドが成功するだろう、ということです。

しかしこのCargo.lockcargo installでバイナリをインストールする際には使われません。

今回それが原因でビルドに失敗するというIssueを貰ったので、関連して調べた話を書きます。


cargo installでのCargo.lockの扱い

これは正確にはcargo installの問題ではなく、cargo publishの問題です。

クレートをcrates.ioに公開する時にcargo publishを使いますが、この時パッケージされるファイルにCargo.lockは含まれません。

cargo installはcrates.ioからパッケージ化されたソース一式をダウンロードしてきてcargo buildしますが、そのソース一式にCargo.lockは入っていないので使われない、というわけです。


Cargo.lockがないとどうなるのか?

ライブラリのAPIに破壊的変更が入って、それがSemantic Versioning(SemVer)によって適切に表現されていない場合に問題が発生します。

(RustのSemVerに関しては「Rust (Cargo)におけるクレートのバージョンと互換性について」が詳しいです。)

例えば今回のビルド失敗はlibcクレートの破壊的変更の影響です。

libcクレートのv0.2.56でgetrlimit64の引数の型が変更されました。

//v0.2.55

pub fn getrlimit64(resource: ::c_int, rlim: *mut rlimit64) -> ::c_int;

//v0.2.56

pub type __rlimit_resource_t = ::c_uint;
pub fn getrlimit64(resource: ::__rlimit_resource_t,
rlim: *mut ::rlimit64) -> ::c_int;

resourceの型がc_intからc_uintに変わっているので、この関数を呼んでいる箇所は修正が必要な場合があります。

実際にこの関数を呼んでいるpalaverクレートのdependenciesはこんな感じです。


Cargo.toml

libc = "0.2.47"


このようにv0.2.47を指定していますが、=によるバージョン指定はパッチバージョンをなるべく上げようとするので、2019/6/9時点ではv0.2.58が使われます。

本来APIの破壊的変更はSemVerではメジャーバージョンの変更(つまり1.0.0 → 2.0.0のような感じ)で表現されるべきですし、

Rustではバージョン1.0.0未満の場合はマイナーバージョンの変更(つまり0.1.0 → 0.2.0)にするようRFCで規定があります。

しかし今回のようにそれに従わないバージョンアップがあった場合はビルドが壊れてしまいます。

もしCargo.lockがあれば、実際にビルドが成功したバージョン(手元のCargo.lockではv0.2.48でした)を再現することができるので、cargo installでのビルド失敗は防ぐことができます。


CIでの検出

この問題によるビルド失敗は普通に設定したCIでは検出できません。CIではgitからクローンしたリポジトリに対してcargo testcargo buildを行うので、この場合はCargo.lockが参照されてビルドは成功します。

検出するためには明示的にcargo installを行うテストを追加する必要があります。


publish-lockfile

この問題は2016年頃から指摘されていて、このようなIssueが立っています。

Packages for binary crates should include Cargo.lock

これを受けて、cargo publishCargo.lockをパッケージできるようにするunstable featureが入りました。

Package lock files in published crates

この機能はnightlyのcargoでは実際に使用することができて


Cargo.toml

cargo-features = ["publish-lockfile"]

[package]
publish-lockfile = true

としてcargo +nightly publishとするとCargo.lockをパッケージできるようです。

(が、次に書く通りこの機能は安定化されずに消える予定です)


安定化に向けて

この機能の安定化は現在final comment periodまで来ているようです。

Publish lockfile for binary crates

安定化に向けての提案は次の通りです。


  • binaryあるいはexamplesを持つクレートをpublishするときは必ずCargo.lockを含める。


  • cargo installはデフォルトではCargo.lockを参照しない。cargo install --lockedで参照する。


  • publish-lockfileは安定化せず削除する。

というわけでユーザから見た挙動はこれまで通りで、Cargo.lockを使ったビルドをしたい場合はcargo install --lockedを使う、ということになったようです。

デフォルトでCargo.lockを使わない理由としてはセキュリティの観点(依存ライブラリに脆弱性の修正があった場合にcargo installですぐに反映される)が書かれていました。

安定化のターゲットは8月リリースの1.37です。