nikkie-ftnextの日記

イベントレポートや読書メモを発信

RustのプロジェクトでCargo.lockはバージョン管理する?しない?どっちなんだい? The Cargo Bookをたどって

はじめに

俺も、風の戦士だったんだ...😭 nikkieです。

Rustプロジェクトの依存管理に使うファイルについて、The Cargo Bookを読んだログです。

目次

はじまりは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.tomlrevにコミットハッシュを指定する案が提示されますが、再現性を獲得すると同時に、毎回人手でrevを更新することになってしまいます。

そこでCargo.lockの出番です!
Cargo.tomlrevまで指定する必要はありません。
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 or cargo 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.tomlCargo.lockについて、The Cargo Bookの中を確認しました。

  • 環境の再現に使うCargo.lock
    • 開発者・CI・デプロイ、全てで再現性をもたらせる(だからバージョン管理したい)
    • cargoコマンドの--lockedオプション
  • 一方で、最新の依存でのテストも必要
    • パッケージの利用者向けに
    • Cargo.lockがあってもcargo updateを叩けば最新化できる

今回のきっかけツイート

おまけ:Pythonでは?

依存管理がかっちりしているRust(Cargo)の考え方を知り、Pythonの依存管理にも活かせるように思われました。

  • 私にはpip-toolsがよさそう。pip-compileCargo.lock相当のファイルができる感じ
  • pip install .pyproject.tomlをもとにインストールすることで「Verifying Latest Dependencies」になるのではないか

関連性を見出したエントリを並べます。

pip-tools

pyproject.tomlCargo.tomlと重なりそう

チュートリアルにあるのはpip freezeなんですが、Cargo.lock相当を作るcargopip-compileと比べて機能不足を感じてしまうな〜


  1. false sense of securityということを知りました。 https://eow.alc.co.jp/search?q=false+sense+of+security