繁体   English   中英

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

[英]Kotlin-multiplatform: ShadowJar gradle plugin creates empty jar

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

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 的回答是正确的,但他没有解释为什么剪断解决了他的问题,并且他的代码片段不能直接转换为build.gradle.kts因为需要强制转换才能看到来自 Kotlin 的runtimeDependencyFiles 在 groovy 中,他的代码片段有效,因为 groovy 支持鸭子类型,而 Kotlin 不支持。

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

The com.github.johnrengelman.shadow gradle plugin is designed to work with regular the java gradle plugin, which builds a single jar by default, so it automatically generates a single fat-jar based on that jar classpath.

Kotlin-Multiplatform gradle 插件的工作方式不同,它为每个jvm目标创建jar文件,基于许多自定义设置,这些设置是使用 Kot shadow-JMultiplatform 独有的方法设置的,为什么不能解决 Kot shadow Kotlin-Multiplatform shadowJar独有的方法? Kotlin-Multiplatform上的盒子。

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

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

在创建目标期间

解释在代码上注释:

// 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")

我过去遇到过同样的问题,对我来说有效的语法(对于 groovy gradle )是:

shadowJar {
    mergeServiceFiles()
    manifest {
        attributes 'Main-Class': 'com.example.JvmMainKt'
    }
}

我终于找到了解决我的问题的方法。

import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar

//...

task shadowJar2(type: ShadowJar) {
    def target = kotlin.targets.jvm
    from target.compilations.main.output
    def runtimeClasspath = target.compilations.main.runtimeDependencyFiles
    manifest {
        attributes 'Main-Class': mainClassName
    }
    configurations = [runtimeClasspath]
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM