Статьи

jar-with-deps не любит META-INF / услуги

Недавно я готовил средство проверки соединения для мощной среды удаленного выполнения Deployit Overthere . Чтобы сделать чекер настолько компактным, насколько это возможно, я собрал флягу 1 для раздачи.
Тесты и пробные запуски из IDE работали, поэтому я ожидал, что пробный запуск дистрибутива будет быстрой формальностью. Вместо этого: бум!
Оказывается, что одна из библиотек, используемых Overthere, TrueZIP — или вообще любой код, который использует механизм SPI Java 2 — не подходит для идеи jar-with-deps .

Видя двойной

TrueZIP использует файл конфигурации провайдера de.schlichtherle.truezip.fs.spi.FsDriverService в META-INF / services для регистрации драйверов для различных форматов архивов, и проверка завершалась сбоем, поскольку один из необходимых драйверов не загружался, хотя код был правильно включен в jar-with-deps .

Опция дублирования задачи 3 Jar Ant и выпуска 1050 Gradle , в которой говорится о слиянии файлов при создании архива, намекает на проблему: после упаковки в один архив jar-with-deps только один из файлов конфигурации провайдера TrueZIP был быть найденным

Maven, Gradle и Java

После того, как я рассмотрел jar-with-deps, созданный Maven 4 , стало ясно, почему: JAR содержит только один из файлов конфигурации — выглядит как первый, встречающийся в моем случае, но это может быть недетерминированным. Предположительно, это происходит потому, что Maven использует временный каталог для подготовки содержимого архива, который, конечно, не может содержать несколько файлов с одинаковыми именами.

С Gradle 5 все становится немного интереснее, потому что архив действительно содержит все файлы конфигурации — формат ZIP поддерживает повторяющиеся записи 6 . Однако ClassLoader.getResources не подыгрывает, поэтому снова обнаруживается только одна запись.

Может быть только один

По сути, кажется, что объединение затронутых файлов — единственный реальный способ обойти это. Вот возможный фрагмент Gradle 7 :

task jarWithDeps(type: Jar, dependsOn: classes) {
    mergeDir = "${buildDir}/merge"
    // might need a lazy var in a multi-module project where deps are inherited
    runtimeDeps = configurations.runtime.collect { zipTree(it) }
 
    doFirst {
        new File(mergeDir).delete()
        mergeFiles(mergeDir, runtimeDeps, file-to-merge)
        // ... possibly other files too
    }
 
    // this project's classes and all deps
    from sourceSets*.classesDir
    from(runtimeDeps) {
        exclude file-to-merge
    }
    from mergeDir
}
 
private def mergeFiles(targetDir, fileTrees, relativePath) {
  // prepare the merge
  mergedFile = new File("${targetDir}/${relativePath}")
  new File(mergedFile.parent).mkdirs()
 
  fileTrees*.matching({ include "**/${relativePath}" })*.each {
    mergedFile << it.bytes
  }
}

К сожалению, я не смог найти подобную опцию для архиватора Maven , но я думаю, что в экосистеме Maven должно быть что-то там;-)

Сноски

  1. Ака «FarJar». Эквивалентное определение задачи Gradle см. Здесь .
  2. Посмотрите MultiSPI для подхода, который обеспечивает большую гибкость и «современные альтернативы» для поставщиков услуг.
  3. Обратите внимание , что баночка утилита сама не обеспечивает такой же вариант.
  4. запустить mvn clean package
  5. запусти gradle clean jarWithDeps
  6. хотя ZipOutputStream явно не был ими доволен и, судя по реализации 1.6.0_20, все равно будет выдавать исключение ZipException
  7. Было бы интересно попытаться выполнить слияние во время создания JAR, возможно, сохранив «merge-so-far (s)» в mergeDir и используя eachFile для замены неопрятных файлов и обновив «merge-so-far (s)» » в то же время.

 

С http://blog.xebia.com/2011/07/jar-with-deps-dont-like-meta-infservices/