【问题标题】:Why Maven assembly works when SBT assembly find conflicts为什么当 SBT 程序集发现冲突时 Maven 程序集工作
【发布时间】:2018-10-19 08:41:20
【问题描述】:

标题也可以是:
Maven和SBT组装插件有什么区别。

在将项目从 Maven 迁移到 SBT 时,我发现这是一个问题。

为了描述问题,我创建了一个示例项目,其中包含我发现行为不同的依赖项,具体取决于构建工具。

https://github.com/atais/mvn-sbt-assembly


唯一的依赖是(sbt风格)

"com.netflix.astyanax" % "astyanax-cassandra" % "3.9.0",
"org.apache.cassandra" % "cassandra-all" % "3.4",

而我不明白的是,为什么mvn package 成功创建了胖罐,而sbt assembly 给出了冲突:

[error] 39 errors were encountered during merge
[error] java.lang.RuntimeException: deduplicate: different file contents found in the following:
[error] /home/siatkowskim/.ivy2/cache/org.slf4j/jcl-over-slf4j/jars/jcl-over-slf4j-1.7.7.jar:org/apache/commons/logging/<some classes>
[error] /home/siatkowskim/.ivy2/cache/commons-logging/commons-logging/jars/commons-logging-1.1.1.jar:org/apache/commons/logging/<some classes>
...
[error] /home/siatkowskim/.ivy2/cache/com.github.stephenc.high-scale-lib/high-scale-lib/jars/high-scale-lib-1.1.2.jar:org/cliffc/high_scale_lib/<some classes>
[error] /home/siatkowskim/.ivy2/cache/com.boundary/high-scale-lib/jars/high-scale-lib-1.0.6.jar:org/cliffc/high_scale_lib/<some classes>
...

【问题讨论】:

标签: maven sbt maven-3 maven-assembly-plugin sbt-assembly


【解决方案1】:

扩展至Alexey Romanov answer

我还更新了my project 的详细解释,所以你可能想看看。

听从建议

您可以通过解压 Maven 生成的 jar 和 SBT 错误消息中的依赖 jar 来验证这种情况,然后检查 Maven 使用了哪个 .class 文件。

我比较了mavensbt产生的fat-jars

  • MergeStrategy.first,显示了一些额外的文件
  • MergeStrategy.last,显示二进制差异和额外文件

我已采取下一步措施,并检查了 fat-jarssbt 的依赖项,发现冲突在以下位置:

结论

maven-assembly-plugin 解决jar 级别的冲突。 当它发现任何冲突时,它会选择第一个 jar 并忽略其他的所有内容。

sbt-assembly 混合所有class 文件,在本地解决冲突,逐个文件。

我的理论是,如果你的 fat-jarmaven-assembly-plugin 制作,你可以 为sbt 中的所有冲突指定MergeStrategy.first。 它们唯一的区别是,用sbt 生成的jar 会更大,包含maven 忽略的额外类。

【讨论】:

  • 哇,它的解析策略比我想象的还要糟糕 :)
【解决方案2】:

似乎maven-assembly-plugin 解决冲突相当于MergeStrategy.first(不确定它是否完全等效)只需在@时以未指定的方式选择其中一个文件使用 987654327@(自it only has one phase):

If two or more elements (e.g., file, fileSet) select different sources for the same file for archiving, only one of the source files will be archived.

根据 2.5.2 版的程序集插件,将文件添加到存档的第一阶段“获胜”。过滤仅基于存档中的名称完成,因此可以在不同的输出名称下添加相同的源文件。阶段顺序如下:1) FileItem 2) FileSets 3) ModuleSet 4) DepenedencySet 和 5) Repository 元素。

相同类型的元素将按照它们在描述符中出现的顺序进行处理。如果您需要“覆盖”前一组包含的文件,唯一的方法就是将该文件从前一组中排除。

请注意,此行为在早期版本的程序集插件中略有不同。

即使其中一个冲突文件适用于您的所有依赖项(不一定如此),Maven 也不知道是哪一个,因此您可以默默地得到错误的结果。我的意思是,在构建时默默地;在运行时,您可以获得例如AbstractMethodError,或者再次只是一个错误的结果。

您可以通过编写自己的描述符来影响选择哪个文件,但它非常冗长,不等同于仅编写 MergeStrategy.first/last(并且不允许使用 concat/discard)。

SBT 插件可以做同样的事情:当你没有指定一个策略时,默认为一个策略,但是,你可能会默默地得到错误的结果。

【讨论】:

  • 似乎有可能,它的行为类似于MergeStrategy.first,但不同的来源陈述不同的事情。铁。 gist.github.com/simonwoo/04b133cb0745e1a0f1d6 说它会“导致 Java 类名冲突问题”。如果你能找到某种方法来确认它的工作方式,我会 110% 满意。
  • 嗯,文档更可能是正确的而不是随机的要点。但他们实际上并不反对:它导致的问题可能正是“你默默地得到了错误的结果”。
  • 您可以通过解压 Maven 生成的 jar 和 SBT 错误消息中的依赖 jar 来验证它对于这种情况,然后检查 Maven 使用了哪个 .class 文件。对于一般情况,您必须依赖文档,或者查看maven-assembly-plugin 的来源。
  • @Atais 实际上,在仔细阅读它所说的内容之后,并查看maven.apache.org/plugins/maven-assembly-plugin/…jar-with-dependencies的定义,它可以选择任意.class文件,不一定是第一个:第一个 phase 获胜,但在 jar-with-dependencies 中只有一个阶段包含所有依赖项。所以你确实需要查看来源。
  • @Atais 但是它只选择一个类,如果它选择了错误的类,在运行程序(如果你幸运的话)之前你不会知道这一点保持不变。除了它选择哪一个不太可预测。
【解决方案3】:

build.sbt 我可以看到他们在你的构建中没有合并策略。另外,您的 libraryDependencies Key 中还有一个 Rogue "," 放置在 "org.apache.cassandra" % "cassandra-all" % "3.4 的依赖项之后" 在您上面共享的链接的项目中的 build.sbt 中。

需要一个合并策略来处理所有重复的文件和 jar 以及版本。以下是如何在您的构建中使用的示例。

assemblyMergeStrategy in assembly := {
  case m if m.toLowerCase.endsWith("manifest.mf")       => MergeStrategy.discard
  case m if m.toLowerCase.matches("meta-inf.*\\.sf$")   => MergeStrategy.discard
  case "reference.conf"                                 => MergeStrategy.concat
  case x: String if x.contains("UnusedStubClass.class") => MergeStrategy.first
  case _                                                => MergeStrategy.first
}

如果您的项目中没有子项目,您可以尝试编写一个简单的构建文件。你可以试试下面的 build.sbt。

name := "assembly-test",

version := "0.1",

scalaVersion := "2.12.4",

libraryDependencies ++= Seq(
      "com.netflix.astyanax" % "astyanax-cassandra" % "3.9.0",
      "org.apache.cassandra" % "cassandra-all" % "3.4"
)

mainClass in assembly := Some("com.atais.cassandra.MainClass")

assemblyMergeStrategy in assembly := {
      case m if m.toLowerCase.endsWith("manifest.mf")       => MergeStrategy.discard
      case m if m.toLowerCase.matches("meta-inf.*\\.sf$")   => MergeStrategy.discard
      case "reference.conf"                                 => MergeStrategy.concat
      case x: String if x.contains("UnusedStubClass.class") => MergeStrategy.first
      case _                                                => MergeStrategy.first
    }

【讨论】:

  • 我知道assemblyMergeStrategy,多余的逗号无关紧要。你没有回答问题。我知道如何“让它发挥作用”。我想了解为什么它没有。此外,您的合并策略看起来非常随机,这不是一个好主意,imo。
猜你喜欢
  • 1970-01-01
  • 2015-04-10
  • 2013-07-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-07
相关资源
最近更新 更多