そういえば、はっきり書かれたドキュメントが思い当たらない(or 存在していたとしても、改めて日本語で書くことに意味はあるだろうと思う)、ので、書いてみることにする。
自分が知ってる理由の1つとしては、以下のようなことをすると、普通にNoClassDefFoundErrorやNoSuchMethodErrorになるからである。
とりあえず、2.12の場合でmimaでforwards.excludesされている( = methodやclassなどが例外的に追加されている)、パターンで適当に試してみる。今回は2.12の途中のどこかで追加された showAsInfix というアノテーションのclassを参照してみる。
https://github.com/scala/scala/blob/2.12.x/src/library/mima-filters/2.12.0.forwards.excludes
build.sbt
lazy val a = project.settings( scalaVersion := "2.12.8" ) lazy val b = project.settings( scalaVersion := "2.12.0" ).dependsOn(a)
a/A.scala
package example class A { def a: AnyRef = scala.annotation.showAsInfix }
b/B.scala
package example object B { def main(args: Array[String]): Unit = { (new A).a } }
project/build.properties
sbt.version=1.2.8
sbt b/run の実行結果
[error] (run-main-0) java.lang.NoClassDefFoundError: scala/annotation/showAsInfix$ [error] java.lang.NoClassDefFoundError: scala/annotation/showAsInfix$ [error] at example.A.a(A.scala:4) [error] at example.B$.main(B.scala:5) [error] at example.B.main(B.scala) [error] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) [error] at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) [error] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) [error] at java.lang.reflect.Method.invoke(Method.java:498) [error] Caused by: java.lang.ClassNotFoundException: scala.annotation.showAsInfix$ [error] at java.net.URLClassLoader.findClass(URLClassLoader.java:382) [error] at java.lang.ClassLoader.loadClass(ClassLoader.java:424) [error] at java.lang.ClassLoader.loadClass(ClassLoader.java:357) [error] at example.A.a(A.scala:4) [error] at example.B$.main(B.scala:5) [error] at example.B.main(B.scala) [error] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) [error] at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) [error] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) [error] at java.lang.reflect.Method.invoke(Method.java:498) [error] Nonzero exit code: 1 [error] (b / Compile / run) Nonzero exit code: 1
以下追加説明や、想定されるツッコミへの回答など
- 同じproject内でscalaVersion揃えないやつなんていないだろ! => 再現が面倒なので同じプロジェクトでやったが、とあるライブラリが、2.12.xで追加された機能使っていて、そのライブラリを使いつつ、自分のプロジェクトでは2.12.yを設定 ( y は x より小さい)、みたいなことをすると発生する
- 常に最新のversion使うようにすればいいだけでは? => ある意味そのとおりかもしれない?
- というかsbtが他の普通のライブラリと比べて、Scalaのversionに関して特別扱い(依存してるやつのなかで最新を使うわけではない) しているのが悪いだけでは? => ある意味それもそのとおりかもしれない?
とまぁ、sbtの挙動の話に行き着くわけですが、とはいえsbt関連以外でも前方互換を守っておくと得することは多々あると思うが、とはいえすぐに思いつかないので、他に情報集まったら追記するなどするかもしれません。
ちなみに、なんで今さらこれを書こうか?と思ったのかというと、ScalaMatsuriのハッカソンをキッカケとして、Scala本体にpull reqする(日本人の) 人を数名見かけるのですが、このパターンにより前方バイナリ互換維持しない変更pull req出してるのを見かけるからです。
まぁ日本人に限らず、よくわからずそういうpull req出す人は昔から結構見かけます。 (ある意味、Scala本体 CONTRIBUTING.md の説明が悪い?足りないのか・・・?)
そもそも2.14.xの前方互換も後方互換も壊していい開発用branchが作成されたら、そっちにpull req出せばいいだけなのですが、例年 .0
のversion ( = 2.11.0や2.12.0や2.13.0のこと)が出たあとしばらくは、それのバグ修正が中心でまだ開発用branchは作成されないので、今はそういう意味では新機能追加や、(バグ修正だとしても)互換を壊すpull reqをするには時期が悪いです。待ちましょう・・・(という雑な結論でいいのか・・・?)
そういえば、2.12.9のstring interpolationのbugみたいなのがあったとき
— Kenji Yoshida (@xuwei_k) August 7, 2019
- その影響あるので2.12.8使いたい
- でも使いたいライブラリが2.12.9使ってbuildされてる
というパターンのときに
"前方バイナリ互換も維持"
されてると、安全に古いversion使えるというメリットはあるhttps://t.co/tnhZaJZHMf