4

is there any way to setup Gradle Shadow with a Kotlin multiplatform project? I am using the "new" version of a multiplatform project, where I have all my source set definitions/dependencies in just one file. Here is my build file:

buildscript {
    ext.ktor_version = "1.0.0-beta-3"

    repositories {
       maven { url "https://plugins.gradle.org/m2/"}
    }

    dependencies {
        classpath "com.github.jengelman.gradle.plugins:shadow:4.0.2"
    }
}


plugins {
    id 'kotlin-multiplatform' version '1.3.0'
    id 'com.github.johnrengelman.shadow' version '4.0.2'
    id 'application'
}

version = '1.0'
group = '[redacted]'
mainClassName = '[redacted]'

repositories {
    maven { url "https://dl.bintray.com/kotlin/exposed" }
    maven { url "https://dl.bintray.com/kotlin/ktor" }
    mavenCentral()
    jcenter()
}
kotlin {
    targets {
        fromPreset(presets.jvm, 'jvm')
        fromPreset(presets.js, 'js')
    }
    sourceSets {
        commonMain {
            dependencies {
                implementation 'org.jetbrains.kotlin:kotlin-stdlib-common'
            }
        }
        commonTest {
            dependencies {
                implementation 'org.jetbrains.kotlin:kotlin-test-common'
                implementation 'org.jetbrains.kotlin:kotlin-test-annotations-common'
            }
        }
        jvmMain {
            dependencies {
                implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
                implementation 'org.jetbrains.exposed:exposed:0.11.2'
                implementation "org.mindrot:jbcrypt:0.4"
                implementation "org.slf4j:slf4j-simple:1.8.0-beta2"
                implementation "io.ktor:ktor-server-netty:$ktor_version"
                implementation "io.ktor:ktor-jackson:$ktor_version"
                implementation "mysql:mysql-connector-java:8.0.13"
            }
        }
        jvmTest {
            dependencies {
                implementation 'org.jetbrains.kotlin:kotlin-test'
                implementation 'org.jetbrains.kotlin:kotlin-test-junit'
            }
        }
        jsMain {
            dependencies {
                implementation 'org.jetbrains.kotlin:kotlin-stdlib-js'
            }
        }
        jsTest {
            dependencies {
                implementation 'org.jetbrains.kotlin:kotlin-test-js'
            }
        }
    }
}

shadowJar {
    baseName = '[redacted]'
    version = 1.0
}

Trying to use this, I have the sad outcome of a JAR file, with just the META-INF (304 bytes). I'm not sure where to begin, honestly, and this has kept me thinking and confused for hours. Anyone's help will be appreciated.

Skeleton of my project:

├── build.gradle
├── gradle.properties
├── settings.gradle
└── src
    ├── commonMain
    │   └── kotlin
    │       ├── PasswordValidator.kt
    │       └── Responses.kt
    └── jvmMain
        └── kotlin
            └── XXX
                └── XXXXXX
                    └── ticketing
                        ├── Auth.kt
                        ├── Registration.kt
                        ├── Server.kt
                        ├── requests
                        │   ├── Auth.kt
                        │   ├── Register.kt
                        │   └── account
                        │       ├── Close.kt
                        │       ├── List.kt
                        │       ├── ModifyPassword.kt
                        │       ├── New.kt
                        │       └── SetAdmin.kt
                        └── services
                            ├── AsyncHandler.kt
                            ├── Exception.kt
                            ├── RateLimiter.kt
                            └── Token.kt
Hamza ALI
  • 95
  • 1
  • 2
  • 8

2 Answers2

4

I do have a solution which works with the kotlin-multiplatform plugin version 1.3.31 and a project generated via IntelliJ New Project/Kotlin/JS Client and JVM Server | Gradle option.

buildscript {
  repositories {
    jcenter()
  }
}

plugins {
  id 'com.github.johnrengelman.shadow' version '5.0.0'
  id 'kotlin-multiplatform' version '1.3.31'
}

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

repositories {
  jcenter()
  maven { url "https://dl.bintray.com/kotlin/ktor" }
  mavenCentral()
}
def ktor_version = '1.2.1'
def logback_version = '1.2.3'

kotlin {
  jvm()
  js() {
    compilations.all {
      kotlinOptions {
        languageVersion = "1.3"
        moduleKind = "umd"
        sourceMap = true
        metaInfo = true
      }
    }
  }
  sourceSets {
    commonMain {
      dependencies {
        implementation kotlin('stdlib-common')
      }
    }
    commonTest {
      dependencies {
        implementation kotlin('test-common')
        implementation kotlin('test-annotations-common')
      }
    }
    jvmMain {
      dependencies {
        implementation kotlin('stdlib-jdk8')
        implementation "io.ktor:ktor-server-netty:$ktor_version"
        implementation "io.ktor:ktor-html-builder:$ktor_version"
        implementation "io.ktor:ktor-jackson:$ktor_version"
        implementation "ch.qos.logback:logback-classic:$logback_version"
      }
    }
    jvmTest {
      dependencies {
        implementation kotlin('test')
        implementation kotlin('test-junit')
        implementation "io.ktor:ktor-server-test-host:$ktor_version"
      }
    }
    jsMain {
      dependencies {
        implementation kotlin('stdlib-js')
      }
    }
    jsTest {
      dependencies {
        implementation kotlin('test-js')
      }
    }
  }
}

def webFolder = new File(project.buildDir, "web")
def jsCompilations = kotlin.targets.js.compilations

task populateWebFolder(dependsOn: [jsMainClasses]) {
  doLast {
    copy {
      from jsCompilations.main.output
      from kotlin.sourceSets.jsMain.resources.srcDirs
      jsCompilations.test.runtimeDependencyFiles.each {
        if (it.exists() && !it.isDirectory()) {
          from zipTree(it.absolutePath).matching { include '*.js' }
        }
      }
      into webFolder
    }
  }
}

jsJar.dependsOn(populateWebFolder)

def mainServerClassName = "org.pongasoft.jamba.quickstart.server.be.ServerKt"

task run(type: JavaExec, dependsOn: [jvmMainClasses, jsJar]) {
  main = mainServerClassName
  ignoreExitValue = true
  classpath {
    [
        kotlin.targets.jvm.compilations.main.output.allOutputs.files,
        configurations.jvmRuntimeClasspath,
    ]
  }
  args = ["-P:org.pongasoft.jamba.quickstart.server.staticWebDir=${webFolder.canonicalPath}"]
}

task shadowJar(type: ShadowJar, dependsOn: [jvmJar]) {
  from jvmJar.archiveFile
  configurations = [project.configurations.jvmRuntimeClasspath]
  manifest {
      attributes 'Main-Class': mainServerClassName
  }
}

I believe the main issue of why it is not working is that, according to the documentation:

From: Shadow documentation, Shadow is a reactive plugin. This means that applying Shadow by itself will perform no configuration on your project. Instead, Shadow reacts This means, that for most users, the java or groovy plugins must be explicitly applied to have the desired effect.

and as a result it doesn't work out of the box with kotlin multiplatform plugin which has a non traditional setup. So the trick is to define a ShadowJar task which depends on jvmJar and uses the artifact as its from (jvmJar.archiveFile) and the project.configurations.jvmRuntimeClasspath configuration to include all the runtime dependencies. And this is also the place to define the Main-Class entry for the manifest.

Note that this version is not bundling static resources generated for the javascript portion of the compilation.

yan
  • 2,932
  • 1
  • 23
  • 25
0

Actually, you don't need Shadow. Just add the following code in kotlin > targets (build.gradle) block

configure([jvmJar]) {
    manifest{
        attributes 'Main-Class':'main.class.path.MainKt'
    }
}

The generated Jar file in build/libs will contain specified Main-Class in manifest. All the required classes are also already there. Generated jar is ready to use (don't forget to specify external dependencies in the project where you are using jar - I managed to get it work on gradle java project).

Simon
  • 1,657
  • 11
  • 16
  • Thank you! It is good to see that Using external plugins to do this isn't necessary. – Hamza ALI Jan 09 '19 at 03:07
  • 1
    This doesn't work. `id 'kotlin-multiplatform' version '1.3.21'` – Nandor Poka Mar 30 '19 at 21:51
  • This compiles but the kotlin dependencies aren't included and I can't run `java -jar ...` – GarouDan Apr 25 '19 at 17:55
  • True. It does not work completely OOB. You must create java project in IntelliJ, add the generated jar file as a dependency and then specify the required dependencies in build.gradle (jar contains only your code). – Simon May 06 '19 at 04:48
  • I don't know why this is marked as a "good" response since it does not work at all. The jvmJar target does not include any of the kotlin dependencies which is the reason why you need to use shadowJar in the first place (Caused by: java.lang.ClassNotFoundException: io.ktor.application.Application). – yan Jun 06 '19 at 18:18