使用sbt-assembly时的合并策略问题

29

我正在尝试使用sbt-assembly将Scala项目转换为可部署的fat jar。当我在sbt中运行我的assembly任务时,出现以下错误:

Merging 'org/apache/commons/logging/impl/SimpleLog.class' with strategy 'deduplicate'
    :assembly: deduplicate: different file contents found in the following:
    [error] /Users/home/.ivy2/cache/commons-logging/commons-logging/jars/commons-logging-1.1.1.jar:org/apache/commons/logging/impl/SimpleLog.class
    [error] /Users/home/.ivy2/cache/org.slf4j/jcl-over-slf4j/jars/jcl-over-slf4j-1.6.4.jar:org/apache/commons/logging/impl/SimpleLog.class

现在来自sbt-assembly文档:

如果多个文件共享相同的相对路径(例如,在多个依赖JAR中命名为application.conf的资源),则默认策略是验证所有候选项是否具有相同的内容,否则报错。可以使用以下内置策略之一或编写自定义策略来配置每个路径的此行为:

  • MergeStrategy.deduplicate 是上述默认值
  • MergeStrategy.first 选择类路径顺序中匹配文件的第一个
  • MergeStrategy.last 选择最后一个
  • MergeStrategy.singleOrError 在冲突时中止并显示错误消息
  • MergeStrategy.concat 简单地连接所有匹配的文件并包含结果
  • MergeStrategy.filterDistinctLines 也会连接,但沿途省略重复项
  • MergeStrategy.rename 重命名来自jar文件的文件
  • MergeStrategy.discard 简单地丢弃匹配的文件

根据此,我将我的build.sbt设置为如下:

import sbt._
import Keys._
import sbtassembly.Plugin._
import AssemblyKeys._
name := "my-project"
version := "0.1"
scalaVersion := "2.9.2"
crossScalaVersions := Seq("2.9.1","2.9.2")

//assemblySettings
seq(assemblySettings: _*)

resolvers ++= Seq(
    "Typesafe Releases Repository" at "http://repo.typesafe.com/typesafe/releases/",
    "Typesafe Snapshots Repository" at "http://repo.typesafe.com/typesafe/snapshots/",
    "Sonatype Repository" at "http://oss.sonatype.org/content/repositories/releases/"
)

libraryDependencies ++= Seq(
    "org.scalatest" %% "scalatest" % "1.6.1" % "test",
    "org.clapper" %% "grizzled-slf4j" % "0.6.10",
    "org.scalaz" % "scalaz-core_2.9.2" % "7.0.0-M7",
    "net.databinder.dispatch" %% "dispatch-core" % "0.9.5"
)

scalacOptions += "-deprecation"
mainClass in assembly := Some("com.my.main.class")
test in assembly := {}
mergeStrategy in assembly := mergeStrategy.first

在build.sbt的最后一行,我有:

mergeStrategy in assembly := mergeStrategy.first

现在,当我运行SBT时,会出现以下错误:

error: value first is not a member of sbt.SettingKey[String => sbtassembly.Plugin.MergeStrategy]
    mergeStrategy in assembly := mergeStrategy.first

有人能指出我在这里可能做错了什么吗?

谢谢


2
mergeStrategy is deprecated now. Try instead assemblyMergeStrategy. I'm using assembly version 0.14.10 - luca.giovagnoli
7个回答

15

至于当前版本0.11.2(2014-03-25),定义合并策略的方式不同了。

这在这里有记录,相关部分如下:

注意: 在assembly中,mergeStrategy期望一个函数,你不能这样做:

mergeStrategy in assembly := MergeStrategy.first
新的方式是(从同一来源复制):
mergeStrategy in assembly <<= (mergeStrategy in assembly) { (old) =>
  {
    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 => old(x)
  }
}

这可能也适用于早期版本,但我不确定它何时发生了变化。


2
最后一个例子(官方的)在Scala 10上无法工作,显示类型不匹配。 - tribbloid
@tribbloid 我已经在 assembly.sbt 中测试了这段代码,它可以正常工作(Scala 2.10.3,sbt 0.13.2)。 - Beryllium

13
我认为应该使用大写的M,即MergeStrategy.first,因此代码应该是mergeStrategy in assembly := MergeStrategy.first

拍了拍我的脑袋。那真是个疏忽大意。谢谢你指出来。 - sc_ray
1
无法正常工作,它显示:build.sbt:27: 错误:未找到值:mergeStrategy assembly中的mergeStrategy := MergeStrategy.first ^ [error] 表达式类型错误 - stantonk
@gjain 不,我基本上是复制了整个代码,并改变了合并策略如何解决冲突,通过接受第一个版本而不是失败(即Maven所做的)。:https://gist.github.com/stantonk/5303b7ec84b782a58628 我不知道为什么sbt不这样做... - stantonk
2
根据文档,这是不正确的。你不能使用mergeStrategy in assembly := MergeStrategy.first,因为mergeStrategy in assembly需要一个函数,而你会得到一个“类型不匹配”的错误。 - eliasah

12

这是合并大多数常见Java/Scala项目的正确方法,它会处理META-INF和类。

META-INF中的服务注册也会被处理。

assemblyMergeStrategy in assembly := {
case x if Assembly.isConfigFile(x) =>
  MergeStrategy.concat
case PathList(ps @ _*) if Assembly.isReadme(ps.last) || Assembly.isLicenseFile(ps.last) =>
  MergeStrategy.rename
case PathList("META-INF", xs @ _*) =>
  (xs map {_.toLowerCase}) match {
    case ("manifest.mf" :: Nil) | ("index.list" :: Nil) | ("dependencies" :: Nil) =>
      MergeStrategy.discard
    case ps @ (x :: xs) if ps.last.endsWith(".sf") || ps.last.endsWith(".dsa") =>
      MergeStrategy.discard
    case "plexus" :: xs =>
      MergeStrategy.discard
    case "services" :: xs =>
      MergeStrategy.filterDistinctLines
    case ("spring.schemas" :: Nil) | ("spring.handlers" :: Nil) =>
      MergeStrategy.filterDistinctLines
    case _ => MergeStrategy.first
  }
case _ => MergeStrategy.first}

3
+1 META-INF规则。这帮助我解决了将jpmml-spark和其他库一起打包到一个JAR中的问题。 - Elmar Macek
同意Elmar的观点 - 谢谢!被接受的答案更直接地解决了OP的问题,但这些META-INF规则是非常重要的;大多数建议(在此页面和其他地方)都会丢弃META-INF下的所有内容,但这会导致许多库所需的配置丢失。 - AlterEgo

8

我刚刚建立了一个小的sbt项目,需要重写一些合并策略,但发现答案有点过时了。让我添加我的工作代码(截至2015年4月7日)。

  • sbt 0.13.8
  • scala 2.11.6
  • assembly 0.13.0

    mergeStrategy in assembly := {
      case x if x.startsWith("META-INF") => MergeStrategy.discard // Bumf
      case x if x.endsWith(".html") => MergeStrategy.discard // More bumf
      case x if x.contains("slf4j-api") => MergeStrategy.last
      case x if x.contains("org/cyberneko/html") => MergeStrategy.first
      case PathList("com", "esotericsoftware", xs@_ *) => MergeStrategy.last // For Log$Logger.class
      case x =>
         val oldStrategy = (mergeStrategy in assembly).value
         oldStrategy(x)
    }
    

10
mergeStrategy 已经被弃用,请使用 assemblyMergeStrategy 代替。 - Chunliang Lyu

4

1

快速更新:mergeStrategy已经被弃用。请使用assemblyMergeStrategy。除此之外,先前的响应仍然是可靠的。


0

在build.sbt中添加以下内容,将kafka作为源或目的地

 assemblyMergeStrategy in assembly := {
 case PathList("META-INF", xs @ _*) => MergeStrategy.discard
 //To add Kafka as source
 case "META-INF/services/org.apache.spark.sql.sources.DataSourceRegister" => 
 MergeStrategy.concat
 case x => MergeStrategy.first
 }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接