Eviction of a specific transitive dependency in sbt
Let's assume a small test project (sbt 0.13.8, complete project as gist ):
name := "test"
organization := "org.example"
version := "0.1.0-SNAPSHOT"
scalaVersion := "2.11.6"
libraryDependencies ++= Seq (
"com.lowagie" % "itext" % "4.2.1",
"com.github.wookietreiber" %% "scala-chart" % "0.4.2"
)
The dependency tree looks like this:
dependencyTree
[info] org.example:test_2.11:0.1.0-SNAPSHOT [S]
[info] +-com.github.wookietreiber:scala-chart_2.11:0.4.2 [S]
[info] | +-org.jfree:jfreechart:1.0.17
[info] | | +-org.jfree:jcommon:1.0.21
[info] | | +-xml-apis:xml-apis:1.3.04
[info] | |
[info] | +-org.scala-lang.modules:scala-swing_2.11:1.0.1 [S]
[info] |
[info] +-com.lowagie:itext:4.2.1
[info] +-bouncycastle:bctsp-jdk14:138
[info] | +-org.bouncycastle:bctsp-jdk14:1.38
[info] | +-org.bouncycastle:bcmail-jdk14:1.38
[info] | | +-org.bouncycastle:bcprov-jdk14:1.38
[info] | |
[info] | +-org.bouncycastle:bcprov-jdk14:1.38
[info] |
[info] +-dom4j:dom4j:1.6.1
[info] | +-xml-apis:xml-apis:1.0.b2 (evicted by: 1.3.04)
[info] | +-xml-apis:xml-apis:1.3.04
[info] |
[info] +-jfree:jfreechart:1.0.12
[info] | +-jfree:jcommon:1.0.15
[info] |
[info] +-org.swinglabs:pdf-renderer:1.0.5
[info]
[success] Total time: 0 s, completed Jun 2, 2015 12:16:14 PM
The problem is, depending on jfreechart
which is drawn in scala-chart
, and in itext
(the same applies to jcommon
, but I will focus on jfreechart
). However, the slightly older version that is being pulled itext
in uses a different name for the organization ( jfree
vs. org.jfree
).
Usually, if they use the same organization, the older one can be evicted (as in example c xml-apis
), and there will be no conflicts if binary compatibility exists.
The real conflict now is that both dependencies jfree:jfreechart
and org.jfree:jfreechart
are on the classpath and provide the same classes. The compilation works fine and sbt doesn't even complain. Take the following example for example App
:
package org.example
import scalax.chart.api._
object Main extends App {
val data = for {
i <- 1 to 5
category = i.toString
date = new org.jfree.data.time.Day(i, 2, 1998)
value = i * 2
} yield category -> Map(date -> value)
val chart = XYAreaChart.stacked(data.toTimeTable)
chart.saveAsPDF("/tmp/my-chart.pdf")
}
This compiles just fine! I don't know how sbt picks which dependency to use. The whole thing will explode if you try to actually run the thing:
sbt @ test $ compile
[info] Updating {file:/home/wookietreiber/projects/scala/jfree-itext-test/}jfree-itext-test...
[info] Resolving jline#jline;2.12.1 ...
[info] Done updating.
[info] Compiling 1 Scala source to /tmp/sbt/test/scala-2.11/classes...
[success] Total time: 1 s, completed Jun 2, 2015 1:18:36 PM
sbt @ test $ run
[info] Running org.example.Main
[error] (run-main-1) java.lang.NoSuchMethodError: org.jfree.data.time.TimeTableXYDataset.add(Lorg/jfree/data/time/TimePeriod;Ljava/lang/Number;Ljava/lang/Comparable;Z)V
java.lang.NoSuchMethodError: org.jfree.data.time.TimeTableXYDataset.add(Lorg/jfree/data/time/TimePeriod;Ljava/lang/Number;Ljava/lang/Comparable;Z)V
at scalax.chart.module.RichChartingCollections$RichCategorizedTuple2s$$anonfun$toTimeTable$1$$anonfun$apply$4.apply(RichChartingCollections.scala:300)
...
To solve this problem, I need to be specially evict / remove the old version jfree:jfreechart
and jfree:jcommon
the list of dependencies / classpath. Is this possible, and if so, how?
Another problem I have found is with sbt-assembly
errors like the following (for each duplicate file):
[error] deduplicate: different file contents found in the following:
[error] /home/wookietreiber/.ivy2/cache/jfree/jfreechart/jars/jfreechart-1.0.12.jar:org/jfree/data/KeyedValues2D.class
[error] /home/wookietreiber/.ivy2/cache/org.jfree/jfreechart/jars/jfreechart-1.0.17.jar:org/jfree/data/KeyedValues2D.class
source to share
You need mergeStragey in assembly
to define * .sbt or * .scala in the project definition in order to work with the sbt-assembly command. Below is an example.
mergeStrategy in assembly <<= (mergeStrategy in assembly) { (old) =>
{
case PathList("javax", "servlet", xs @ _*) => MergeStrategy.last
case PathList("javax", "activation", xs @ _*) => MergeStrategy.last
case PathList("org", "apache", xs @ _*) => MergeStrategy.last
case PathList("com", "google", xs @ _*) => MergeStrategy.last
case PathList("com", "esotericsoftware", xs @ _*) => MergeStrategy.last
case PathList("com", "codahale", xs @ _*) => MergeStrategy.last
case PathList("com", "yammer", xs @ _*) => MergeStrategy.last
case "about.html" => MergeStrategy.rename
case "META-INF/ECLIPSEF.RSA" => MergeStrategy.last
case "META-INF/mailcap" => MergeStrategy.last
case "META-INF/mimetypes.default" => MergeStrategy.last
case "plugin.properties" => MergeStrategy.last
case "log4j.properties" => MergeStrategy.last
case x => old(x)
}
}
If you have a clear idea of ββwhat the transitive jar is that is causing the problem, you can exclude it in your library. For example, I excluded the httpclient jar from my direct game dependency 2.3.6 which depends on a higher version of httpclient, which I didn't want.
"com.typesafe.play" %% "play" % "2.3.6" exclude("org.apache.httpcomponents", "httpclient")
In your case, you can do exclude("jfree", "jfreechart")
for your direct dependence, which introduced this old jar.
source to share