Lambdaカクテル

京都在住Webエンジニアの日記です

Invite link for Scalaわいわいランド

Scalaにもpackage-lock.jsonが欲しい!sbt-dependency-lockの紹介

先日、言語の塩漬けビリティの話がネットで盛り上がっていた。

Scalaの塩漬けビリティについてはまた後ほど記事にする予定だが、一般に塩漬けしやすいといったとき、最後にビルドに成功したときと同じライブラリが降ってくるという要素が大事になる。当たり前のように思えるが、ライブラリをホストしてくれているリポジトリが何らかの理由で侵害されたり、そこまで大仰な理由がなくとも、ビルドツールのライブラリ指定方法が甘くて「3.0.0以上」みたいに指定するスタイルの場合に、最新バージョンが変わって別のライブラリが降ってきてしまう、ということもありうる。しかも運悪くライブラリの行儀が悪いと、マイナーバージョンの変更でbreaking changesが起こったりする。

こうした事故を防ぐため、npmは勝手にpackage-lock.jsonを作ってくれるが、sbtはそのような機能を標準で提供しないため、Discordで相談していたところsbt-dependency-lockというツールの存在を教えてもらった。このツールを使うとbuild.sbt.lockが生成され、ライブラリのJARファイルのダイジェストがロックファイルに記録されるようになる。

この記事では、このstringbean/sbt-dependency-lockの概要とその使い方について紹介する。

また、この記事の例が収録されたサンプルプロジェクトは以下で利用できる:

github.com

stringbean/sbt-dependency-lockとは

stringbean/sbt-dependency-lockは、build.sbtに対するロックファイルを生成・検証してくれるsbtプラグインである。

stringbean.github.io

github.com

ちなみにsbt-dependency-lockと似たsbt-lockというプラグインもあるが、こちらはpublic archive扱いとなっており、もうメンテされていないので選ばなくてよい。 sbt-dependency-lockのほうは後発で、ユーザビリティが改善されている。

インストール

sbt-dependency-lockをインストールするには、プロジェクトのproject/plugins.sbtに以下の記述を追加する:

addSbtPlugin("software.purpledragon" % "sbt-dependency-lock" % "1.5.1")

バージョンは執筆時点での最新なので、利用する際はScaladexなどで確認すること:

sbt-dependency-lock

利用方法

このプラグインはゼロコンフィグで以下のコマンドをsbt上で利用できる:

  • dependencyLockWrite
    • build.sbt.lockにロック情報を書き込む
  • dependencyLockCheck
    • build.sbt.lockからロック情報を読み取り、これを現在手元にあるライブラリと突合して検証する

最初にdependencyLockCheckを実行すると、ロックファイルが存在しないのでエラーを返す:

sbt:core> dependencyLockCheck
[error] stack trace is suppressed; run last dependencyLockCheck for the full output
[error] (dependencyLockCheck) Dependency lock check failed - missing lockfile
[error] Total time: 0 s, completed Jan 1, 2025 6:28:14 PM

dependencyLockWriteを実行してみる:

sbt:core> dependencyLockWrite
[success] Total time: 0 s, completed Jan 1, 2025 6:30:33 PM

すると、build.sbt.lockに以下のようなJSONファイルが出力される:

{
  "lockVersion" : 1,
  "timestamp" : "2025-01-01T09:31:38.719Z",
  "configurations" : [
    "compile",
    "optional",
    "provided",
    "runtime",
    "test"
  ],
  "dependencies" : [
    {
      "org" : "org.scala-lang",
      "name" : "scala-library",
      "version" : "2.13.15",
      "artifacts" : [
        {
          "name" : "scala-library.jar",
          "hash" : "sha1:ed6f1d58968b16c5f9067d5cac032d952552de58"
        }
      ],
      "configurations" : [
        "compile",
        "runtime",
        "test"
      ]
    },
    {
      "org" : "org.scala-lang",
      "name" : "scala3-library_3",
      "version" : "3.6.2",
      "artifacts" : [
        {
          "name" : "scala3-library_3.jar",
          "hash" : "sha1:0c3f9f3da15f8c0cdea22ced65f6cf3745454e9f"
        }
      ],
      "configurations" : [
        "compile",
        "runtime",
        "test"
      ]
    }
  ]
}

この状態でふたたびdependencyLockCheckしてみよう:

sbt:core> dependencyLockCheck
[info] Dependency lock check passed
[success] Total time: 0 s, completed Jan 1, 2025 6:37:04 PM

チェックが行われ、問題ないことが表示された。

build.sbt.lockとは

このbuild.sbt.lockファイルにはプロジェクトの依存性に指定されたライブラリ(標準ライブラリを含む)とその推移的依存関係にあるライブラリが格納されたJARファイルに対するダイジェストが記録されている。

また、このライブラリがどこで利用されているか(コンパイルタイミングか、ランタイムか、テストタイミングか)も記載される。

このファイルはバージョン管理システムにコミットする。

普段の開発環境での利用

sbt-dependency-lockは、ライブラリの解決フェーズ(具体的にはupdateが実行された後)で自動的にロックファイルと手元にあるライブラリとの整合性を確認してくれる。

デフォルトの設定では、ライブラリの整合性チェックに失敗したとき(例えばロックファイルが存在しなかったり、ダイジェストが合致しなかったり、新たにライブラリが増えたとき)に警告が表示される。この設定は変更可能だ(後述)。

CIでの利用

CIでは、明示的にsbt dependencyLockCheckを追加しておくことでロックファイルのチェックが行われるようにできる。チェックに失敗した場合はそのままコマンドも失敗する。明示的にdependencyLockCheckを実行する場合は、整合性確認できなかったときは常に失敗してくれるので便利だ。この振舞いはnpm ciに近い。

設定項目

正確な内容については以下のページを参照すること。

stringbean.github.io

dependencyLockFile

ロックファイルのパスとファイル名を設定できる。

dependencyLockAutoCheck

ライブラリ解決フェーズで実行される自動チェックの厳しさを設定できる。

  • DependencyLockUpdateMode.CheckDisabled
    • 自動チェックを行わない。
  • DependencyLockUpdateMode.WarnOnError
    • 差分が検出された場合は警告を表示する。デフォルト。
  • DependencyLockUpdateMode.FailOnError
    • 差分が検出された場合は処理を失敗させる。より厳しい。
  • DependencyLockUpdateMode.AutoUpdate
    • 差分が検出された場合はロックファイルの方を更新する。npm installに近い挙動。

dependencyLockModuleFilter

特定のモジュールをチェック対象外にできる。詳細は公式ページを参照。

dependencyLockConfigurationFilter

特定のコンフィギュレーション(コンパイルとかテストとかランタイムとかの区分)をチェック対象外にできる。詳細は公式ページを参照。

まとめ

sbt-dependency-lockを使ってプロジェクトの依存性のチェックを行う方法について解説した。このプラグインは他のsbtプラグインそのもののチェックまではやらないので、そこまで確認するためにはprojectディレクトリにさらにプラグインを置いて二段階でチェックする必要があるかもしれない。

★記事をRTしてもらえると喜びます
Webアプリケーション開発関連の記事を投稿しています.読者になってみませんか?