User Stories: Conflict Warning
This is a more detailed spec on sbt#1200 shell should display all evicted libraries on start up, which was inspired by StackOverflow#22551430 In SBT 0.13, does scalaVersion still control the version of scala used for compile, run and test? Meet Alice, a build user. She is using future version of sbt.
Alice specifies her scalaVersion
to 2.10.2. She also wants to use Akka 2.3.0.
scalaVersion := "2.10.2"
libraryDependencies += "com.typesafe.akka" %% "akka-actor" % "2.3.0"
When the first time Alice runs clean
and compile
or update
, sbt detects that scalaVersion
specified by Alice is no longer in effect and displays a friendly warning:
[warn] scala version was updated by one of library dependencies:
[warn] - org.scala-lang:scala-library:2.10.2 -> 2.10.3
This warning should apply to any of the libraries from "org.scala-lang" organization including reflection libraries, XML libraries etc.
Alice includes Apache commons-io 1.4 and 2.4 in a project:
id := "p1"
libraryDependencies ++= Seq(
"commons-io" % "commons-io" % "2.4",
"commons-io" % "commons-io" % "1.4"
)
When the first time Alice runs clean
and compile
or update
, sbt detects that there's a potentially binary incompatible libraries in her project's direct dependencies, and displays a warning indicating that the source of the dependencies is her build:
[warn] There may be incompatibilities among your library dependencies.
[warn] Here are the libraries that were evicted:
[warn] - commons-io:commons-io:1.4 (parent: p1_2.10) -> 2.4
Here we make an assumption that first non-zero segment is different, it's a suspect for binary incompatibility.
Parent info is available as "caller" in ivy resolution report:
<revision name="1.4" evicted="latest-revision" evicted-reason="" downloaded="false" searched="false" conf="default(compile), default, compile, runtime, master" position="-1">
<evicted-by rev="2.4"/>
<caller organisation="p1" name="p1_2.10" conf="compile" rev="1.4" rev-constraint-default="1.4" rev-constraint-dynamic="1.4" callerrev="0.1-SNAPSHOT"/>
<artifacts>
</artifacts>
</revision>
As the project is getting bigger, Alice decides to split it up into two parts.
lazy val util = (project in file("util")).
settings(
libraryDependencies += "commons-io" % "commons-io" % "1.4"
)
lazy val app = (project in file("app")).
settings(
libraryDependencies += "commons-io" % "commons-io" % "2.4"
).
dependsOn(util)
When the first time Alice runs clean
and compile
or update
on app
project, sbt detects that there's a potentially binary incompatible libraries in her project's dependencies graph, and displays a warning indicating that the source of the dependencies is her build:
[warn] There may be incompatibilities among your library dependencies.
[warn] Here are the libraries that were evicted:
[warn] - commons-io:commons-io:1.4 (parent: util_2.10) -> 2.4
Ivy resolution report includes the parent's name:
<revision name="1.4" evicted="latest-revision" evicted-reason="" downloaded="false" searched="false" conf="default(compile)" position="-1">
<evicted-by rev="2.4"/>
<caller organisation="util" name="util_2.10" conf="compile" rev="1.4" rev-constraint-default="1.4" rev-constraint-dynamic="1.4" callerrev="0.1-SNAPSHOT"/>
<artifacts>
</artifacts>
</revision>
Alice realizes that Scala libraries break binary compatibility at the minor version updates, unlike Java libraries. In fact, generally accepted naming of major.minor.patch
often doesn't apply to Scala libraries at all. Here are some samplings from Top 100 Most popular Scala libraries:
- Scala standard libraries 2.10.x are binary compatible with 2.10.0, but not with 2.9 or 2.8.
- ScalaTest 2.1.2 It appears that ScalaTest 2.0.x is source compat, but not bincompat. "However, 2.1.2 is binary compatible with 2.1.0 and 2.1.1"
- Play 2.2 It appears that Play 2.2 is not compat with Play 2.0 in any way.
- Not sure about Specs2, but I suspect 2.x and 2.0 are not binary compat.
- Akka 2.3 It appears that Akka 2.3 is not compat with Akka 2.0 in any way.
Why are they all 2.x? Let's say Alice uses Akka Actor 2.2.4 and 2.3.2.
lazy val util = (project in file("util")).
settings(
libraryDependencies ++= Seq(
"commons-io" % "commons-io" % "1.4",
"com.typesafe.akka" %% "akka-actor" % "2.2.4"
)
)
lazy val app = (project in file("app")).
settings(
libraryDependencies ++= Seq(
"commons-io" % "commons-io" % "2.4",
"com.typesafe.akka" %% "akka-actor" % "2.3.2"
)
).
dependsOn(util)
When the first time Alice runs clean
and compile
or update
on app
project, sbt detects that there's a potentially binary incompatible libraries in her project's dependencies graph, and displays a warning indicating that the source of the dependencies is her build:
[warn] There may be incompatibilities among your library dependencies.
[warn] Here are the libraries that were evicted:
[warn] - commons-io:commons-io:1.4 (parent: util_2.10) -> 2.4
[warn] - com.typesafe.akka:akka-actor_2.10:2.2.4 (parent: util_2.10) -> 2.3.2
The Scala libraries can be differentiated because many of them append Scala binary version, which is denoted as %%
in the libraryDependencies. In those cases, the first two segments should be used regardless of zero.
User Story: Warn if transitive dependencies include suspect binary incompatibilities of cross versioned Scala libs
As a variant of the above story, sbt should be able to detect potential binary incompatibilities among the cross versioned library and non-cross versioned one as follows:
libraryDependencies ++= Seq(
"com.typesafe.akka" %% "akka-actor" % "2.2.1",
"com.typesafe.akka" % "akka-actor" % "2.0.1"
)
Alice realized she can utilize incompatibility report to streamline her internal organization's library usage. Alice can customize incompatibility algorithm based on ModuleID
of the library.
def fullCheck(ModuleID, ModuleID): Level = ???
incompatibilityDetection := {
val old = incompatibilityDetection.value
{
case ModuleID("com.example.internal", _, _, _, _, _, _, _, _, _, _) => fullCheck
case x => old(x)
}
}