使用 Gradle 和 Kotlin 构建一个自执行的 jar

Posted

技术标签:

【中文标题】使用 Gradle 和 Kotlin 构建一个自执行的 jar【英文标题】:Building a self-executable JAR with Gradle and Kotlin 【发布时间】:2014-12-15 15:42:49 【问题描述】:

为了入门,我编写了一个简单的 kotlin 源文件,以及一个 gradle 脚本文件。 但我不知道如何将 main func 添加到清单中,以便 jar 可以自执行。

这是我的 build.gradle 脚本:

buildscript 
    repositories 
        mavenCentral()
    
    dependencies 
        classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:0.9.66'
    

apply plugin: "kotlin"
repositories 
    mavenCentral()

dependencies 
    compile 'org.jetbrains.kotlin:kotlin-stdlib:0.9.66'


jar 
    manifest 
        attributes 'Main-Class': 'com.loloof64.kotlin.exps.ExpsPackage'
    

这是我的 com.loloof64.kotlin.exps.Multideclarations.kt

package com.loloof64.kotlin.exps

class Person(val firstName: String, val lastName: String) 
    fun component1(): String 
        return firstName
    
    fun component2(): String 
        return lastName
    


fun main(args: Array < String > ) 
    val(first, last) = Person("Laurent", "Bernabé")
    println("First name : $first - Last name : $last")

当我从终端(java -jar MYJar.jar)启动 jar 时,我得到以下堆栈跟踪,说我缺少 kotlin 反射库类,实际上它们还没有添加到 jar 中。看来我缺少最终 jar 中的 kotlin-compiler 工件类以及 kotlin-stdlib 源,但我不知道如何调整 gradle 构建。

$> java -jar build/libs/kotlin_exps.jar 

Exception in thread "main" java.lang.NoClassDefFoundError: kotlin/reflect/jvm/internal/InternalPackage
    at com.loloof64.kotlin.exps.ExpsPackage.<clinit>(MultiDeclarations.kt)
Caused by: java.lang.ClassNotFoundException: kotlin.reflect.jvm.internal.InternalPackage
    at java.net.URLClassLoader$1.run(URLClassLoader.java:372)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:361)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:360)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    ... 1 more

我正在使用 Kotlin 0.9.66 和 gradle 2.1

【问题讨论】:

请注意,Kotlin 为诸如main 之类的***函数生成的类名是在同一个文件中定义的包,加上附加了KT 的文件名。在您的情况下,这将是com.loloof64.kotlin.exps.MultideclarationsKT。您可以通过在文件顶部添加 @file:JvmName("OtherName") 来更改此行为,以便为文件中的所有***函数定义新的类名。 您附加两个字符 Kt 而不是 KT... 即大写 K 和小写 t。这是一个容易犯的错误,但会导致 jar 执行失败。由于打字不完善,我自己有时也会遇到这个问题。 见this rather comprehensive answer。 【参考方案1】:

添加插件application,然后设置mainClassName

mainClassName = '[your_namespace].[your_arctifact]Kt'

例如,假设您已将以下代码放在名为main.kt 的文件中:

package net.mydomain.kotlinlearn

import kotlin
import java.util.ArrayList

fun main(args: Array<String>) 

    println("Hello!")


您的build.gradle 应该是:

apply plugin: 'kotlin'
apply plugin: 'application'

mainClassName = "net.mydomain.kotlinlearn.MainKt"

事实上,Kotlin 正在构建一个类来封装你的主函数,该函数与你的文件同名 - 使用标题大小写。

【讨论】:

您能否更新您对 Kotlin 如何为***函数创建类的新命名约定的答案。在他的示例中,现在将是:com.loloof64.kotlin.exps.MultideclarationsKT 作为类名。 Kotlin 1.0 beta 及更高版本也是如此。 针对 Kotlin 1.0 Beta 和更新版本进行了更新。谢谢杰森。 这不应该是公认的答案;这是正确的,但不是全貌:需要“收集” Kotlin 运行时文件以制作完整的可执行 jar,如下面@loloof64 的示例所示。 这不再起作用了。如果你修复它,我会删除我的反对票。 @AdamArold 我用 1.1.51 进行了测试,它可以工作。你指的是哪个版本?谢谢【参考方案2】:

我找到了解决方法(感谢MkYong website)

    gradle 脚本需要 kotlin-compiler 工件作为依赖项 gradle 脚本需要一种方法来收集所有 kotlin 文件并将它们放入 jar 中。

所以我得到了以下 gradle 脚本:

buildscript 
    repositories 
        mavenCentral()
    
    dependencies 
       classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.0.1-2'
    


apply plugin: "kotlin"

repositories 
    mavenCentral()


dependencies 
    compile 'org.jetbrains.kotlin:kotlin-stdlib:1.0.1-2'


jar 
    manifest 
        attributes 'Main-Class': 'com.loloof64.kotlin.exps.MultideclarationsKT'
    

    // NEW LINE HERE !!!
    from  configurations.compile.collect  it.isDirectory() ? it : zipTree(it)  

【讨论】:

对我来说它在没有编译器依赖的情况下工作,收集 jar 的文件就足够了 可以确认不需要编译器依赖,收集文件就够了。 执行任务后的jar在哪里? jar 文件的位置取决于构建 jar 的方法。如果您执行 gradle jar 任务,它将被放入目录build/libs。构建目录与build.gradle 文件处于同一级别。如果您在 IntelliJ IDE 中将 jar 映射构建为工件并使用 IntelliJ 构建工件,则将其放置在 IntelliJ 项目级别的 out/artifacts 目录中 具体来说,我发现将这个jar_block添加到build.gradle文件后,我可以使用IntelliJ神器机制或者使用gradle任务机制构建jar文件来创建一个成功的jar。跨度> 【参考方案3】:

在构建 Artifact 时,上述解决方案都不适合我。

IDE 版本IntelliJ IDEA 2019.1.1

要解决此问题,请执行以下操作

步骤

第 1 步 - 创建工件

    转到文件 -> 项目结构 -> 工件

    点击+ -> JAR -> From modules with dependencies

    选择您的程序的Main Class

第 2 步 - 更改清单路径

    Directory for META-INF/MANIFEST.MF 的值更改为您的项目根目录。

    例如,从/your/project/directory/src/main/kotlin/your/project/directory

    OK,然后按ApplyOK

第 3 步 - 构建工件

    最后,转到 Build -> Build Artifacts -> [your-artifact-name] -> Build

生成的JAR文件可以在out/artifact/[your-artifact-name]目录下找到。 (y)

【讨论】:

非常感谢!!!经过几次尝试,这是唯一对我有用的东西!【参考方案4】:

谢谢,对我来说,添加 jar 部分很有效

jar 
    manifest 
        attributes 'Main-Class': 'com.photofiles.Application'
    

    from  configurations.compile.collect  it.isDirectory() ? it : zipTree(it)  

不需要应用程序插件。

【讨论】:

主类名需要以Kt结尾。所以上面应该是ApplicationKt【参考方案5】:

如果使用GradleKotlin DSL,那么我的duplicate 问题的答案是:

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar

plugins 
    kotlin("jvm") version "1.2.51"
    id("com.github.johnrengelman.shadow") version "2.0.4"


group = "xxx.yyy"
version = "1.0-SNAPSHOT"

repositories 
    mavenCentral()


dependencies 
    implementation(kotlin("stdlib-jdk8"))


tasks.withType<KotlinCompile> 
    kotlinOptions.jvmTarget = "1.8"


tasks.withType<ShadowJar> 

    manifest.attributes.apply 
        put("Implementation-Title", "Gradle Jar File Example")
        //put("Implementation-Version" version)
        put("Main-Class", "HelloKotlinWorld.App")
    

我认为这是最简单的解决方案。哦,也许您只使用Kotlin 本身而不是DSL

【讨论】:

【参考方案6】:

如果你碰巧像我一样愚蠢:

不要将 IntelliJ 运行配置创建为应用程序。 IntelliJ 将假定它是 Java 并且它永远不会工作。相反,请使用“Kotlin”条目。

【讨论】:

【参考方案7】:

对于任何使用 Kotlin MP 插件的人,这里是您的代码

jvm
    jvmJar 
        manifest
            attributes 'Main-Class':'Class path here'
        
    


【讨论】:

以上是关于使用 Gradle 和 Kotlin 构建一个自执行的 jar的主要内容,如果未能解决你的问题,请参考以下文章

用于 kotlin 代码的 gradle 构建

如何解析和修改build.gradle.kts Kotlin Gradle构建脚本?

如何在Kotlin中使用Gradle构建缓存?

为啥 kotlin gradle 插件无法使用 1.8 目标构建?

Gradle Kotlin DSL:在独特的地方定义 Kotlin 版本

结合Kotlin使用Gradle build cache