Почему гуава неправильно затеняется в моем build.sbt?

tl;dr: Вот репозиторий, содержащий проблему.


Cassandra и HDFS используют внутреннюю гуаву, но ни один из них не скрывает зависимость по разным причинам. Поскольку версии гуавы несовместимы в двоичном формате, я нахожу NoSuchMethodError во время выполнения.

Я пытался сам затенить гуаву в своем build.sbt:

val HadoopVersion =  "2.6.0-cdh5.11.0"

// ...

val hadoopHdfs = "org.apache.hadoop" % "hadoop-hdfs" % HadoopVersion
val hadoopCommon = "org.apache.hadoop" % "hadoop-common" % HadoopVersion
val hadoopHdfsTest = "org.apache.hadoop" % "hadoop-hdfs" % HadoopVersion % "test" classifier "tests"
val hadoopCommonTest = "org.apache.hadoop" % "hadoop-common" % HadoopVersion % "test" classifier "tests"
val hadoopMiniDFSCluster = "org.apache.hadoop" % "hadoop-minicluster" % HadoopVersion % Test

// ...

assemblyShadeRules in assembly := Seq(
  ShadeRule.rename("com.google.common.**" -> "shade.com.google.common.@1").inLibrary(hadoopHdfs).inProject,
  ShadeRule.rename("com.google.common.**" -> "shade.com.google.common.@1").inLibrary(hadoopCommon).inProject,
  ShadeRule.rename("com.google.common.**" -> "shade.com.google.common.@1").inLibrary(hadoopHdfsTest).inProject,
  ShadeRule.rename("com.google.common.**" -> "shade.com.google.common.@1").inLibrary(hadoopCommonTest).inProject,
  ShadeRule.rename("com.google.common.**" -> "shade.com.google.common.@1").inLibrary(hadoopMiniDFSCluster).inProject
)

assemblyJarName in assembly := s"${name.value}-${version.value}.jar"

assemblyMergeStrategy in assembly := {
  case PathList("META-INF", "MANIFEST.MF") => MergeStrategy.discard
  case _ => MergeStrategy.first
}

но исключение во время выполнения сохраняется (ха, люди, это шутка про Кассандру).

Конкретным исключением является

[info] HdfsEntitySpec *** ABORTED ***
[info]   java.lang.NoSuchMethodError: com.google.common.base.Objects.toStringHelper(Ljava/lang/Object;)Lcom/google/common/base/Objects$ToStringHelper;
[info]   at org.apache.hadoop.metrics2.lib.MetricsRegistry.toString(MetricsRegistry.java:406)
[info]   at java.lang.String.valueOf(String.java:2994)
[info]   at java.lang.StringBuilder.append(StringBuilder.java:131)
[info]   at org.apache.hadoop.ipc.metrics.RetryCacheMetrics.<init>(RetryCacheMetrics.java:46)
[info]   at org.apache.hadoop.ipc.metrics.RetryCacheMetrics.create(RetryCacheMetrics.java:53)
[info]   at org.apache.hadoop.ipc.RetryCache.<init>(RetryCache.java:202)
[info]   at org.apache.hadoop.hdfs.server.namenode.FSNamesystem.initRetryCache(FSNamesystem.java:1038)
[info]   at org.apache.hadoop.hdfs.server.namenode.FSNamesystem.<init>(FSNamesystem.java:949)
[info]   at org.apache.hadoop.hdfs.server.namenode.FSNamesystem.<init>(FSNamesystem.java:796)
[info]   at org.apache.hadoop.hdfs.server.namenode.NameNode.format(NameNode.java:1040)
[info]   ...

Как я могу правильно затенить гуаву, чтобы остановить ошибки времени выполнения?


person erip    schedule 20.12.2017    source источник
comment
Можете ли вы опубликовать ошибку, которую вы видите?   -  person Tim Moore    schedule 20.12.2017
comment
Я думаю, вам также нужно переименовать в самой библиотеке гуавы.   -  person Tim Moore    schedule 20.12.2017
comment
Вы, вероятно, захотите следовать процессу, подобному этому: manuzhang.github.io/ 15.10.2016/shading.html   -  person Tim Moore    schedule 20.12.2017
comment
@TimMoore отредактировал сообщение. Сейчас пытаюсь затенить гуаву в более широком смысле.   -  person erip    schedule 20.12.2017
comment
@TimMoore Не повезло с затенением "com.google.**".   -  person erip    schedule 20.12.2017
comment
Насколько мне известно, затенение с помощью ассемблера имеет некоторые ограничения, я рекомендую вам взглянуть на этот альтернативный плагин затенения в Coursier github.com/coursier/coursier/tree/master/sbt-shading/src.   -  person Jorge Vicente Cantero    schedule 21.12.2017
comment
@JorgeVicenteCantero Это не задокументировано.   -  person erip    schedule 21.12.2017
comment
РЖУ НЕ МОГУ. Ты умеешь читать код, мой друг. Сам пользователь этого плагина есть в билде coursier, если хотите пример. Проблема с ассемблером заключается в том, что он не может транзитивно затенять вещи, что может сделать IIRC. Стоило того.   -  person Jorge Vicente Cantero    schedule 21.12.2017
comment
вы получаете ошибку при запуске тестов? затенение будет накапливаться только при создании толстой банки, а не во время обычной компиляции   -  person lev    schedule 22.12.2017
comment
@lev это верно. Я пробовал sbt test и sbt assemble:test.   -  person erip    schedule 22.12.2017
comment
@JorgeVicenteCantero Я пытался использовать coursier, но не совсем понял. Вот моя попытка.   -  person erip    schedule 22.12.2017


Ответы (1)


Правила затенения будут применяться только тогда, когда вы строите толстую банку. Он не будет применяться во время других задач sbt.

Если вы хотите затенить некоторую библиотеку внутри ваших зависимостей от Hadoop, вы можете создать новый проект только с зависимостями от Hadoop, затенить библиотеки и опубликовать толстый банк со всеми затененными зависимостями от Hadoop.

Это не идеальное решение, потому что все зависимости в новом jar-файле Hadoop будут «неизвестны» тем, кто их использует, и вам нужно будет вручную обрабатывать конфликты.

Вот код, который вам понадобится в вашем build.sbt, чтобы опубликовать толстую банку Hadoop (используя ваш код и сборку sbt документы):

val HadoopVersion =  "2.6.0-cdh5.11.0"

val hadoopHdfs = "org.apache.hadoop" % "hadoop-hdfs" % HadoopVersion
val hadoopCommon = "org.apache.hadoop" % "hadoop-common" % HadoopVersion
val hadoopHdfsTest = "org.apache.hadoop" % "hadoop-hdfs" % HadoopVersion classifier "tests"
val hadoopCommonTest = "org.apache.hadoop" % "hadoop-common" % HadoopVersion %  classifier "tests"
val hadoopMiniDFSCluster = "org.apache.hadoop" % "hadoop-minicluster" % HadoopVersion 

lazy val fatJar = project
  .enablePlugins(AssemblyPlugin)
  .settings(
    libraryDependencies ++= Seq(
        hadoopHdfs,
        hadoopCommon,
        hadoopHdfsTest,
        hadoopCommonTest,
        hadoopMiniDFSCluster
    ),
      assemblyShadeRules in assembly := Seq(
      ShadeRule.rename("com.google.common.**" -> "shade.@0").inAll
    ),
    assemblyMergeStrategy in assembly := {
      case PathList("META-INF", "MANIFEST.MF") => MergeStrategy.discard
      case _ => MergeStrategy.first
    },
    artifact in (Compile, assembly) := {
      val art = (artifact in (Compile, assembly)).value
      art.withClassifier(Some("assembly"))
    },
    addArtifact(artifact in (Compile, assembly), assembly),
    crossPaths := false, // Do not append Scala versions to the generated artifacts
    autoScalaLibrary := false, // This forbids including Scala related libraries into the dependency
    skip in publish := true
  )

lazy val shaded_hadoop = project
  .settings(
    name := "shaded-hadoop",
    packageBin in Compile := (assembly in (fatJar, Compile)).value
  )

Я не проверял это, но суть в этом.


Я хотел бы указать на еще одну проблему, которую я заметил: ваша стратегия слияния может вызвать у вас проблемы, поскольку вы хотите применить разные стратегии к некоторым файлам. см. стратегию по умолчанию здесь.
Я бы рекомендовал использовать что-то вроде этого сохранить исходную стратегию для всего, что не deduplicate

assemblyMergeStrategy in assembly := {
          entry: String => {
            val strategy = (assemblyMergeStrategy in assembly).value(entry)
            if (strategy == MergeStrategy.deduplicate) MergeStrategy.first
            else strategy
          }
      }
person lev    schedule 26.12.2017
comment
У меня не было времени построить толстую банку до сегодняшнего дня. Это был чрезвычайно полезный ответ, и он поставил меня на правильный путь. Единственное изменение, которое мне нужно было сделать, было в стратегии слияния для fatJar. Мне нужно было добавить case PathList("META-INF", "services", "org.apache.hadoop.fs.FileSystem") => MergeStrategy.filterDistinctLines, чтобы hdfs могла поддерживать все различные файловые системы. - person erip; 09.02.2018
comment
И этот шаблон взят из комментариев этот ответ. - person erip; 09.02.2018