如何使用Gradle Kotlin脚本创建一个包含所有依赖的可执行JAR文件?

73

如题,我想知道如何修改 gradle.build.kts 以创建一个任务,该任务可用于在其中包含所有依赖项(包括 Kotlin 库)的唯一 jar

我在 Groovy 中找到了这个示例:

//create a single Jar with all dependencies
task fatJar(type: Jar) {
    manifest {
        attributes 'Implementation-Title': 'Gradle Jar File Example',
            'Implementation-Version': version,
            'Main-Class': 'com.mkyong.DateUtils'
    }
    baseName = project.name + '-all'
    from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
    with jar
}

但是我不知道除了以下代码外,应该如何使用 Kotlin 编写:

task("fatJar") {

}

这里有一个相关问题 - Mahozad
这个回答解决了你的问题吗?使用Gradle和Kotlin构建自执行jar文件 - Mahozad
4个回答

60

这里有一个不使用插件的版本,更像Groovy版本。

import org.gradle.jvm.tasks.Jar

val fatJar = task("fatJar", type = Jar::class) {
    baseName = "${project.name}-fat"
    manifest {
        attributes["Implementation-Title"] = "Gradle Jar File Example"
        attributes["Implementation-Version"] = version
        attributes["Main-Class"] = "com.mkyong.DateUtils"
    }
    from(configurations.runtime.map({ if (it.isDirectory) it else zipTree(it) }))
    with(tasks["jar"] as CopySpec)
}

tasks {
    "build" {
        dependsOn(fatJar)
    }
}

这里也有解释


一些评论者指出这种方法在新版本的Gradle中已不适用。 已测试可行的Gradle版本为5.4.1:

import org.gradle.jvm.tasks.Jar

val fatJar = task("fatJar", type = Jar::class) {
    baseName = "${project.name}-fat"
    manifest {
        attributes["Implementation-Title"] = "Gradle Jar File Example"
        attributes["Implementation-Version"] = version
        attributes["Main-Class"] = "com.mkyong.DateUtils"
    }
    from(configurations.runtimeClasspath.get().map({ if (it.isDirectory) it else zipTree(it) }))
    with(tasks.jar.get() as CopySpec)
}

tasks {
    "build" {
        dependsOn(fatJar)
    }
}
请注意configurations.runtimeClasspath.get()with(tasks.jar.get() as CopySpec)之间的差异。

6
他不是在开玩笑。这句话需要添加到 https://github.com/gradle/kotlin-dsl/tree/master/samples 的某个地方。 - Mitchell Tracy
24
请注意,在 Gradle 5 中,您需要将 configurations.runtime.map 替换为 configurations.runtime.get().map,以避免出现“未解决的引用:isDirectory”的情况。请参见此处的讨论(https://github.com/gradle/kotlin-dsl/issues/1082#issuecomment-433037363)。 - PHPirate
2
我还需要在主类中排除以下内容:exclude("META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA", "META-INF/INDEX.LIST"),以便在jar包中找到该类。 - mbonnin
5
fatJar任务中必须使用duplicatesStrategy = DuplicatesStrategy.EXCLUDE或者排除一些文件以避免失败 - 每个jar依赖项中包含的LICENSE.txt导致了一些错误。 - breandan
1
这个在 Gradle 6.8 上已经不再起作用了。 - Chris
显示剩余8条评论

29
以下是四种实现方法。请注意,前三种方法会修改Gradle的现有Jar任务。
方法1:将库文件放在与结果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要求我们使用相对URL来设置Class-Path属性。因此,我们不能使用Gradle依赖项的绝对路径(这也容易被更改并且不可用于其他系统)。如果您想使用绝对路径,也许this workaround可以起作用。
使用以下命令创建JAR:
./gradlew jar

默认情况下,结果JAR将在build/libs/目录中创建。

创建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文件的方法与之前的方法完全相同。

方法三:使用Shadow插件(创建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"] = "com.example.MyMainClass"
}

使用以下命令创建JAR文件:

./gradlew shadowJar

请参阅Shadow文档以获取有关配置插件的更多信息。

方法4:创建新任务(而不是修改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 文档以创建 uber (fat) JARs
有关清单的更多信息,请参见 Oracle Java 文档:使用清单文件
有关 tasks.create()tasks.register() 之间的区别,请参见 此帖子

请注意,您的 资源文件将自动包含在 JAR 文件中(假设它们放置在 /src/main/resources/ 目录或在构建文件中设置为资源根目录的任何自定义目录中)。要访问应用程序中的资源文件,请使用此代码(请注意名称开头的 /):

  • Kotlin
    val vegetables = MyClass::class.java.getResource("/vegetables.txt").readText()
    // 其他方式:
    // 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();
    // 或者 var stream = MyClass.class.getResourceAsStream("/vegetables.txt");
    
    var reader = new BufferedReader(new InputStreamReader(stream));
    var vegetables = reader.lines().collect(Collectors.joining("\n"));
    

如果想要一个瘦身的jar包和一个可选的胖(一些)的jar包,你会如何处理变体?我正在尝试使用PlantUML,它只有瘦身或胖的选项 - 但没有控制它的选项。 - soloturn
抱歉,我不知道这个。 - Mahozad
它像你发布的那样使用专用任务和方法4运行良好。唯一让我想知道的是如何签署fat jar,因为标准签署任务找不到它。 - soloturn
也许这个Gradle指南可以帮助你。 - Mahozad
在Gradle论坛上得到了答案:https://discuss.gradle.org/t/build-variant-command-line/42193/8。现在已经在PlantUML主Gradle脚本中,签名工作正常。 - soloturn
点赞 Shadow 插件,它与 Kotlin 无缝集成。 - kris larson

9
以下是使用 Gradle 6.5.1,Kotlin/Kotlin-Multiplatform 1.3.72 来完成,利用 build.gradle.kts 文件进行操作,无需额外插件(似乎在多平台环境下没有必要且可能存在问题)的方法:
注意:实际上,从我所知道的情况来看,很少有插件能够与此多平台插件良好地兼容,这就是为什么我认为其设计哲学本身就很冗长。 在我看来,它实际上相当优雅,但不够灵活或文档化,所以即使在没有额外插件的情况下,也需要大量的试错来设置。
希望对他人有所帮助。
kotlin {
    jvm {
        compilations {
            val main = getByName("main")
            tasks {
                register<Jar>("fatJar") {
                    group = "application"
                    manifest {
                        attributes["Implementation-Title"] = "Gradle Jar File Example"
                        attributes["Implementation-Version"] = archiveVersion
                        attributes["Main-Class"] = "[[mainClassPath]]"
                    }
                    archiveBaseName.set("${project.name}-fat")
                    from(main.output.classesDirs, main.compileDependencyFiles)
                    with(jar.get() as CopySpec)
                }
            }
        }
    }
}

执行任务“:fatJar”失败。
输入main / MainKt $ runGradleRunInProject $ removeReplFile $ 1.class是重复的,但没有设置重复处理策略。
- undefined

8
你可以使用ShadowJar插件来构建一个大的可执行jar包:
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar

buildscript {
    repositories {
        mavenCentral()
        gradleScriptKotlin()
    }
    dependencies {
        classpath(kotlinModule("gradle-plugin"))
        classpath("com.github.jengelman.gradle.plugins:shadow:1.2.3")
    }
}

apply {
    plugin("kotlin")
    plugin("com.github.johnrengelman.shadow")
}

repositories {
    mavenCentral()
}

val shadowJar: ShadowJar by tasks
shadowJar.apply {
    manifest.attributes.apply {
        put("Implementation-Title", "Gradle Jar File Example")
        put("Implementation-Version" version)
        put("Main-Class", "com.mkyong.DateUtils")
    }

    baseName = project.name + "-all"
}

只需使用'shadowJar'运行任务即可。

注意:这假定您正在使用GSK 0.7.0(截至2017年2月13日最新版本)。


第一行中的import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar无法解析ShadowJar - elect
你自从那时候试过我的答案了吗?我已经测试并且工作正常。我认为最初的问题是()需要一个扩展类。 - mbStavola
我刚刚又尝试了一下(http://imgur.com/qUozPd2),在“tasks”上显示“[DELEGATE_SPECIAL_FUNCTION_MISSING] Missing 'getValue(Build_gradle, KProperty<*>)' method on delegate of type 'TaskContainer!'”。 - elect
嗯,你肯定使用的是最新的 GSK 吗?编译器和插件也是最新的吗? - mbStavola
我看了一下,发现你使用的不是最新的 GSK 开发版本,而是最近的稳定版本。为了利用上述任务委派功能,您需要更新 gradle distributionUrl。一旦我克隆了您的 repo 并更新了它,一切都正常了。您可以在此处找到 GSK 的最新版本:https://github.com/gradle/gradle-script-kotlin/releases - mbStavola
显示剩余2条评论

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