sbt evictedからライブラリ依存問題を解決するための検討をやってみた

sbt evictedにてバージョン衝突のログが出た場合に、選択すべきバージョンの検討方法について幾つか書いてみました。
2020.08.21

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

sbtのプロジェクトでライブラリ依存の競合表示に遭遇しました。具体的にはsbt evictedにて表示されるログのことです。ライブラリを使う上では回避し難い状況にも思えますが、問題はscalaに馴染みがない場合

  • 対象ライブラリがどのような状態になっているのか
  • 動作する上で大きく問題が生じることなのか
  • 果たして解消できるのか
  • 対処方法として不味いやり方にはあるのか

等が不明で、動作上は問題ないのか判断し難いという点です。

ドキュメントによると、既定動作ではより新しいバージョンを選択し、古いバージョンを弾くものとして扱います。

The default conflict manager will select the newer version of akka-actor, 2.3.7. This can be confirmed in the output of show update, which shows the newer version as being selected and the older version as evicted.

この動作に従いつつも、各バージョンの確認及び明示的に確定する手段について備忘録兼ねてまとめました。

ログをよみとる

sbt evictedを実行すると、以下のようなログが大量にでてくることがあります。この場合、AWSGlueJdbcCommons と mongo-spark-connector が mongo-java-driver に依存していることを示しています。

[warn] Found version conflict(s) in library dependencies; some are suspected to be binary incompatible:
[warn]  * org.mongodb:mongo-java-driver:3.10.1 is selected over [3.10,3.11)
[warn]      +- com.amazonaws:AWSGlueJdbcCommons:0.9.0             (depends on 3.10.1)
[warn]      +- org.mongodb.spark:mongo-spark-connector_2.11:2.4.1 (depends on [3.10,3.11))
  • AWSGlueJdbcCommons は 3.10.1 に依存
  • mongo-spark-connector は 3.10, 3.11 に依存

ポイントは AWSGlueJdbcCommonsが依存する 3.10.1 を選択されているという点です。

近似2バージョンからの選択

まずは、より新しいバージョンのうち選択されなかった 3.11 について。3.11.0 と 3.10.0、3.10.1 の差分ですが、ABI-Laboratoryを使って各バージョン間の互換性を確認してみます。

この2バージョンは完全な上位互換性は取れていないことがわかります。

比較に 3.10.2を使っていますが、3.10.1 の上位互換性が100%であることと、3.10.1と3.11.0の比較ができなかったためです。次に、3.10.0 と 3.10.1 の互換性を確認します。

3.10.0 は 3.10.1 に対して上位互換性が100%とあります。mongo-java-driverについては暗黙的に選択されている 3.10.1 で恐らく問題なしと言えそうです。

3つ以上のバージョンからの選択

問題は沢山のバージョンが表示されるケースです。

[warn]  * io.netty:netty:3.9.9.Final is selected over {3.6.2.Final, 3.6.2.Final, 3.6.2.Final, 3.6.2.Final, 3.6.2.Final, 3.6.2.Final, 3.7.0.Final, 
3.6.2.Final, 3.6.2.Final, 3.6.2.Final, 3.6.2.Final, 3.6.2.Final, 3.6.2.Final, 3.7.0.Final, 3.6.2.Final, 3.6.2.Final, 3.6.2.Final, 3.6.2.Final, 
3.6.2.Final, 3.6.2.Final, 3.7.0.Final, 3.6.2.Final, 3.6.2.Final, 3.6.2.Final, 3.6.2.Final, 3.6.2.Final, 3.6.2.Final, 3.7.0.Final, 3.6.2.Final, 
3.6.2.Final, 3.6.2.Final, 3.6.2.Final, 3.6.2.Final, 3.6.2.Final, 3.7.0.Final, 3.6.2.Final, 3.6.2.Final, 3.6.2.Final, 3.6.2.Final, 3.6.2.Final, 
3.6.2.Final, 3.7.0.Final, 3.6.2.Final, 3.6.2.Final, 3.6.2.Final, 3.6.2.Final, 3.6.2.Final, 3.6.2.Final, 3.7.0.Final, 3.6.2.Final, 3.6.2.Final, 
3.6.2.Final, 3.6.2.Final, 3.6.2.Final, 3.6.2.Final, 3.7.0.Final, 3.6.2.Final, 3.6.2.Final, 3.6.2.Final, 3.6.2.Final, 3.6.2.Final, 3.6.2.Final, 
3.7.0.Final, 3.6.2.Final, 3.6.2.Final, 3.6.2.Final, 3.6.2.Final, 3.6.2.Final, 3.6.2.Final, 3.7.0.Final}
[warn]      +- org.apache.spark:spark-core_2.11:2.4.3             (depends on 3.9.9.Final)
[warn]      +- org.apache.zookeeper:zookeeper:3.4.6               (depends on 3.7.0.Final)
[warn]      +- org.apache.hadoop:hadoop-mapreduce-client-shuffle:2.8.5 (depends on 3.6.2.Final)
[warn]      +- org.apache.hadoop:hadoop-mapreduce-client-jobclient:2.8.5 (depends on 3.6.2.Final)
[warn]      +- org.apache.hadoop:hadoop-mapreduce-client-core:2.8.5 (depends on 3.6.2.Final)
[warn]      +- org.apache.hadoop:hadoop-mapreduce-client-common:2.8.5 (depends on 3.6.2.Final)
[warn]      +- org.apache.hadoop:hadoop-mapreduce-client-app:2.8.5 (depends on 3.6.2.Final)
[warn]      +- org.apache.hadoop:hadoop-hdfs:2.8.5                (depends on 3.6.2.Final)

依存先に物凄い量のバージョンが挙がっているようにも見えますが、実際は

  • 3.9.9.Final
  • 3.7.0.Final
  • 3.6.2.Final

の三択です。3.6.2.Finalと3.7.0.Finalが大量に含まれている状況ですが、とりあえずは置いておきます。ABI-Laboratory にはnettyそのものはありません。nettyのリポジトリを確認すると、

Note: This artifact was moved to: io.netty » netty-all

とあるように、netty-allで調べます。ですが、netty-allは4.0.0以降のバージョンが対象となっており、netty以前はabi-laboratoryでは確認が取れません。この場合、ソースコードを直接比較するか、Release Noteから上位互換性やbugfixの有無を確認を行うべきでしょう。

Release Noteの更新から決める

3.9.9.Finalが選ばれている上で動作に問題は見つかってはいない場合、上位互換性が十分に確保できている可能性はあります。そこで、過去バージョンにない機能及び、脆弱性のfixが含まれていないかをRelease Noteから見てみます。

3.7.0.Final → 3.9.9.Final
(3.9.9) Fix cookie encoding when maxAge is greater than 24 days (#4008)
(3.9.9) Fix invalid check arguments in 'OpenSslServerContext.setTicketKeys' (#4009)
(3.9.8) Validate cookie name and value characters (#3754)
(3.9.6) Fix the bugs in ZlibEncoder and JdkZlibEncoder which caused HttpContentCompressor to fail(#3107)
(3.9.6) Disable SSLv3 to protect the users against POODLE(#3131)
(3.9.6) Add support for pushed resources in SPDY(#3142)
(3.9.6) Fix integer overflow when handling an HTTP multipart request(#3210)
(3.9.6) Fix potential race condition in I/O boss/worker pool initialization(#3249)
(3.9.4) Improve traffic shaping handlers(#2747)
(3.9.4) Fix a bug in IpFilterSubnetRule handling 0.0.0.0/0 incorrectly(#2767)
(3.9.4) Add a system property to change the default ZLIB compression library in HttpContentCompressor(#2821)
(3.9.3) Fix a dead lock in SslHandler(#2093)
(3.9.3) Fix a message loss during HTTP protocol switch(#2179)
(3.9.3) Fix a redundant 'SSLEngine is closing/closed' exception(#2333)
(3.9.3) Upgrade JBoss Logging to the latest GA version (by @gaol)(#2626)
(3.9.3) Fix a NPE in SPDY(#2683)
(3.9.3) Fix an incorrect ConnectException message(#2713)
(3.9.3) Fix a missing channelInterestOpsChanged event(#2714)
(3.9.3) Fix missing encrypted private key support in JdkSslContext (by @schulzp)(#2718)
(3.9.3) Disallow ChannelFuture.setFailure(null), which leads to inconsistent state(#2728)
(3.9.3) Fix incorrect URI encoding in HTTP encoder(#2732)
(3.9.3) Do not simplify stack trace(#2735)
(3.9.3) Fix a bug where SpdySessionHandler sometimes does not handle all pending writes(#2742)
(3.9.2) Possible DoS by CPU exhaustion when using malicious SSL packets (#2562)
(3.9.2) Add OpenSslEngine (#2464)
(3.9.2) Provide universal API to create an SSLEngine (#2499)
(3.9.2) Minimize memory footprint of HashedWheelTimer and context-switching (#2453)
3.6.2.Final →3.7.0.Final
(3.7.0) Even though we bumped the minor version from 6 to 7, it is mostly a bug fix release if the changes in the SPDY support is not considered. Because SPDY is relatively new and its specification has been changing over time, we had to reorganize it a little bit. If you were using the SPDY package in 3.6, you might want to make sure everything works as before and check #1778, #1490, #1476, #1462, and #1419. Otherwise, you will not notice anything particular but bug fixes.
(3.6.6) Fix multiple issues when handling HTTP multipart. (#1228), (#1313), (#1332) and (#1358)
(3.6.6) HttpChunkAggregator throws IllegalArgumentException because of incorrect removal of chunked transfer-encoding (#1322)
(3.6.6) Deadlock can happen when MemoryAwareThreadPoolExecutor with limit is used (#1310)
(3.6.5) Fix regression which could cause CPU-Spinning. (#1243) and (#1246)
(3.6.5) Fix bug in AbstractTrafficShaper which could make it work incorrect if the system clock went backwards. (#1237)
(3.6.4) You can specify ThreadNameDeterminer as a parameter when you construct Oio*ChannelFactory and HashedWheelTimer. (#1176)
(3.6.4) ProtobufDecoder takes advantage of the performance improvements in protobuf 2.5.0. (#1146)
(3.6.4) The message of a ConnectException now contains the remote address where the connection attempt was made to. (#1082)
(3.6.4) NIO transport raises a ConnectTimeoutException, which extends ConnectException, when a connection attempt timed out. (#1036)
(3.6.3) The most important is a possible deadlock when using the SslHandler and trigger a close operation from a different thread then the Worker thread. For the full list of issues and more details visit our issue tracker. The release itself can be found on our download page or on maven central.

多くが細かいFixとなりますが、特記すべきものとしては以下のものがあります。

3.9.2
Possible DoS by CPU exhaustion when using malicious SSL packets
This is an important release if you use a previous version of the 3.9 branch because it fixes a possible DoS attack when using the SslHandler. So please upgrade as soon as possible. For the security bug CVE-2014-3488 was assigned.
3.7.0
Even though we bumped the minor version from 6 to 7, it is mostly a bug fix release if the changes in the SPDY support is not considered. Because SPDY is relatively new and its specification has been changing over time, we had to reorganize it a little bit. If you were using the SPDY package in 3.6, you might want to make sure everything works as before and check #1778, #1490, #1476, #1462, and #1419. Otherwise, you will not notice anything particular but bug fixes.

セキュリティ面の修正とSPDYパッケージサポートの2点から古いバージョンに切り替えるリスクは高いと思われます。deprecatedと明記が見当たらない点もポイントです。

選択されているバージョンを固定する

事前に明確に固定することでバージョン衝突のメッセージ出力を抑えます。

dependencyOverrides ++= Set(
  "io.netty" % "netty" % "3.9.9.Final",
  "org.mongodb" % "mongo-java-driver" % "3.10.1"
)

ポイントは、暗黙での選択と同一の場合はsbt evictedでのログ出力抑制と同義になることです。

あとがき

sbt evictedで警告にそってバージョンの指定を行う場合の検討方法について、幾つかのやりかたを例に書いてみました。

既定の選択で問題なく動く場合、sbt evictedでのログが気にならないのであれば特に対処は不要かもしれません。厳密に調査を行う場合、ライブラリによっては差分の調査コストがとても大きくなります。

ライブラリの依存バージョンを変更する場合はユニットテストの確認も忘れずに行いましょう。

参考リンク