はじめに
RustのCargo.lock
ファイルは実際にビルドしたときに使った全ライブラリのバージョンを列挙したものです。
このCargo.lock
の存在を意識することはあまりないと思いますが、バイナリクレートを作る場合(すなわちcargo new --bin
した場合)デフォルトでgitにコミットされるよう.gitignore
が生成されます。
これによって、git clone
したリポジトリでcargo build
するとコミットされたCargo.lock
が参照されて、そこに書かれたバージョンのライブラリが使われることで確実にビルドが成功するだろう、ということです。
しかしこのCargo.lock
はcargo 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はこんな感じです。
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 test
やcargo build
を行うので、この場合はCargo.lock
が参照されてビルドは成功します。
検出するためには明示的にcargo install
を行うテストを追加する必要があります。
publish-lockfile
この問題は2016年頃から指摘されていて、このようなIssueが立っています。
Packages for binary crates should include Cargo.lock
これを受けて、cargo publish
でCargo.lock
をパッケージできるようにするunstable featureが入りました。
Package lock files in published crates
この機能はnightlyのcargoでは実際に使用することができて
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です。