我尝试使用 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/