Include dependency for custom sourceSet - kotlin

I have a build.gradle.kts for a small, pure kotlin project (I am aware I am using slightly non-standard source paths):
plugins {
kotlin("jvm") version "1.3.72"
}
repositories { mavenCentral() }
dependencies {
implementation(kotlin("stdlib-jdk8"))
testImplementation("org.jetbrains.kotlin:kotlin-test")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
}
sourceSets["main"].java.srcDir("src")
sourceSets["test"].java.srcDirs("test")
sourceSets {
create("demo")?.let {
it.java.srcDir("demo")
// Also tried: it.java.srcDirs("src", "demo")
it.compileClasspath += main.get().output
it.runtimeClasspath += main.get().output
}
}
tasks {
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}
}
listOf("InteractiveClient", "LockingBufferDemo").forEach {
tasks.register<Jar>(it) {
manifest { attributes["Main-Class"] = "${it}Kt" }
from(sourceSets.main.get().output)
from(sourceSets["demo"].output) {
include("**/${it}Kt.class")
}
dependsOn(configurations.runtimeClasspath)
from({
configurations.runtimeClasspath.get().filter {
it.name.endsWith("jar") }.map { zipTree(it) }
})
}
}
When I try to run one of the "demo" sourceSet based jar tasks ("InteractiveClient" and "LockingBufferDemo"),1 I get a the long list of "Cannot access built-in..." errors indicating the kotlin stdlib is not properly in play.
The actual failing task is compileDemoKotlin, so I tried adding mimetically to the tasks block:
withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
this.kotlinOptions.jvmTarget = "1.8"
}
Which makes no difference.
What's strange to me is that the demo stuff was originally in the test sourceSet, and changing the above back to that (by removing the definition, changing from(sourceSets["demo"]... to from(sourceSets.test... in the jar task(s), and moving the source file) makes the problem disappear. It works.
I don't want this stuff in with automated tests. I imagine I could put them in branches of the main or test set and then use a from() { exclude(... pattern in building the jars,
but that seems awkward and unnecessary.
How do I get a custom source set to compile against the default project dependencies?
See this other recent question of mine about the from(... include( in the jar tasks.

It looks to me like you are missing the configurations that will make the demo source sets use the same dependencies as the main set. Something like this:
configurations["demoImplementation"].extendsFrom(configurations.implementation.get())
configurations["demoRuntimeOnly"].extendsFrom(configurations.runtimeOnly.get())
There is an example in the user guide here that seems to have a very similar use case as yours.
Also, from the issue you created in the Gradle repository, you mentioned it failed with:
Unresolved reference: printlin
I am pretty sure this is a typo of println.

I'm not entirely sure what you're trying to do with your jar files, but I spotted a few problems in your build script:
You have set your source sets like this:
sourceSets["main"].java.srcDir("src")
sourceSets["test"].java.srcDirs("src", "test")
sourceSets {
create("demo")?.let {
it.java.srcDir("demo")
}
}
This means you're supposed the following directory structure:
- <module root>
- src <-- Belongs to both 'main' and a 'test' source sets!
- test <-- Belongs to the 'test' source set
- demo <-- Belongs to the 'demo' source set
As you can see, there's a directory that belongs to two source sets. I'm not sure how this turns out in practice, probably one or the other is discarded. Here's a more standard directory structure:
- <module root>
- src
- main
- test
- demo
You configure it like this:
sourceSets {
main {
java.srcDir("src/main")
}
test {
java.srcDir("src/test")
}
create("demo") {
java.srcDir("src/demo")
}
}
The task compileDemoKotlin actually exists, but you can't access it just like that. If you look at the compileKotlin and compileTestKotlin extension functions source, they look like this:
val TaskContainer.`compileKotlin`: TaskProvider<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>
get() = named<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>("compileKotlin")
So the trick is to use named to get the task instead:
named<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>("compileDemoKotlin") {
kotlinOptions.jvmTarget = "1.8"
}
I don't know if that answers your question. If I missed anything please let me know.

Related

Failing to create a fat jar

I am trying to use Shadow Gradle plugin to create a fat jar.
Part of my build.gradle.kts looks like this:
plugins {
application
kotlin("jvm") version "1.6.21"
id("com.github.johnrengelman.shadow") version "7.1.2"
}
group = "com.test"
version = "0.1"
application {
mainClass.set("com.test.ApplicationKt")
}
Everything is good, but I want to include .properties files in the fat jar as well.
tasks {
withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> { kotlinOptions { jvmTarget = "17" } }
named<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>("shadowJar") {
archiveBaseName.set("shadow")
mergeServiceFiles()
manifest { attributes(mapOf("Main-Class" to "com.test.ApplicationKt")) }
// include("*.properties")
}
For some reason uncommenting the include("*.properties") statement above, makes my fat jar empty. I can build it ok with ./gradlew shadowJar but when I try to run the jar with java -jar I get the error Error: Could not find or load main class...
Any idea what I am missing?
specify the main class name inside shadow task
tasks {
named<ShadowJar>("shadowJar") {
archiveBaseName.set("shadow")
mergeServiceFiles()
manifest {
attributes(mapOf("Main-Class" to "main-class-path"))
}
}
}

How properly to publish a Kotlin Multiplatform package with 1.6.10

I'm trying to publish to GitHub private package repo, and failing when I try to build the dependent project.
I've built and published it, and loaded it into a dependent project. Gradle makes no complaint and appears to download the requested project. The editor sees the symbols and is able to give autocomplete advice and type checking, but when I try to build, the :common:compileKotlinMetadata task fails with Unresolved references on the import statements referring to my package.
I've never yet seen something like a manifest describing exactly which artifacts are required by Kotlin MPP's various components. This project builds and runs fine if the dependency is just added as a subproject to build.gradle.kts and built along with the main package.
Again, the symbols all appear to have been published and acquired by the dependent project.
So, what artifacts might be missing, that the compiler requires for compileKotlinMetadata? I'd be fascinated to learn something about the kotlin toolchain here, namely: what files/resources are needed by the compiler versus those used by the editor to produce coding advice!
A rundown of what I've done:
On the dependent project:
gradle.properties
kotlin.code.style=official
kotlin.native.enableDependencyPropagation=false
android.useAndroidX=true
kotlin.version=1.6.10
agp.version=7.0.4
compose.version=1.1.1
kotlin.mpp.enableGranularSourceSetsMetadata=true
kotlin.native.disableCompilerDaemon=true
build.gradle.kts
repositories {
/* configure my repo here */
}
kotlin {
sourceSets {
commonMain {
dependencies {
implementation("com.mycompany.groupname:package:1.0.12")
}
}
desktopMain {
implementation("com.mycompany.groupname:package-jvm:1.0.12")
}
}
}
On the published project:
gradle.properties
kotlin.code.style=official
kotlin.mpp.enableGranularSourceSetsMetadata=true
kotlin.native.enableDependencyPropagation=false
android.useAndroidX=true
kotlin.version=1.6.10
agp.version=7.0.4
compose.version=1.1.1
realm.version=0.10.2
build.gradle.kts
fun String.dasherize() = fold("") {acc, value ->
if (value.isUpperCase()) {
"$acc-${value.toLowerCase()}"
} else {
"$acc$value"
}
}
fun makeArtifactId(name: String) =
if ("kotlinMultiplatform" in name) {
mvnArtifactId
} else {
"$mvnArtifactId-${name.dasherize()}"
}
afterEvaluate {
configure<PublishingExtension> {
publications.all {
val mavenPublication = this as? MavenPublication
mavenPublication?.artifactId = makeArtifactId(name)
}
}
}
configure<PublishingExtension> {
publications {
withType<MavenPublication> {
groupId = "com.meowbox.fourpillars"
artifactId = makeArtifactId(name)
version
}
}
}

Jar made by gradle's jar task is not "fat" enough

...obviously some necessary things are not included from the dependencies.
Once it reaches a call to an external library, it breaks, either with ClassNotFoundException, or without a word.
I started with this skeleton project.
Relevant changes in build.gradle:
application {
mainClassName = 'net.laca.FoKt'
}
(my main function is in fo.kt)
dependencies {
//...
compile "com.sparkjava:spark-core:2.9.3"
implementation 'com.google.code.gson:gson:2.8.6'
implementation fileTree('libs') { include '*.jar' }
}
jar {
archiveBaseName = 'csira'
// Uncommend the last two lines to build a "fat" jar with `./gradlew jar`,
// and run it without Gradle's help: `java -jar build/libs/skeleton.jar`
manifest { attributes 'Main-Class': 'net.laca.FoKt' }
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
}
versions: Kotlin 1.4.20, Java 11, Gradle 6.7.1
Allegedly it should work this way. As it does if I start it with gradle run.
But when I start it with java -jar build/libs/csira.jar after gradle jar, it doesn't.
Relevant parts of fo.kt:
package net.laca
import spark.Spark.*
import com.google.gson.GsonBuilder
fun main(args: Array<String>) {
before("/*")
{ req, res ->
res.type("application/json")
println("hívás: ${req.requestMethod()} ${req.pathInfo()} " + req.queryString())
println(GsonBuilder().create().toJson(req.queryMap().toMap())) //line 14
//...
}
At GsonBuilder it breaks:
java.lang.NoClassDefFoundError: com/google/gson/GsonBuilder
at net.laca.FoKt$main$1.handle(fo.kt:14)
at spark.FilterImpl$1.handle(FilterImpl.java:73)
at spark.http.matching.BeforeFilters.execute(BeforeFilters.java:48)
at spark.http.matching.MatcherFilter.doFilter(MatcherFilter.java:133)
at ...
...
Caused by: java.lang.ClassNotFoundException: com.google.gson.GsonBuilder
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:602)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
... 19 more
And when I take/comment out the 14th line, and it reaches a call to my own jar in /libs:
get("/whatever")
{
println("before")
com.zz.app.MyScalaClass.apply().myFun()
println("after")
}
then the last thing I see is before, the rest is silence.
It happens because your jar task is configured incorrectly. To understand why, look at your dependencies:
dependencies {
//...
compile "com.sparkjava:spark-core:2.9.3"
implementation 'com.google.code.gson:gson:2.8.6'
implementation fileTree('libs') { include '*.jar' }
}
You are using both the compile and implementation configurations. The former is deprecated and should not be used by the way.
Then look at the jar task:
jar {
archiveBaseName = 'csira'
// Uncommend the last two lines to build a "fat" jar with `./gradlew jar`,
// and run it without Gradle's help: `java -jar build/libs/skeleton.jar`
manifest { attributes 'Main-Class': 'net.laca.FoKt' }
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
}
The from part instructs Gradle to collect all dependencies from the compile configuration only, and this will ignore the implementation configuration completely.
While you could change "compile" to "implementation" everywhere, the correct way to construct a fat jar is to actually collect from the runtimeClasspath configuration. This one extends other configurations like compile and implementation, but also runtimeOnly which you might find handy in the future.
There is actually also an example of how to do this in the Gradle user guide. To adapt it for your project, it should look like:
jar {
archiveBaseName = 'csira'
manifest { attributes 'Main-Class': 'net.laca.FoKt' }
dependsOn configurations.runtimeClasspath
from {
configurations.runtimeClasspath.findAll { it.name.endsWith('jar') }.collect { zipTree(it) }
}
}
The extra dependsOn line ensures that the runtimeClasspath configuration is completely resolved before trying to use it. Another difference is that it only collects jar files.

Koltlin 1.3.61 - Multi platform project - Not able to run java test in jvmTests

I created a multiplatform project using Intellij2019.3.1. The project contains only default sample classes created by Idea.
I am trying to run a java test in kotlin 1.3.61 using IntelliIdea(2019.3.1).
When I try to run the jvm test then it's fail with
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':jvmTest'.
> No tests found for given includes: [sample.TestJava](filter.includeTestsMatching)
Please see the build.gradle file which was created by Idea by default
plugins {
id 'org.jetbrains.kotlin.multiplatform' version '1.3.61'
}
repositories {
mavenCentral()
}
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
macosX64("macos")
sourceSets {
commonMain {
dependencies {
implementation kotlin('stdlib-common')
}
}
commonTest {
dependencies {
implementation kotlin('test-common')
implementation kotlin('test-annotations-common')
}
}
jvmMain {
dependencies {
implementation kotlin('stdlib-jdk8')
}
}
jvmTest {
dependencies {
implementation kotlin('test')
implementation kotlin('test-junit')
// implementation kotlin("org.junit.jupiter:junit-jupiter-api:5.3.2")
//implementation kotlin("org.junit.jupiter:junit-jupiter-engine:5.3.2")
}
}
jsMain {
dependencies {
implementation kotlin('stdlib-js')
}
}
jsTest {
dependencies {
implementation kotlin('test-js')
}
}
macosMain {
}
macosTest {
}
}
}
Could someone please help
Not sure if this is the solution to your concrete problem, but whenever IDEA gave me an error like this "no tests found" message you got, it was because my project (with the soirces I wanted to test) failed to compile.
Oddly enough, IDEA didn't give me a warning that it hit a compiler error somewhere along the way... So I'd advise you to check your code compiles fine all the way.
As I said, maybe this isn't the solution to your specific problem, just an educated guess. To be sure about it, one would need some code to reproduce the error.
Solution has been provided in https://discuss.kotlinlang.org/t/koltlin-1-3-61-multi-platform-project-default-idea-project-not-able-to-run-java-test-in-jvmtests/15962/3
Quoting from kotlin forum
By default, Java sources are not supported in MPP project. You have to
add the support explicitly using withJava() DSL:
https://kotlinlang.org/docs/reference/building-mpp-with-gradle.html#java-support-in-jvm-targets
1. Please try it.

Create fat jar from kotlin multiplatform project

I recently switched from old 1.2 multiplatform into 1.3. Difference is, there's one one build.gradle file per multiplatform module (I got 5 of them) so a lot less configuration.
However I can't seem to be able to configure creating runnable fat jar with all dependencies from jvm platform.
I used to use standard "application" plugin in my jvm project and jar task, but that does not work anymore. I found there's "jvmJar" task and I modified it (set Main-class), but created jar doesn't contain dependencies and crashes on ClassNotFoundException. How do I do it?
This is what I have now:
jvm() {
jvmJar {
manifest {
attributes 'Main-Class': 'eu.xx.Runner'
}
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
}
}
I did hit that bump and used this work around.
1. Restructure your project
Lets call your project Project.
create another submodule say subA, which will have the gradle notation Project:subA
now, subA has your multiplatform code in it (It is the gradle project with apply :kotlin-multiplafrom) in its build.gradle
2. Add Another submodule
create another submodule which targets only jvm say subB, which will have the gradle notation Project:subB
So, subB will have plugins: 'application' and 'org.jetbrains.kotlin.jvm'
3. Add your module as a gradle dependency (see my build.gradle)
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.3.31'
id "application"
}
apply plugin: "kotlinx-serialization"
group 'tz.or.self'
version '0.0.0'
mainClassName = "com.example.MainKt"
sourceCompatibility = 1.8
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
dependencies {
implementation project(':subA')
}
you can proceed and build subB as you would a regular java project or even use the existing plugins, it will work
Got it working with the multiplatform plugin in kotlin 1.3.61:
The following works for a main file in src/jvmMain/kotlin/com/example/Hello.kt
Hello.kt must also specify its package as package com.example
I configured my jvm target in this way:
kotlin {
targets {
jvm()
configure([jvm]) {
withJava()
jvmJar {
manifest {
attributes 'Main-Class': 'com.example.HelloKt'
}
from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
}
}
}
}
Got it to work with a slightly modified version of what luca992 did:
kotlin {
jvm() {
withJava()
jvmJar {
manifest {
attributes 'Main-Class': 'sample.MainKt'
}
from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
}
}
...
}
The only way to get gradle/multiplatform working appears to be endless trial and error; It's a nightmare, it's not being built as a "build" system so much as a "build system"; to put it another way, these two tools (together or in isolation) are a means of implementing only a single software development life cycle that the plugin maker intended, however, if you've engineered a desired software lifecycle and CI/CD system and now your trying to implement that engineering, it will be MUCH harder to do it with these tools than it would be to do it with scripts, code or maven. There are a number of reasons for this:
Massive changing in coding convention due to the plugin makers only exposing bar minimum configurability, probably only giving access to the things they need for their own personal project.
Very poor documentation updates; Kotlin, gradle and plugins are changing so rapidly I have begun to seriously question the usefulness of these tools.
Thus, at the time of writing this seems to be the correct syntax to use when using kotlin 1.3.72, multiplatform 1.3.72, ktor 1.3.2 and gradle 6.2.2 (using the kts format).
Note the fatJar seems to assemble correctly but won't run, it can't find the class, so I included the second runLocally task I've been using in the mean time.
This isn't a complete solution so I hate posting it on here, but from what I can tell... it is the most complete and up to date solution I can find documented anywhere.
//Import variables from gradle.properties
val environment: String by project
val kotlinVersion: String by project
val ktorVersion: String by project
val kotlinExposedVersion: String by project
val mySqlConnectorVersion: String by project
val logbackVersion: String by project
val romeToolsVersion: String by project
val klaxonVersion: String by project
val kotlinLoggingVersion: String by project
val skrapeItVersion: String by project
val jsoupVersion: String by project
val devWebApiServer: String by project
val devWebApiServerVersion: String by project
//Build File Configuration
plugins {
java
kotlin("multiplatform") version "1.3.72"
}
group = "com.app"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
jcenter()
jcenter {
url = uri("https://kotlin.bintray.com/kotlin-js-wrappers")
}
maven {
url = uri("https://jitpack.io")
}
}
//Multiplatform Configuration
kotlin {
jvm {
compilations {
val main = getByName("main")
tasks {
register<Jar>("buildFatJar") {
group = "application"
manifest {
attributes["Implementation-Title"] = "Gradle Jar File Example"
attributes["Implementation-Version"] = archiveVersion
attributes["Main-Class"] = "com.app.BackendAppKt"
}
archiveBaseName.set("${project.name}-fat")
from(main.output.classesDirs, main.compileDependencyFiles)
with(jar.get() as CopySpec)
}
register<JavaExec>("runLocally") {
group = "application"
setMain("com.app.BackendAppKt")
classpath = main.output.classesDirs
classpath += main.compileDependencyFiles
}
}
}
}
js {
browser { EXCLUDED FOR LENGTH }
}
sourceSets { EXCLUDED FOR LENGTH }
}