はじめに
俺も、風の戦士だったんだ...😭 nikkieです。
Rustプロジェクトの依存管理に使うファイルについて、The Cargo Bookを読んだログです。
目次
- はじめに
- 目次
- はじまりはRustの.gitignore
- Cargo.toml vs Cargo.lock
- Why have Cargo.lock in version control?
- Verifying Latest Dependencies
- 小まとめ:Cargo.lockはバージョン管理する?
- cargoコマンド調査メモ
- 終わりに
- おまけ:Pythonでは?
はじまりはRustの.gitignore
Rustの素振りの中で、初めてRustプロジェクトをGitHubにpushする1ので、.gitignoreを探しました。
こういうとき私はgithub/gitignoreを見に行きます。
https://github.com/github/gitignore/blob/8779ee73af62c669e7ca371aaab8399d87127693/Rust.gitignore
そこにはCargo.lock
をバージョン管理しないという設定が。
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html Cargo.lock
ここについて The Cargo Book を見ていきます📦
Cargo.toml vs Cargo.lock
Rustプロジェクトで登場する2つのファイルについて(※Cargoを使う前提ですね)
Cargo.toml
- 依存を範囲で記述する
- 開発者が書く
- 用語集でいう manifest
Cargo.lock
- 依存の厳密なバージョンを記述する
- Cargoによってメンテナンスされ、人手の編集はすべきでない
- 用語集でいう lock file
ドキュメントに登場する例は https://github.com/rust-lang/regex の指定。
Cargo.toml
を以下のように書いたとき
[dependencies] regex = { git = "https://github.com/rust-lang/regex.git" }
「デフォルトブランチの最新のコミット」のregexに依存すると書いたことになります。
このとき
- 私の手元で今ビルドし、
- 同僚の開発者が明日ビルドしたとすると
最新のコミットは変わりうるので、2人の環境は異なることになり、再現性に難があります。
人がCargo.toml
のrev
にコミットハッシュを指定する案が提示されますが、再現性を獲得すると同時に、毎回人手でrev
を更新することになってしまいます。
そこでCargo.lock
の出番です!
Cargo.toml
にrev
まで指定する必要はありません。
CargoがCargo.lock
にコミットハッシュを記録します。
同僚の開発者の環境ではCargo.lock
をもとに再現すればいいわけですね。
Cargo.lock
の更新はcargo update regex
となるそうです。
依存のバージョンの更新もCargoが担ってくれるのですね。
Why have Cargo.lock in version control?
FAQを見ます:
https://doc.rust-lang.org/cargo/faq.html#why-have-cargolock-in-version-control
ここでもCargo.lock
による再現性の話がされています(用語はdeterministic builds「決定論的ビルド」)
Cargo uses the lockfile to provide deterministic builds at different times and on different systems
deterministic buildsの利点の1つ
Ensuring CI only fails due to new commits and not external factors
開発者の環境間とCI環境とでビルドが再現する場合、CIが落ちる原因は新しいコミットだけとなります(外部要因はありえないでしょう)
これ(斜体部分)は私の意見なのですが、開発者の環境・CI環境・デプロイする環境(念頭にあるのはdockerイメージ)とで再現するビルドを選択したいかなと思います。
開発環境で動いたならば、CIは通り、デプロイしても問題なく動くと期待できるためです
さて、FAQに戻るとCargo.lock
によるdeterministic buildsの大丈夫だという誤った感覚2が言及されています。
this determinism can give a false sense of security because Cargo.lock does not affect the consumers of your package, only Cargo.toml does that.
パッケージの開発者ではなく、利用者にはCargo.lock
は影響を与えず、Cargo.toml
だけが影響を与えると書かれています。
この点がThe Cargo BookのCIの節で取り上げられています(FAQからもリンクされています)
Verifying Latest Dependencies
CIについての節の中の「Verifying Latest Dependencies」です
Verifying the latest versions would at least test for users who run
cargo add
orcargo install
.
この箇所はCIで、パッケージの利用者向けにテストする話として読みました。
タイトルの通り、パッケージの利用者向けには(Cargo.lockに基づく依存ではなく)最新の依存をテストします。
見込みのある解決策(potential solutions)としていくつか挙げられています
- Not checking in the
Cargo.lock
- Have a scheduled CI job to verify latest dependencies
GitHub Actionsのサンプル
jobs: latest_deps: name: Latest Dependencies runs-on: ubuntu-latest continue-on-error: true steps: - uses: actions/checkout@v4 - run: rustup update stable && rustup default stable - run: cargo update --verbose - run: cargo build --verbose - run: cargo test --verbose
このActionを週次とかで動かしておけば、依存ライブラリの更新でビルド失敗となったことに気づけそうですね。
小まとめ:Cargo.lock
はバージョン管理する?
Cargo.lock
をバージョン管理するべきなのかしないべきなのかを知りたくてここまで読み進めましたが、以下の認識に至りました。
- 開発者の環境を揃えるうえで、
Cargo.lock
はバージョン管理したい- 開発者・CI・デプロイ、全てで再現性があることを私は優先したい
- 一方、パッケージの利用者向けには、最新の依存をテストする必要があることも納得した(これは今回の学び)
- 開発が緩やかになったとしても、これがあればメンテナンスが続いていきそう
cargoコマンド調査メモ
Cargo.lock
に関連して、気になったコマンドを調べたメモです
cargo run
Cargo.lock
を見る指定が--locked
(これを指定しないと見ないってことかな?)
Asserts that the exact same dependencies and versions are used as when the existing Cargo.lock file was originally generated.
It may be used in environments where deterministic builds are desired, such as in CI pipelines.
cargo build
--locked
はこちらにも。
run
はまずbuild
するので、共通なのかなと思いました。
cargo update
This command will update dependencies in the Cargo.lock file to the latest version.
Cargo.lock
にある依存を最新化してくれるコマンド。
GitHub Actionsの例でも使っていましたが、「Verifying Latest Dependencies」で重宝しそうです
cargo update regex
のように特定の依存だけ最新化もできます。
ここにも--locked
がありました(あんまり使わないんじゃないかな?)
終わりに
Cargo.toml
とCargo.lock
について、The Cargo Bookの中を確認しました。
- 環境の再現に使う
Cargo.lock
- 開発者・CI・デプロイ、全てで再現性をもたらせる(だからバージョン管理したい)
cargo
コマンドの--locked
オプション
- 一方で、最新の依存でのテストも必要
- パッケージの利用者向けに
Cargo.lock
があってもcargo update
を叩けば最新化できる
今回のきっかけツイート
The Cargo Bookより、Cargo.tomlをバージョン管理するか。
— nikkie / にっきー 技書博 け-04 Python型ヒント本 (@ftnext) 2024年8月4日
バージョン管理したら究極の再現性と思われるがデメリットもあるとのことで、
対照的にバージョン管理せずに常に最新の依存で環境構築をCIのところで紹介https://t.co/XahyPtBmiu
Cargoですら2つの立場があるなら、ごちゃってるPythonさん...
おまけ:Pythonでは?
依存管理がかっちりしているRust(Cargo)の考え方を知り、Pythonの依存管理にも活かせるように思われました。
- 私には
pip-tools
がよさそう。pip-compile
でCargo.lock
相当のファイルができる感じ pip install .
とpyproject.toml
をもとにインストールすることで「Verifying Latest Dependencies」になるのではないか
関連性を見出したエントリを並べます。
pip-tools
pyproject.toml
はCargo.toml
と重なりそう
チュートリアルにあるのはpip freeze
なんですが、Cargo.lock
相当を作るcargo
やpip-compile
と比べて機能不足を感じてしまうな〜
- ↩
- false sense of securityということを知りました。 https://eow.alc.co.jp/search?q=false+sense+of+security↩