I have a full stack Kotlin Multiplatform web app with Kotlin/JVM backend and Kotlin/JS frontend.
The problem I'm having is that, when I go to execute the JAR, there is no Main-Class
manifest entry and I get this error:
$ java -jar shoppinglist-jvm-1.0-SNAPSHOT.jar
no main manifest attribute, in shoppinglist-jvm-1.0-SNAPSHOT.jar
I assume that this is, for some reason, by design and that I'm missing something essential.
Further Details:
- I am using the Ktor example project provided by Jetbrains, but this is also occurring my own project in the same way.
- I am executing the
jvm
JAR in the libs folder, but I also attempted it on both thejs
andmetadata
JARs with the same result. - I am using
gradle build
within IntelliJ Run Configurations to compile.
Extracting the JARs with 7-Zip reveals that js
and metadata
don't contain Java bytecode; so, I'm, mostly, ignoring those for now.
However, jvm
has interesting contents:
- The manifest file contains the version without the
Main-Class
. - There is a .kotlin_module file that does contain some kind of class list, but I'm not sure how that can be used.
- The bytecode .class and .js files are all in the expected locations.
- There is a .tar and .zip file that contains all the lib JARs along with a script that seems as though it is supposed to start the application, but it's all in an archive.
- In my project, these are included in the JAR itself.
- In other projects, these are included in the distributions directory.
I'm thinking this is something simple that I'm missing, but I can't seem to find documentation or questions about this specifically anywhere.
CodePudding user response:
You have 3 options to make it work.
1. Out of box option
# Build from the root dir
./gradlew clean build
cd build/distributions/
# unzip
tar -xvf shoppinglist-1.0-SNAPSHOT.tar
cd shoppinglist-1.0-SNAPSHOT/bin
# Execute launch script (generated by application plugin)
./shoppinglist
# Result
Hello, JVM!
2. Distribution option (My Fork with changes)
We need to add our custom distribution and edit manifest if you really want to execute it like java -jar shoppinglist-jvm-1.0-SNAPSHOT.jar
- Replace
application
plugin withdistribution
plugin - Remove
application
configuration block - Remove this
tasks.getByName<JavaExec>("run") {
classpath(tasks.getByName<Jar>("jvmJar")) // so that the JS artifacts generated by `jvmJar` can be found and served
}
- Replace
distribution
configuration block with
distributions {
main {
distributionBaseName.set("shoppinglist")
contents {
into("") {
val jvmJar by tasks.getting
from(jvmJar)
}
into("lib/") {
val main by kotlin.jvm().compilations.getting
from(main.runtimeDependencyFiles)
}
}
}
}
tasks.withType<Jar> {
doFirst {
manifest {
val main by kotlin.jvm().compilations.getting
attributes(
"Main-Class" to "ServerKt",
"Class-Path" to main.runtimeDependencyFiles.files.joinToString(" ") { "lib/" it.name }
)
}
}
}
Now you can launch it like so
# Build from the root dir
./gradlew clean build
cd build/distributions/
# unzip
tar -xvf shoppinglist-1.0-SNAPSHOT.tar
# Run
java -jar shoppinglist-jvm-1.0-SNAPSHOT.jar
# Result
Hello, JVM!
3. uberJar (fatJar) option (My Fork with changes)
1. Remove `applicaion` plugin
2. Remove `distributions` and `application` configuration blocks
3. Remove `stage` and `run` tasts
4. Add uberJar task
tasks.withType<Jar> {
doFirst {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
val main by kotlin.jvm().compilations.getting
manifest {
attributes(
"Main-Class" to "ServerKt",
)
}
from({
main.runtimeDependencyFiles.files.filter { it.name.endsWith("jar") }.map { zipTree(it) }
})
}
}