从Kotlin多平台项目创建fat jar

7

我最近从旧的1.2多平台切换到了1.3。不同之处在于,每个多平台模块都有一个build.gradle文件(我有5个),所以配置要少得多。 然而,我似乎无法配置创建可运行的fat jar并包含所有来自jvm平台的依赖项。 我过去使用标准的“application”插件和jar任务,但现在不起作用了。我发现有“jvmJar”任务,我修改了它(设置了主类),但创建的jar文件不包含依赖关系,并且在ClassNotFoundException时崩溃了。我该怎么办?

这是我现在拥有的:

    jvm() {
        jvmJar {
            manifest {
                attributes 'Main-Class': 'eu.xx.Runner'
            }
            from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
        }

    }

也许ShadowJar能帮到你? - Greg Kopff
是的,可能吧。我还不确定如何处理它。 - K.H.
1
每个多平台模块只有一个build.gradle文件。你有相关的来源吗?文档非常糟糕,我想你也会同意,我甚至从未听说过这种可能性。 - gunslingor
4个回答

3

我撞上了这个障碍并使用了这个解决方法。

1. 重新组织你的工程

假设你的工程名为Project

创建一个名为subA的子模块,并在其 build.gradle 中添加 gradle 表示法 Project:subA

现在,subA 子模块中包含了多平台代码(它是应用了 :kotlin-multiplafrom 的 gradle 工程)。

2. 添加另一个子模块

创建一个仅针对 jvm 平台的子模块,名为 subB,并在其 build.gradle 中添加 gradle 表示法 Project:subB

所以,subB 将具有 plugins: 'application''org.jetbrains.kotlin.jvm'

3. 将你的模块作为 gradle 依赖项添加(参见我的 build.gradle 文件)

plugins {
    id 'org.jetbrains.kotlin.jvm' version '1.3.31'
    id "application"
}

apply plugin: "kotlinx-serialization"

group 'tz.or.self'
version '0.0.0'

mainClassName = "com.example.MainKt"

sourceCompatibility = 1.8

compileKotlin {
    kotlinOptions.jvmTarget = "1.8"
}

dependencies {
    implementation project(':subA')
}

您可以像普通的Java项目一样构建并使用现有的插件来构建subB,这是可行的。


为什么有两个项目?它们为什么必须是子项目?不过我喜欢这个总体的想法。我尝试创建另一个与project同级别的模块,使用kotlin/jvm/jar设置并依赖于project,它完美地工作了。 - K.H.
如果它能够完美地工作,请花些时间接受答案。 - andylamax
目前,kotlin-multiplatform插件与应用程序插件配合良好,但是如果您开始针对Android进行开发,则可能会出现插件冲突(如果在mpp中使用com.android.library或com.android.application)。我只能将其作为解决方法。此外,我尝试将应用程序插件配置为与kotlin-multiplatform目标兼容,但没有成功。 - andylamax
不想接受你的答案,因为这不是我最终得出的结果。正如我在评论中提到的那样,我的方法完全可行,基于你提出的将项目分开的想法,但并不完全相同。 - K.H.

2

使用Kotlin 1.3.61的多平台插件可以使其正常工作:

以下内容适用于位于src/jvmMain/kotlin/com/example/Hello.kt中的主文件:

Hello.kt还必须将其包指定为package com.example

我按照以下方式配置了我的JVM目标:

kotlin {
    targets {
        jvm()

        configure([jvm])  {
            withJava()
            jvmJar {
                manifest {
                    attributes 'Main-Class': 'com.example.HelloKt'
                }
                from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
            }
        }
    }
}

runtimeClasspath 在我的多平台(应用程序)项目中没有起作用,因为它是空的。所以我找到并使用了 jvmRuntimeClasspath,它完美地工作了。 - Andreas

2
唯一让gradle/multiplatform工作的方法似乎是无尽的尝试和错误;这是一场噩梦,它不仅仅是一个“构建”系统,更像是一个“构建系统”;换句话说,这两个工具(一起或独立使用)仅是实现插件制作者打算的单个软件开发生命周期的手段,然而,如果您已经设计了所需的软件生命周期和CI/CD系统,现在正在尝试实现该工程,则使用这些工具要比使用脚本、代码或maven难得多。这其中有许多原因:
  • 由于插件制作者只公开最低限度的可配置性,因此编码约定发生了巨大变化。
  • 非常差的文档更新;Kotlin、gradle和插件变化如此之快,以至于我已经开始严重质疑这些工具的实用性。
因此,在编写本文时,似乎是使用kotlin 1.3.72、multiplatform 1.3.72、ktor 1.3.2和gradle 6.2.2(使用kts格式)时使用的正确语法。
请注意,fatJar似乎已经正确组装,但无法运行,找不到类,因此我包括了我一直在使用的第二个runLocally任务。
这不是一个完整的解决方案,所以我不喜欢在这里发布它,但从我所知道的来看...这是我能找到的最完整和最新的解决方案。
//Import variables from gradle.properties
val environment: String by project
val kotlinVersion: String by project
val ktorVersion: String by project
val kotlinExposedVersion: String by project
val mySqlConnectorVersion: String by project
val logbackVersion: String by project
val romeToolsVersion: String by project
val klaxonVersion: String by project
val kotlinLoggingVersion: String by project
val skrapeItVersion: String by project
val jsoupVersion: String by project
val devWebApiServer: String by project
val devWebApiServerVersion: String by project

//Build File Configuration
plugins {
    java
    kotlin("multiplatform") version "1.3.72"
}

group = "com.app"
version = "1.0-SNAPSHOT"

repositories {
    mavenCentral()
    jcenter()
    jcenter {
        url = uri("https://kotlin.bintray.com/kotlin-js-wrappers")
    }
    maven {
        url = uri("https://jitpack.io")
    }
}

//Multiplatform Configuration
kotlin {
    jvm {
        compilations {
            val main = getByName("main")
            tasks {
                register<Jar>("buildFatJar") {
                    group = "application"
                    manifest {
                        attributes["Implementation-Title"] = "Gradle Jar File Example"
                        attributes["Implementation-Version"] = archiveVersion
                        attributes["Main-Class"] = "com.app.BackendAppKt"
                    }
                    archiveBaseName.set("${project.name}-fat")
                    from(main.output.classesDirs, main.compileDependencyFiles)
                    with(jar.get() as CopySpec)
                }
                register<JavaExec>("runLocally") {
                    group = "application"
                    setMain("com.app.BackendAppKt")
                    classpath = main.output.classesDirs
                    classpath += main.compileDependencyFiles
                }
            }
        }
    }
    js {
        browser { EXCLUDED FOR LENGTH }
    }
    sourceSets { EXCLUDED FOR LENGTH }
}

哇,真不敢相信你昨天刚回答了这个问题。似乎唯一让Gradle /多平台工作的方法是无休止的尝试和错误。完全同意!请注意,fatJar似乎组装正确,但无法运行,找不到该类。我使用“jar tvf <project.name>-fat-<version>.jar”检查了jar的内容,我注意到存档中有一堆“.jar”文件。 我不是专家,自从我上次使用fat jars以来已经有一段时间了,但我猜fat jars只应该有“.class”文件。我会深入挖掘,但也许这是你已经知道的东西? - Yogesh Nachnani
所以文档表明允许在jar文件中添加其他jar文件。我尝试通过添加attributes["Class-Path"] = main.compileDependencyFiles.map { it.name }.joinToString(separator = " ")来添加Class-Path,但这也不起作用。检查MANIFEST.MF后,我发现唯一的奇怪之处是在第72列添加了换行符(很奇怪!)。基本上我正在尝试使java -jar <fat-jar>工作。 - Yogesh Nachnani
我目前使用的解决方法是,使用您的代码创建的jar文件,使用jar xvf <fat-jar.jar>解压缩它,然后使用java -cp "./*:." com.app.BackendAppKt运行它。所以问题可能出在manifest.mf文件生成的方式上。不用说,我快要抓狂了。 - Yogesh Nachnani
实际上在另一个类似的问题上已经完全解决了。我认为这里我错过了Java,这就是为什么它没有正确地运行构建、几乎构建正确的原因。这里有更多信息:https://dev59.com/QLroa4cB1Zd3GeqPjWb4#62770101 - gunslingor

1

我使用了稍微修改过的luca992版本,终于让它运行起来了:

kotlin {
jvm() {
    withJava()
    jvmJar {
        manifest {
            attributes 'Main-Class': 'sample.MainKt'
        }
        from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
    }
}
...
}

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