diff --git a/.travis.yml b/.travis.yml index 98d2b23a..601a2e8a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -54,6 +54,11 @@ matrix: scala: 2.12.6 env: TEST_SCALAFIX=true + # run binary compatibility test + - jdk: oraclejdk8 + scala: 2.12.6 + env: TEST_BINARY_COMPAT=true + # | jdk | scala | scala target | scala target version | scalafix test | # | ----------- | --------- | ------------ | -------------------- |---------------| # | openjdk6 | 2.11.12 | jvm | | | diff --git a/admin/build.sh b/admin/build.sh index 7993b08e..9060f9ee 100755 --- a/admin/build.sh +++ b/admin/build.sh @@ -22,7 +22,11 @@ if [ "$SCALAJS_VERSION" = "" ]; then if [[ "$TEST_SCALAFIX" == "true" ]]; then projectPrefix="scalafixRules" else - projectPrefix="compat" + if [[ "$TEST_BINARY_COMPAT" == "true" ]]; then + projectPrefix="binary-compat" + else + projectPrefix="compat" + fi fi else projectPrefix="compatJS" diff --git a/binary-compat/new/src/main/scala/org.example/Lib.scala b/binary-compat/new/src/main/scala/org.example/Lib.scala new file mode 100644 index 00000000..68c91ef7 --- /dev/null +++ b/binary-compat/new/src/main/scala/org.example/Lib.scala @@ -0,0 +1,15 @@ +package org.example + +import scala.collection.generic._ +import scala.collection.compat._ + +object Lib { + def id[A, C[X] <: Iterable[X]](x: C[A])(implicit factory: Factory[A, C[A]]) = { + val builder = factory.newBuilder + builder ++= x + builder.result + } + + def test1 = + id(List(1, 2, 3)) +} diff --git a/binary-compat/old/src/main/scala/org.example/Lib.scala b/binary-compat/old/src/main/scala/org.example/Lib.scala new file mode 100644 index 00000000..fa51e475 --- /dev/null +++ b/binary-compat/old/src/main/scala/org.example/Lib.scala @@ -0,0 +1,14 @@ +package org.example + +import scala.collection.generic._ + +object Lib { + def id[A, C[X] <: Iterable[X]](x: C[A])(implicit cbf: CanBuildFrom[C[A], A, C[A]]) = { + val builder = cbf() + builder ++= x + builder.result + } + + def test1 = + id(List(1, 2, 3)) +} diff --git a/binary-compat/test/src/test/scala/BinaryCompaTest.scala b/binary-compat/test/src/test/scala/BinaryCompaTest.scala new file mode 100644 index 00000000..86511900 --- /dev/null +++ b/binary-compat/test/src/test/scala/BinaryCompaTest.scala @@ -0,0 +1,16 @@ +import org.junit.{Assert, Test} + +import build.BuildInfo._ + +import com.typesafe.tools.mima.lib.MiMaLib +import com.typesafe.tools.mima.core.Config + +class BinaryCompaTest { + @Test + def compat(): Unit = { + Config.setup("foo", Array(oldClasspath, newClasspath)) + val mima = new MiMaLib(Config.baseClassPath) + val allProblems = mima.collectProblems(oldClasspath, newClasspath) + Assert.assertEquals(allProblems, Nil) + } +} diff --git a/build.sbt b/build.sbt index bca893c1..f93c19d3 100644 --- a/build.sbt +++ b/build.sbt @@ -14,6 +14,8 @@ lazy val root = project // == Core Libraries == +lazy val junit = libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % Test + lazy val compat = crossProject(JSPlatform, JVMPlatform) .withoutSuffixFor(JVMPlatform) .crossType(CrossType.Pure) @@ -32,7 +34,7 @@ lazy val compat = crossProject(JSPlatform, JVMPlatform) ) .jvmSettings( OsgiKeys.exportPackage := Seq(s"scala.collection.compat.*;version=${version.value}"), - libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % "test", + junit, javaHome in Compile := { val oldValue = (javaHome in Compile).value val isOnCi = sys.env.get("CI").isDefined @@ -65,6 +67,36 @@ lazy val compat = crossProject(JSPlatform, JVMPlatform) lazy val compatJVM = compat.jvm lazy val compatJS = compat.js +lazy val `binary-compat-old` = project + .in(file("binary-compat/old")) + .settings(scalaVersion := scala212) + .disablePlugins(ScalafixPlugin) + +lazy val `binary-compat-new` = project + .in(file("binary-compat/new")) + .settings(scalaVersion := scala212) + .dependsOn(compatJVM) + .disablePlugins(ScalafixPlugin) + +lazy val `binary-compat` = project + .in(file("binary-compat/test")) + .settings( + scalaVersion := scala212, + libraryDependencies += "com.typesafe" %% "mima-reporter" % "0.3.0" % Test, + junit, + buildInfoPackage := "build", + buildInfoKeys := Seq[BuildInfoKey]( + "oldClasspath" -> (classDirectory in (`binary-compat-old`, Compile)).value.toString, + "newClasspath" -> (classDirectory in (`binary-compat-new`, Compile)).value.toString + ), + test in Test := (test in Test).dependsOn( + compile in (`binary-compat-old`, Compile), + compile in (`binary-compat-new`, Compile) + ).value + ) + .enablePlugins(BuildInfoPlugin) + .disablePlugins(ScalafixPlugin) + lazy val scalafixRules = project .in(file("scalafix/rules")) .settings( diff --git a/compat/src/main/scala-2.11_2.12/scala/collection/compat/Factory.scala b/compat/src/main/scala-2.11_2.12/scala/collection/compat/Factory.scala deleted file mode 100644 index 054e3de6..00000000 --- a/compat/src/main/scala-2.11_2.12/scala/collection/compat/Factory.scala +++ /dev/null @@ -1,37 +0,0 @@ -package scala.collection.compat - -import scala.collection.generic.CanBuildFrom -import scala.collection.{TraversableOnce, mutable} - -/** - * A factory that builds a collection of type `C` with elements of type `A`. - * - * @tparam A Type of elements (e.g. `Int`, `Boolean`, etc.) - * @tparam C Type of collection (e.g. `List[Int]`, `TreeMap[Int, String]`, etc.) - */ -trait Factory[-A, +C] extends Any { - - /** - * @return A collection of type `C` containing the same elements - * as the source collection `it`. - * @param it Source collection - */ - def fromSpecific(it: TraversableOnce[A]): C - - /** Get a Builder for the collection. For non-strict collection types this will use an intermediate buffer. - * Building collections with `fromSpecific` is preferred because it can be lazy for lazy collections. */ - def newBuilder: mutable.Builder[A, C] -} - -object Factory { - - implicit def fromCanBuildFrom[A, C](implicit cbf: CanBuildFrom[Nothing, A, C]): Factory[A, C] = - new Factory[A, C] { - def fromSpecific(it: TraversableOnce[A]): C = (cbf() ++= it).result() - def newBuilder: mutable.Builder[A, C] = cbf() - } - - implicit def fromCanBuildFromConversion[X, A, C](x: X)(implicit toCanBuildFrom: X => CanBuildFrom[Nothing, A, C]): Factory[A, C] = - fromCanBuildFrom(toCanBuildFrom(x)) - -} diff --git a/compat/src/main/scala-2.11_2.12/scala/collection/compat/PackageShared.scala b/compat/src/main/scala-2.11_2.12/scala/collection/compat/PackageShared.scala index 72780986..fe18fdf9 100644 --- a/compat/src/main/scala-2.11_2.12/scala/collection/compat/PackageShared.scala +++ b/compat/src/main/scala-2.11_2.12/scala/collection/compat/PackageShared.scala @@ -10,6 +10,33 @@ import scala.{collection => c} private[compat] trait PackageShared { import CompatImpl._ + /** + * A factory that builds a collection of type `C` with elements of type `A`. + * + * @tparam A Type of elements (e.g. `Int`, `Boolean`, etc.) + * @tparam C Type of collection (e.g. `List[Int]`, `TreeMap[Int, String]`, etc.) + */ + type Factory[-A, +C] <: CanBuildFrom[Nothing, A, C] // Ideally, this would be an opaque type + + implicit class FactoryOps[-A, +C](private val factory: Factory[A, C]) { + /** + * @return A collection of type `C` containing the same elements + * as the source collection `it`. + * @param it Source collection + */ + def fromSpecific(it: TraversableOnce[A]): C = (factory() ++= it).result() + + /** Get a Builder for the collection. For non-strict collection types this will use an intermediate buffer. + * Building collections with `fromSpecific` is preferred because it can be lazy for lazy collections. */ + def newBuilder: m.Builder[A, C] = factory() + } + + implicit def fromCanBuildFrom[A, C](implicit cbf: CanBuildFrom[Nothing, A, C]): Factory[A, C] = + cbf.asInstanceOf[Factory[A, C]] + + implicit def fromCanBuildFromConversion[X, A, C](x: X)(implicit toCanBuildFrom: X => CanBuildFrom[Nothing, A, C]): Factory[A, C] = + fromCanBuildFrom(toCanBuildFrom(x)) + implicit def genericCompanionToCBF[A, CC[X] <: GenTraversable[X]]( fact: GenericCompanion[CC]): CanBuildFrom[Any, A, CC[A]] = simpleCBF(fact.newBuilder[A])