Kotlin-multiplatform:ShadowJar gradle 插件创建空 jar

标签 kotlin gradle kotlin-multiplatform fatjar shadowjar

我尝试使用 ShadowJar gradle 插件将我的 ktor 应用程序打包到 fat jar 中。但由于 shadowJar 任务,我每次都得到几乎空 jar 。它仅包含 list (主类已正确设置)。

Gradle 配置(常规):

import org.gradle.jvm.tasks.Jar

buildscript {
    ext.kotlin_version = '1.3.72'
    ext.ktor_version = '1.3.2'
    ext.serialization_version = '0.20.0'
    ext.sl4j_version = '1.6.1'
    repositories { jcenter() }

    dependencies {
        classpath 'com.github.jengelman.gradle.plugins:shadow:5.2.0'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
    }
}

plugins {
    id 'org.jetbrains.kotlin.multiplatform' version '1.3.61'
}

apply plugin: 'kotlinx-serialization'
apply plugin: 'application'
apply plugin: 'java'

mainClassName = 'com.example.JvmMainKt'

apply plugin: 'com.github.johnrengelman.shadow'

repositories {
    mavenCentral()
    jcenter()
}
group 'com.example'
version '0.0.1'

apply plugin: 'maven-publish'

kotlin {

    jvm {
    }
    js {
        browser {
        }
        nodejs {
        }
    }
    // For ARM, should be changed to iosArm32 or iosArm64
    // For Linux, should be changed to e.g. linuxX64
    // For MacOS, should be changed to e.g. macosX64
    // For Windows, should be changed to e.g. mingwX64
    mingwX64("mingw") {
        binaries {
            executable {
                // Change to specify fully qualified name of your application's entry point:
                entryPoint = 'main'
                // Specify command-line arguments, if necessary:
                runTask?.args('')
            }
        }
    }
    sourceSets {
        commonMain {
            dependencies {
                implementation kotlin('stdlib-common')
                implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime-common:$serialization_version"
                implementation "io.ktor:ktor-client-core:$ktor_version"
            }
        }
        commonTest {
            dependencies {
                implementation kotlin('test-common')
                implementation kotlin('test-annotations-common')
            }
        }
        jvmMain {
            dependencies {
                implementation kotlin('stdlib-jdk8')
                implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:$serialization_version"
                implementation "io.ktor:ktor-serialization:$ktor_version"
                implementation "io.ktor:ktor-server-netty:$ktor_version"
                implementation "org.slf4j:slf4j-simple:$sl4j_version"

            }
        }
        jvmTest {
            dependencies {
                implementation kotlin('test')
                implementation kotlin('test-junit')
            }
        }
        jsMain {
            dependencies {
                implementation kotlin('stdlib-js')
                implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime-js:$serialization_version"
            }
        }
        jsTest {
            dependencies {
                implementation kotlin('test-js')
            }
        }
        mingwMain {
            dependencies {
                implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime-native:$serialization_version"
            }
        }
        mingwTest {
        }
    }
}

shadowJar {
    mainClassName = 'com.example.JvmMainKt'

    mergeServiceFiles()
}

最佳答案

marcu's answer是正确的,但他没有解释为什么这个片段解决了他的问题,并且他的代码片段不能直接转换为 build.gradle.kts因为需要 Actor 才能看到 runtimeDependencyFiles来自 Kotlin 。在 groovy 中,他的代码片段可以工作,因为 groovy 支持 duck typing而 Kotlin 则不然。

我在 Gradle Kotlin 中需要这个解决方案,所以我以这种方式分享。

com.github.johnrengelman.shadow gradle 插件旨在与常规 java 一起使用gradle 插件,构建单个 jar默认情况下,因此它会自动生成单个 fat-jar基于此jar类路径。

Kotlin-Multiplatform gradle 插件的工作方式不同,它创建 jar每个 jvm 的文件目标基于许多自定义设置,这些设置是使用 Kotlin-Multiplatform 独有的方法设置的。 ,这就是为什么默认 shadowJar任务无法在 Kotlin-Multiplatform 上开箱即用.

为了解决这个问题,我们必须创建一个新的 ShadowJar每个jvm的任务我们需要一个目标fat-jar手动,我们可以在声明目标之后(如 marcu 的示例所示)或在创建目标期间执行此操作。我将展示这两种方法,但我建议在创建过程中这样做以避免必须转换对象。

另一件事是,我创建了函数来应用所需的配置,因为我有 8 个 JVM 目标,所以这样我就不需要复制粘贴片段 8 次,我只调用该函数。

创建目标期间

代码注释中有解释:

// Add this imports on top of your build.gradle.kts file
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget

// Since you need a main class, I added this default constant
// which you can change as you need:
val defaultMainClassName: String? = null

// Here I declare the function that will register the new ShadowJar task for the target
// If you need another mainClassName, you can pass it as parameter
fun KotlinJvmTarget.registerShadowJar(mainClassName: String? = defaultMainClassName) {
    // We get the name from the target here to avoid conflicting 
    // with the name of the compilation unit
    val targetName = name
    // Access the main compilation
    // We only want to create ShadowJar 
    // for the main compilation of the target, not the test
    compilations.named("main") {
        // Access the tasks
        tasks {
            // Here we register our custom ShadowJar task, 
            // it's being prefixed by the target name
            val shadowJar = register<ShadowJar>("${targetName}ShadowJar") {
                // Allows our task to be grouped alongside with other build task
                // this is only for organization
                group = "build"
                // This is important, it adds all output of the build to the fat-jar
                from(output)
                // This tells ShadowJar to merge all jars in runtime environment
                // into the fat-jar for this target
                configurations = listOf(runtimeDependencyFiles)
                // Here we configure the name of the resulting fat-jar file
                // appendix makes sure we append the target name before the version
                archiveAppendix.set(targetName)
                // classifier is appended after the version, 
                // it's a common practice to set this classifier to fat-jars
                archiveClassifier.set("all")

                // Apply the main class name attribute
                if (mainClassName != null) {
                    manifest {
                        attributes("Main-Class" to mainClassName)
                    }
                }

                // This instruction tells the ShadowJar plugin to combine
                // ServiceLoader files together, this is needed because
                // a lot of Kotlin libs uses service files and
                // they would break without this instruction 
                mergeServiceFiles()
            }

            // Finally, we get the normal jar task for this target
            // and tells kotlin to execute our recently created ShadowJar task
            // after the normal jar task completes
            getByName("${targetName}Jar") {
                finalizedBy(shadowJar)
            }
        }
    }
}
kotlin {
    // Here we create a JVM target
    jvm("jvm8") {
        // We can configure anything we want
        compilations.all {
            kotlinOptions.jvmTarget = "1.8"
        }
        testRuns["test"].executionTask.configure {
            useJUnitPlatform()
        }
        // If we need a ShadowJar for it, 
        // all we have to do now is call
        // our custom function
        // Here's an example of what I'm saying:
        // https://stackoverflow.com/a/57951962/804976
        registerShadowJar()
    }
    // Another one just to illustrate the example
    jvm("jvm16") {
        compilations.all {
            kotlinOptions.jvmTarget = "16"
        }
        testRuns["test"].executionTask.configure {
            useJUnitPlatform()
        }
        registerShadowJar()
    }
}
删除评论
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget

val defaultMainClassName: String? = null

fun KotlinJvmTarget.registerShadowJar(mainClassName: String? = defaultMainClassName) {
    val targetName = name
    compilations.named("main") {
        tasks {
            val shadowJar = register<ShadowJar>("${targetName}ShadowJar") {
                group = "build"
                from(output)
                configurations = listOf(runtimeDependencyFiles)
                archiveAppendix.set(targetName)
                archiveClassifier.set("all")
                if (mainClassName != null) {
                    manifest {
                        attributes("Main-Class" to mainClassName)
                    }
                }
                mergeServiceFiles()
            }
            getByName("${targetName}Jar") {
                finalizedBy(shadowJar)
            }
        }
    }
}
kotlin {
    jvm("jvm8") {
        compilations.all {
            kotlinOptions.jvmTarget = "1.8"
        }
        testRuns["test"].executionTask.configure {
            useJUnitPlatform()
        }
        registerShadowJar()
    }
    jvm("jvm16") {
        compilations.all {
            kotlinOptions.jvmTarget = "16"
        }
        testRuns["test"].executionTask.configure {
            useJUnitPlatform()
        }
        registerShadowJar()
    }
}

创建目标后

现在,我将仅评论更改:

import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget

kotlin {
    jvm("jvm8") {
        compilations.all {
            kotlinOptions.jvmTarget = "1.8"
        }
        testRuns["test"].executionTask.configure {
            useJUnitPlatform()
        }
        // Not registering on creation this time,
        // we are going to register the task later
    }
    jvm("jvm16") {
        compilations.all {
            kotlinOptions.jvmTarget = "16"
        }
        testRuns["test"].executionTask.configure {
            useJUnitPlatform()
        }
    }
}


val defaultMainClassName: String? = null

// Instead of having KotlinJvmTarget as receiver,
// we are going to cast the target to it.
// We are also getting the target name from
// the function parameter
fun registerShadowJar(targetName: String, mainClassName: String? = defaultMainClassName) {
    // Get the target casting to KotlinJvmTarget in the process
    kotlin.targets.named<KotlinJvmTarget>(targetName) {
        // Access the main compilation
        compilations.named("main") {
            tasks {
                val shadowJar = register<ShadowJar>("${targetName}ShadowJar") {
                    group = "build"
                    from(output)
                    configurations = listOf(runtimeDependencyFiles)
                    archiveAppendix.set(targetName)
                    archiveClassifier.set("all")
                    if (mainClassName != null) {
                        manifest {
                        attributes("Main-Class" to mainClassName)
                        }
                    }
                    mergeServiceFiles()
                }
                getByName("${targetName}Jar") {
                    finalizedBy(shadowJar)
                }
            }
        }
    }
}

// Now we call the method for each JVM target that we need to create a ShadowJar
registerShadowJar("jvm8")
registerShadowJar("jvm16")
删除评论
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget

kotlin {
    jvm("jvm8") {
        compilations.all {
            kotlinOptions.jvmTarget = "1.8"
        }
        testRuns["test"].executionTask.configure {
            useJUnitPlatform()
        }
    }
    jvm("jvm16") {
        compilations.all {
            kotlinOptions.jvmTarget = "16"
        }
        testRuns["test"].executionTask.configure {
            useJUnitPlatform()
        }
    }
}

val defaultMainClassName: String? = null

fun registerShadowJar(targetName: String, mainClassName: String? = defaultMainClassName) {
    kotlin.targets.named<KotlinJvmTarget>(targetName) {
        compilations.named("main") {
            tasks {
                val shadowJar = register<ShadowJar>("${targetName}ShadowJar") {
                    group = "build"
                    from(output)
                    configurations = listOf(runtimeDependencyFiles)
                    archiveAppendix.set(targetName)
                    archiveClassifier.set("all")
                    if (mainClassName != null) {
                        manifest {
                        attributes("Main-Class" to mainClassName)
                        }
                    }
                    mergeServiceFiles()
                }
                getByName("${targetName}Jar") {
                    finalizedBy(shadowJar)
                }
            }
        }
    }
}
registerShadowJar("jvm8")
registerShadowJar("jvm16")

关于Kotlin-multiplatform:ShadowJar gradle 插件创建空 jar,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63426211/

相关文章:

android - 膨胀类 com.google.android.material.textfield.TextInputLayout 时出错

gradle - Windows上的gradle安装在哪里?

android - 扩展时更改 ModalDrawer 的大小,Jetpack Compose

kotlin-multiplatform - 如何为 iOS 制作多模块 kotlin 多平台库?

Kotlin 函数未解析引用

android - Firestore - 如何在 Kotlin 中排除数据类对象的字段

arrays - 返回通过添加数组中的奇数而创建的新数组

java - Gradle 是否应该从 mavenLocal() 为每个项目制作 JAR-s 的本地副本?

java - 通过插件 block 将 Gradle 插件应用于子项目和根项目

android - 在范围内找不到 "Greeting"