Kotlin-多平台:ShadowJar gradle插件创建空jar

t5zmwmid  于 2023-10-19  发布在  Kotlin
关注(0)|答案(4)|浏览(226)

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

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()
}
nkcskrwz

nkcskrwz1#

marcu's answer是正确的,但他没有解释为什么剪切解决了他的问题,他的代码片段不能直接转换为build.gradle.kts,因为需要从Kotlin看到runtimeDependencyFiles。在groovy中,他的代码片段可以工作,因为groovy支持鸭子类型,而Kotlin不支持。
我在GradleKotlin中需要这个解决方案,所以我以这种方式分享它。
com.github.johnrengelman.shadow gradle插件旨在与常规的java gradle插件一起使用,该插件默认构建单个jar,因此它会基于jar类路径自动生成单个fat-jar
Kotlin-Multiplatform gradle插件的工作方式不同,它基于许多自定义设置为每个jvm目标创建jar文件,这些自定义设置使用Kotlin-Multiplatform特有的方法进行设置,这就是为什么默认的shadowJar任务在Kotlin-Multiplatform上无法开箱即用。
为了解决这个问题,我们必须为每个需要fat-jarjvm目标手动创建一个新的ShadowJar任务,我们可以在声明目标之后(如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")
6jygbczu

6jygbczu2#

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

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

hof1towb3#

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

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]
}
zour9fqk

zour9fqk4#

我也遇到了同样的问题,上面的解决方案对我不起作用。
这是我的解决办法

import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import groovy.util.NodeList
import kotlinx.atomicfu.plugin.gradle.sourceSets
import org.gradle.api.Project
import org.gradle.api.artifacts.ResolvedDependency
import org.gradle.api.publish.PublishingExtension
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.api.publish.maven.tasks.PublishToMavenLocal
import org.gradle.api.specs.Spec
import org.gradle.api.tasks.TaskContainer
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.bundling.Jar
import org.gradle.configurationcache.extensions.capitalized
import org.gradle.internal.impldep.com.amazonaws.util.XpathUtils.asNode
import org.gradle.kotlin.dsl.assign
import org.gradle.kotlin.dsl.create
import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.get
import org.gradle.kotlin.dsl.named
import org.gradle.kotlin.dsl.register
import org.gradle.kotlin.dsl.the
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension

private const val JVM = "jvm"

fun Project.jvmShadow() {
    val mainCompilation = with(the<KotlinMultiplatformExtension>()) {
        targets.named(JVM).map { it.compilations.getByName("main") }
    }
    val sourceJar = tasks.register("sourceJar", Jar::class) {
        from(mainCompilation.map { it.allKotlinSourceSets.map { it.kotlin } })
        archiveClassifier.set("sources")
    }
    val shadowJvmJar = tasks.register("shadow${JVM.capitalized()}Jar", ShadowJar::class) {
        archiveBaseName = "${project.name}-$JVM"
        from(mainCompilation.map { it.output })
        archiveClassifier.set("")
        configurations = listOf(
            project.configurations["${JVM}CompileClasspath"],
            project.configurations["${JVM}RuntimeClasspath"],
        )
        dependsOn(tasks.named("jvmJar"))
        dependencies {
            exclude(dependency("org.slf4j:slf4j-api"))
        }
        this.configurations
    }
    with(the<PublishingExtension>()) {
        publications {
            create("shadow${JVM.capitalized()}", MavenPublication::class) {
                artifact(shadowJvmJar.map { it.archiveFile })
                artifact(sourceJar)
                artifactId = "${project.name}-$JVM"
                pom.withXml {
                    (asNode().get("dependencies") as NodeList).clear()
                }
            }
        }
    }
}

val TaskContainer.publishShadowJvmPublicationToMavenLocal: TaskProvider<PublishToMavenLocal>
    get() = named("publishShadowJvmPublicationToMavenLocal", PublishToMavenLocal::class)

相关问题