【问题标题】:How do I create an executable fat JAR with Gradle with implementation dependencies?如何使用带有实现依赖项的 Gradle 创建可执行的胖 JAR?
【发布时间】:2022-01-05 14:34:24
【问题描述】:

我在 Gradle 4.6 中有一个简单的项目,并且想制作一个可执行的 JAR。我试过shadowgradle-fatjar-plugingradle-one-jarspring-boot-gradle-plugin 插件,但它们都没有添加我声明为implementation 的依赖项(我没有任何compile 的)。它适用于compile,例如对于gradle-one-jar 插件,但我想拥有implementation 依赖项。

【问题讨论】:

标签: java gradle executable-jar gradle-plugin fatjar


【解决方案1】:

您可以使用以下代码。

jar {
    manifest {
        attributes(
                'Main-Class': 'com.package.YourClass'
        )
    }
    from {
        configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
    }
 }

确保将com.package.YourClass 替换为包含static void main( String args[] ) 的完全限定类名。

这将打包运行时依赖项。如果您需要更多信息,请查看docs

【讨论】:

  • fat jar 的名称是什么,我在哪个目录中找到它?
  • @VijayKumar 矿山位于build/libs 之下。
  • 您可能想使用configurations.compileClasspath.filter{ it.exists() }.collect { it.isDirectory() ? it : zipTree(it) }。这样,如果您有不存在的类路径条目,它就不会挂断。我正在使用 Kotlin,所以 java 源类路径条目没有任何内容,而且它变得非常暴躁。
  • 当我使用这段代码时,所有的库类都包含在uberjar中,但源目录中的类没有。
  • 伙计,你为我节省了很多时间。网上有一个howto,但是有问题:)
【解决方案2】:

根据接受的答案,我需要再添加一行代码:

task fatJar(type: Jar) {
  manifest {
    attributes 'Main-Class': 'com.yourpackage.Main'
  }
  archiveClassifier = "all"
  from {
    configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
    configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
    }
  with jar
}

没有这一行,它省略了我的源文件,只添加了依赖项:

configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }

对于较新的 gradle (7+),您可能会看到此错误:

Execution failed for task ':fatJar'.
> Entry [some entry here] is a duplicate but no duplicate handling strategy has been set. Please 
refer to https://docs.gradle.org/7.1/dsl/org.gradle.api.tasks.Copy.html#org.gradle.api.tasks.Copy:duplicatesStrategy
 for details.

如果发生这种情况,请将 duplicatesStrategy 添加到 fatJar 任务中,例如 duplicatesStrategy "exclude"

同样,对于 Gradle 7+,您只需删除 configuration.compile.collect 行,因为它在此版本的 gradle 中不再是有效配置。

【讨论】:

  • 这在 Gradle 6.0.1 中所有其他人都没有的地方有效。奇怪的是,Gradle 4.x 中似乎不需要第二行,但是一旦升级到 Gradle 6 或 5,我的旧构建配置突然停止创建胖 JAR。
  • Gradle 6.5 报告以下警告:编译配置已被弃用以解决问题。这将在 Gradle 7.0 中失败并出现错误。请改为解析 compileClasspath 配置。用“compileClasspath”替换“compile”修复了它。
  • 工作得很好,不知道你是怎么找到这个的,但你在做上帝的工作!!
  • @jbel 谢谢!我通过结合另一个帖子的信息偶然发现它
  • 对我来说有点奇怪,尝试运行一个 github 项目(已使用 gradle 包装器设置)并不断收到依赖项的“classdefnotfoundexception”错误。我检查了子模块中的 gradle.build 文件,发现依赖项是使用“实现”关键字(而不是“编译”)导入的。一旦我为 jar 任务添加了“configuration.runtimeClasspath”行,问题就解决了。
【解决方案3】:

同样的任务可以使用Gradle Kotlin DSL以类似的方式完成:

val jar by tasks.getting(Jar::class) {
    manifest {
        attributes["Main-Class"] = "com.package.YourClass"
    }

    from(configurations
        .runtime
        // .get() // uncomment this on Gradle 6+
        // .files
        .map { if (it.isDirectory) it else zipTree(it) })
}

【讨论】:

    【解决方案4】:

    from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }

    这条线对我来说很重要。

    【讨论】:

      【解决方案5】:

      Kotlin 1.3.72 & JVM 插件,Gradle 6.5.1

      所有这些平台的语法都在迅速变化

      tasks {
          compileKotlin {
              kotlinOptions.jvmTarget = "1.8"
          }
          compileTestKotlin {
              kotlinOptions.jvmTarget = "1.8"
          }
          val main = sourceSets.main.get()
          //TODO
          register<Jar>("buildFatJar") {
              group = "app-backend"
              dependsOn(build)
              // shouldRunAfter(parent!!.tasks["prepCopyJsBundleToKtor"]) -> This is for incorporating KotlinJS gradle subproject resulting js file.
              manifest {
                  attributes["Main-Class"] = "com.app.app.BackendAppKt"
              }
      
              from(configurations.compileClasspath.get().files.map { if (it.isDirectory) it else zipTree(it) })
              with(jar.get() as CopySpec)
              archiveBaseName.set("${project.name}-fat")
          }
      }
      

      【讨论】:

        【解决方案6】:

        现在以前的答案有点过时了,请参阅此处了解使用 gradle-7.4 的内容: How to create a fat JAR with Gradle Kotlin script?

        tasks.jar {
            manifest.attributes["Main-Class"] = "com.example.MyMainClass"
            val dependencies = configurations
                .runtimeClasspath
                .get()
                .map(::zipTree) // OR .map { zipTree(it) }
            from(dependencies)
            duplicatesStrategy = DuplicatesStrategy.EXCLUDE
        }
        

        【讨论】:

          【解决方案7】:

          这里我提供 Kotlin DSL (build.gradle.kts) 的解决方案。
          注意前3个方法修改了Gradle现有的Jar任务。

          方法一:将库文件放在结果JAR旁边

          此方法不需要application或任何其他插件。

          tasks.jar {
              manifest.attributes["Main-Class"] = "com.example.MyMainClass"
              manifest.attributes["Class-Path"] = configurations
                  .runtimeClasspath
                  .get()
                  .joinToString(separator = " ") { file ->
                      "libs/${file.name}"
                  }
          }
          

          请注意,Java 要求我们为 Class-Path 属性使用相对 URL。因此,我们不能使用 Gradle 依赖项的绝对路径(这也容易被更改并且在其他系统上不可用)。如果你想使用绝对路径,也许this workaround 可以。

          使用以下命令创建 JAR:

          ./gradlew jar
          

          默认会在build/libs/目录中创建结果JAR。

          创建 JAR 后,将库 JAR 复制到放置结果 JAR 的 libs/ 子目录中。确保您的库 JAR 文件的文件名中不包含空格(其文件名应与上述任务中${file.name} 变量指定的文件名匹配)。

          方法 2:在结果 JAR 文件中嵌入库(fat 或 uber JAR)

          此方法也不需要任何 Gradle 插件。

          tasks.jar {
              manifest.attributes["Main-Class"] = "com.example.MyMainClass"
              val dependencies = configurations
                  .runtimeClasspath
                  .get()
                  .map(::zipTree) // OR .map { zipTree(it) }
              from(dependencies)
              duplicatesStrategy = DuplicatesStrategy.EXCLUDE
          }
          

          创建 JAR 与之前的方法完全相同。

          方法 3:使用Shadow plugin(创建 fat 或 uber JAR)

          plugins {
              id("com.github.johnrengelman.shadow") version "6.0.0"
          }
          // Shadow task depends on Jar task, so these configs are reflected for Shadow as well
          tasks.jar {
              manifest.attributes["Main-Class"] = "org.example.MainKt"
          }
          

          使用以下命令创建 JAR:

          ./gradlew shadowJar
          

          有关配置插件的更多信息,请参阅Shadow documentations

          方法四:新建任务(而不是修改Jar任务)

          tasks.create("MyFatJar", Jar::class) {
              group = "my tasks" // OR, for example, "build"
              description = "Creates a self-contained fat JAR of the application that can be run."
              manifest.attributes["Main-Class"] = "com.example.MyMainClass"
              duplicatesStrategy = DuplicatesStrategy.EXCLUDE
              val dependencies = configurations
                  .runtimeClasspath
                  .get()
                  .map(::zipTree)
              from(dependencies)
              with(tasks.jar.get())
          }
          

          运行创建的 JAR

          java -jar my-artifact.jar
          

          以上解决方案经过以下测试:

          • Java 17
          • Gradle 7.1(将 Kotlin 1.4.31 用于 .kts 构建脚本)

          见官方Gradle documentation for creating uber (fat) JARs

          有关清单的更多信息,请参阅Oracle Java Documentation: Working with Manifest files

          有关tasks.create()tasks.register() 之间的区别,请参阅this post

          注意您的resource files will be included in the JAR file automatically(假设它们被放置在/src/main/resources/ 目录或构建文件中设置为资源根目录的任何自定义目录中)。要访问应用程序中的资源文件,请使用以下代码(注意名称开头的 /):

          • 科特林
            val vegetables = MyClass::class.java.getResource("/vegetables.txt").readText()
            // Alternative ways:
            // val vegetables = object{}.javaClass.getResource("/vegetables.txt").readText()
            // val vegetables = MyClass::class.java.getResourceAsStream("/vegetables.txt").reader().readText()
            // val vegetables = object{}.javaClass.getResourceAsStream("/vegetables.txt").reader().readText()
            
          • Java
            var stream = MyClass.class.getResource("/vegetables.txt").openStream();
            // OR var stream = MyClass.class.getResourceAsStream("/vegetables.txt");
            
            var reader = new BufferedReader(new InputStreamReader(stream));
            var vegetables = reader.lines().collect(Collectors.joining("\n"));
            

          【讨论】:

            猜你喜欢
            • 2018-08-23
            • 1970-01-01
            • 1970-01-01
            • 2021-09-04
            相关资源
            最近更新 更多