[转载] sbt发布assembly解决jar包冲突问题 deduplicate: different file contents found in the following

原文地址:http://blog.csdn.net/oopsoom/article/details/41318599

一、问题定义

最近在用sbt打assembly包时出现问题,在package的时候,发生jar包冲突/文件冲突问题,两个相同的class来自不同的jar包在classpath内引起冲突。

具体是:我有一个self4j的jar, 还有一个hadoop-common-hdfs的jar包,其中hadoop-common-hdfs.jar内包含了self4j这个jar包,导致冲突。
此类异常一般是由于打包不规范和打包疏忽引起的。
(个人认为正确的打包策略是:只打包自己核心功能,不将依赖打包在一起,但是有时为了方便或者有时不得不打在一起,要注意可能会出现上述问题)

异常log如下:

[trace] Stack trace suppressed: run last *:assembly for the full output.  
[error] (*:assembly) deduplicate: different file contents found in the following:  
[error] C:\Users\shengli.victor\.ivy2\cache\org.slf4j\slf4j-api\jars\slf4j-api-1.7.7.jar:org/slf4j/IMarkerFactory.class  
[error] C:\Users\shengli.victor\.ivy2\cache\com.xxx.xx.hdfsfile\hdfscommon\jars\hdfscommon-1.1.jar:org/slf4j/IMarkerFactory.class  
[error] Total time: 4 s, completed 2014-11-20 19:07:33  

异常很明显,来自2个不同的jar包self4j, hdfscommon-1.1.jar里,在org/slf4j/IMarkerFactory.class这个类冲突了。
如下图:

hdfscommon-1.1/jar
slf4j-api-1.7.2.jar

二、解决方案

解决jar包冲突有两种方案:
1、删除其中的某个jar,或者说,在打包的时候,不将2个相同的class打在同一个jar包内的classpath内,即exclude jar。
2、合并冲突

1. Excluding JARs and files

% "provided"
将相同的jar中排除一个,因为重复,可以使用"provided"关键字。
例如spark是一个容器类,编写spark应用程序我们需要spark core jar. 但是真正打包提交到集群上执行,则不需要将它打入jar包内。
这是我们使用 % "provided" 关键字来exclude它。

libraryDependencies ++= Seq(  
  "org.apache.spark" %% "spark-core" % "0.8.0-incubating" % "provided",  
  "org.apache.hadoop" % "hadoop-client" % "2.0.0-cdh4.4.0" % "provided"  
)  

Maven defines "provided" as:

This is much like compile, but indicates you expect the JDK or a container to provide the dependency at runtime. For example, when building a web application for the Java Enterprise Edition, you would set the dependency on the Servlet API and related Java EE APIs to scopeprovided because the web container provides those classes. This scope is only available on the compilation and test classpath, and is not transitive.

2、Merge Strategy

如果在相对路径下,有多个相同的文件或者jar,这时我们可以使用Merge策略。
在build.sbt中对assemblyMergeStrategy 进行定义。
如下:

mergeStrategy in assembly <<= (mergeStrategy in assembly) { (old) =>  
  {  
    case PathList("org", "slf4j", xs @ _*)         => MergeStrategy.first  
    case PathList(ps @ _*) if ps.last endsWith "axiom.xml" => MergeStrategy.filterDistinctLines  
    case PathList(ps @ _*) if ps.last endsWith "Log$Logger.class" => MergeStrategy.first  
    case PathList(ps @ _*) if ps.last endsWith "ILoggerFactory.class" => MergeStrategy.first  
    case x => old(x)  
  }  
}  

解决方法:将org, slf4j 这个下的所有类和文件,都做合并, 采用的策略是:在classpath里,2选1,选的是classpath顺序里第一个self4j。
这里支持多种格式,例如ps.lat endsWith "axiom.xml" ,是以axiom.xml为结尾的文件,都采用filterDistinctLines策略,即合并两个文件,舍去重复的部分。

通过以上修改,终于解决了slf4j冲突的问题,即deduplicate: different file contents found in the following问题。

再次sbt assembly:

[warn] Merging 'META-INF\INDEX.LIST' with strategy 'discard'  
[warn] Merging 'META-INF\MANIFEST.MF' with strategy 'discard'  
[warn] Merging 'META-INF\maven\log4j\log4j\pom.properties' with strategy 'first'  
[warn] Merging 'META-INF\maven\log4j\log4j\pom.xml' with strategy 'first'  
[warn] Merging 'META-INF\maven\org.slf4j\slf4j-api\pom.properties' with strategy 'first'  
[warn] Merging 'META-INF\maven\org.slf4j\slf4j-api\pom.xml' with strategy 'first'  
[warn] Merging 'META-INF\maven\org.slf4j\slf4j-log4j12\pom.properties' with strategy 'first'  
[warn] Merging 'META-INF\maven\org.slf4j\slf4j-log4j12\pom.xml' with strategy 'first'  
[warn] Merging 'com\esotericsoftware\minlog\Log$Logger.class' with strategy 'first'  
[warn] Merging 'com\esotericsoftware\minlog\Log.class' with strategy 'first'  
[warn] Merging 'org\apache\log4j\helpers\LogLog.class' with strategy 'first'  
[warn] Merging 'org\slf4j\ILoggerFactory.class' with strategy 'first'  
[warn] Merging 'org\slf4j\IMarkerFactory.class' with strategy 'first'  
[warn] Merging 'org\slf4j\Logger.class' with strategy 'first'  
[warn] Merging 'org\slf4j\LoggerFactory.class' with strategy 'first'  
[warn] Merging 'org\slf4j\MDC.class' with strategy 'first'  
[warn] Merging 'org\slf4j\Marker.class' with strategy 'first'  
[warn] Merging 'org\slf4j\MarkerFactory.class' with strategy 'first'  
[warn] Merging 'org\slf4j\helpers\BasicMDCAdapter.class' with strategy 'first'  
[warn] Merging 'org\slf4j\helpers\BasicMarker.class' with strategy 'first'  
[warn] Merging 'org\slf4j\helpers\BasicMarkerFactory.class' with strategy 'first'  
[warn] Merging 'org\slf4j\helpers\FormattingTuple.class' with strategy 'first'  
[warn] Merging 'org\slf4j\helpers\MarkerIgnoringBase.class' with strategy 'first'  
[warn] Merging 'org\slf4j\helpers\MessageFormatter.class' with strategy 'first'  
[warn] Merging 'org\slf4j\helpers\NOPLogger.class' with strategy 'first'  
[warn] Merging 'org\slf4j\helpers\NOPLoggerFactory.class' with strategy 'first'  
[warn] Merging 'org\slf4j\helpers\NOPMDCAdapter.class' with strategy 'first'  
[warn] Merging 'org\slf4j\helpers\NamedLoggerBase.class' with strategy 'first'  
[warn] Merging 'org\slf4j\helpers\SubstituteLoggerFactory.class' with strategy 'first'  
[warn] Merging 'org\slf4j\helpers\Util.class' with strategy 'first'  
[warn] Merging 'org\slf4j\impl\Log4jLoggerAdapter.class' with strategy 'first'  
[warn] Merging 'org\slf4j\impl\Log4jLoggerFactory.class' with strategy 'first'  
[warn] Merging 'org\slf4j\impl\Log4jMDCAdapter.class' with strategy 'first'  
[warn] Merging 'org\slf4j\impl\StaticLoggerBinder.class' with strategy 'first'  
[warn] Merging 'org\slf4j\impl\StaticMDCBinder.class' with strategy 'first'  
[warn] Merging 'org\slf4j\impl\StaticMarkerBinder.class' with strategy 'first'  
[warn] Merging 'org\slf4j\spi\LocationAwareLogger.class' with strategy 'first'  
[warn] Merging 'org\slf4j\spi\LoggerFactoryBinder.class' with strategy 'first'  
[warn] Merging 'org\slf4j\spi\MDCAdapter.class' with strategy 'first'  
[warn] Merging 'org\slf4j\spi\MarkerFactoryBinder.class' with strategy 'first'  
[warn] Merging 'rootdoc.txt' with strategy 'concat'  
[warn] Strategy 'concat' was applied to a file  
[info] Strategy 'deduplicate' was applied to 373 files (Run the task at debug level to see details)  
[warn] Strategy 'discard' was applied to 2 files  
[warn] Strategy 'first' was applied to 38 files  
[java] view plain copy 在CODE上查看代码片派生到我的代码片
[info] Done packaging.  
[success] Total time: 84 s, completed 2014-11-20 19:04:52  

合并策略有很多种:
可以参考官方sbt assembly文档:https://github.com/sbt/sbt-assembly
http://stackoverflow.com/questions/19606243/resolving-dependencies-in-creating-jar-through-sbt-assembly
MergeStrategy.deduplicate is the default described above
MergeStrategy.first picks the first of the matching files in classpath order
MergeStrategy.last picks the last one
MergeStrategy.singleOrError bails out with an error message on conflict
MergeStrategy.concat simply concatenates all matching files and includes the result
MergeStrategy.filterDistinctLines also concatenates, but leaves out duplicates along the way
MergeStrategy.rename renames the files originating from jar files
MergeStrategy.discard simply discards matching files
更多的写法,example:

assemblyMergeStrategy in assembly := {
  case PathList("javax", "servlet", xs @ _*)         => MergeStrategy.first
  case PathList(ps @ _*) if ps.last endsWith ".html" => MergeStrategy.first
  case "application.conf"                            => MergeStrategy.concat
  case "unwanted.txt"                                => MergeStrategy.discard
  case x =>
    val oldStrategy = (assemblyMergeStrategy in assembly).value
    oldStrategy(x)
}

Final Qucik Hack:
如果以上写法都不奏效,还有最好一种,强制默认全部合并,不到万不得已,不要用。。

mergeStrategy in assembly <<= (mergeStrategy in assembly) { mergeStrategy => {  
 case entry => {  
   val strategy = mergeStrategy(entry)  
   if (strategy == MergeStrategy.deduplicate) MergeStrategy.first  
   else strategy  
 }  
}}  

三、总结

碰到类似的问题不要慌张,仔细看log描述的是什么意思。
异常报出内容冗余的冲突,在看路径,发现在classpath内有完全相同的2个类,这是导致问题的根本原因。
找出原因,解决方发,消除冲突两种方法,一直是去除法,另一种是合并法。
相对于maven和gradle,sbt的冲突解决方法还是比较接近底层。如果没记错的话,maven和gradle都能自动解决冲突。
本文仅针对该问题提出解决方案和思路,具体的各个配置还需要继续研究。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容