using kotlinx serialization inside a gradle plugin that creates a gradle task - kotlin

I am trying to create a gradle plugin that will generate files (serialized from data classes) from a gradle task that can run in another project.
lets say that the classes that I am serializing are marked with some annotation #Annot and I find all the relevant classes with reflection in the gradle task (I made sure to depend on kotlin compile so that the binaries are created). The problem is that when I try to use
val clazz: Class<*>
clazz.kotlin.serializer()
I get a Serializer for class 'Type' is not found. (Type is the actual class that I found and is annotated with #Serializable and #Annot .
I am using gradle version 7.2, kotlin 1.5.21 (tried with 1.5.31 too)
The project that uses the plugin has a kotlinx serialization plugin enabled
What am I missing? why can’t I access the class serializer with the gradle task?
Note* if I run the above code in the target project (and not in the plugin then the serializer() function doesn't throw an exception

So This didn't work in a the way I wanted it to but I found a way to make it work.
I defined a task that extends JavaExec task:
tasks.create(createFilesTaskName, JavaExec::class.java) {
mainClass.set("package.of.file.SchemaKt")
classpath = sourceSets.getByName("main").runtimeClasspath
group = groupName
}
The code in SchemaKt is in the source set of my kotlin sources or alternatively in a package required by the current project.
The serializer() is accessible and working from there and I can run the schemas creation from a gradle task which is exactly what I needed.
I hope this helps someone in the future.

Related

The same Gradle Kotlin `copy.from` api use `it` when called from build file (kt) and `this` when called from Pluign (kt)

I hope someone can explain this to me.
I was moving some code from a Gradle build file into a Gradle plugin.
Below are two code snippes calling the same from function (based on Ideas indexing)
I noticed that there are some strange differences between how the apis can be used in those two contexts.
I know that Gradle is adding some extra syntax suger around the build files which is why I need to manually cast the task in the Plugin.kt file, but I cannot find anything that explains why from in context of the Build file has this as context where in the plugin the function uses it to access the into function.
It is not just Idea that reports this, running Gradle also shows that it must be like this.
I assume this is something special to Kotlin's way of handling the Action interface in different contexts:
kts file (No wrapping class)
kt file (with class)
Here are the two samples
hostedStaticFiles is gradle configuration that will be used to configure web frontend from a separate build.
build.gradle.kts
tasks.getByName<ProcessResources>("processResources") {
this.from(hostedStaticFiles) {
this#from.into("static") // <-- Note use of this here
}
}
Plugin.kt
project.tasks.getByName("processResources").let<Task, ProcessResources> {
if (it !is ProcessResources) {
throw IllegalStateException("The processResources task in Project is not of type ${ProcessResources::class.java}")
}
it
}.apply {
dependsOn(hostedStaticFiles)
this#apply.from(hostedStaticFiles) { it -> // <-- Note use of it here and below
it.into("static")
}
}
dependencies {
hostedStaticFiles(project("client"))
}
I hobe someone can point me to an explanation or preferably documentation on why this behaves this way :)
Gradle version 7.4.1
###################
After getting the answer from #Joffrey I updated my buildSrc/build.gradle.kts with the below plugin configuration and it all started working as expected.
plugins {
`java-gradle-plugin`
`kotlin-dsl`
}
Gradle uses the HasImplicitReceiver annotation on some function types (like Action), so you can use this instead of it. It leverages Kotlin's SAM-with-receiver compiler plugin.
In Kotlin build scripts (.gradle.kts files) you benefit from this automatically because the Kotlin compiler used to compile your scripts is already properly configured. However, in custom plugin projects, you are in control of the build and you need to apply the kotlin-dsl plugin yourself. As mentioned in the documentation, it does a few things for you, including:
Configures the Kotlin compiler with the same settings that are used for Kotlin DSL scripts, ensuring consistency between your build logic and those scripts.

Multi-project Gradle+Kotlin: How to create Jar containing all sub-projects using Kotlin DSL?

I have a Gradle project with two subprojects. The parent does not contain any code; all the Kotlin code is in the two subprojects. All Gradle build files are defined in the Kotlin DSL.
Upon building, Gradle generates two JAR files, one in the build subfolder of each subproject. I believe this is the intended default behavior of Gradle. But this is not what I want.
I want to publish the JAR file of the parent project as a Maven artifact. Therefore, I need both subprojects to be included in one JAR file. How can I achieve this?
Note: On this web page, the author seems to achieve pretty much what I would need in this code snippet:
apply plugin: "java"
subprojects.each { subproject -> evaluationDependsOn(subproject.path)}
task allJar(type: Jar, dependsOn: subprojects.jar) {
baseName = 'multiproject-test'
subprojects.each { subproject ->
from subproject.configurations.archives.allArtifacts.files.collect {
zipTree(it)
}
}
}
artifacts {
archives allJar
}
However, this is defined in Gradle's native Groovy DSL. And I find myself unable to translate it into the Kotlin DSL. I tried to put a Groovy build file (*.gradle) besides the Kotlin build file (*.gradle.kts), but this led to a strange build error. I'm not sure if mixed build file languages are supported. Besides, I would consider it bad practice too. Better only define all build files in just one language.
Also, the example above pertains to the Java programming language. But I do not expect this to be a big problem, as both Java and Kotlin produce JVM bytecode as compile output.
More clarification:
I am not talking about a "fat JAR". Dependencies and the Kotlin library are not supposed to be included in the JAR.
I do not care if the JAR files for the subprojects are still getting built or not. I'm only interested in the integrated JAR that contains both subprojects.
The main point is getting the combined JAR for the binaries. Combined JARs for the sources and JavaDoc would be a nice-to-have, but are not strictly required.
I would use the Gradle guide Creating "uber" or "fat" JARs from the Gradle documentation as a basis. What you want is essentially the same thing. It's also much better than the Groovy example you found, as it doesn't use the discouraged subprojects util, or 'simple sharing' that requires knowing how the other projects are configured.
Create a configuration for resolving other projects.
// build.gradle.kts
val mergedJar by configurations.creating<Configuration> {
// we're going to resolve this config here, in this project
isCanBeResolved = true
// this configuration will not be consumed by other projects
isCanBeConsumed = false
// don't make this visible to other projects
isVisible = false
}
Use the new configuration to add dependencies on the projects we want to add into our combined Jar
dependencies {
mergedJar(project(":my-subproject-alpha"))
mergedJar(project(":my-subproject-beta"))
}
Now copy the guide from the docs, except instead of using configurations.runtimeClasspath we can use the mergedJar configuration, which will only create the subprojects we specified.
However we need to make some modifications.
I've adjusted the example to edit the existing Jar task rather than creating a new 'fatJar' task.
for some reason, setting isTransitive = false causes Gradle to fail resolution. Instead I've added a filter (it.path.contains(rootDir.path)) to make sure the Jars we're consuming are inside the project.
tasks.jar {
dependsOn(mergedJar)
from({
mergedJar
.filter {
it.name.endsWith("jar") && it.path.contains(rootDir.path)
}
.map {
logger.lifecycle("depending on $it")
zipTree(it)
}
})
}

kapt in Kotlin Multiplatform - src dirs not recognized by gradle and IDE by default

I have a Kotlin annotation processor library which generates some classes and writes them with
FileSpec.builder(...)
...
.build()
.writeTo(processingEnv.filer)
The generated files end up in build/generated/source/kapt/.... They are only recognized by gradle if I add kotlin.srcDir("${buildDir.absolutePath}/generated/source/kapt/") to my build.gradle.kts.
It works the same way if I use kapt.kotlin.generated option - the path changes to build/generated/source/kaptKotlin/... but I still need to explicitly add the path to sources dir for the gradle to use these files during build process. Otherwise it's not only unrecognized by gradle, but also by Intellij.
Important note: my processor works in a Kotlin Multiplatform module.
I don't see necessity to include the generated source dirs with codegen libraries like dagger or moshi - they seem to just write to processingEnv.filer and everything works automagically.
So there must either be some additional configuration which makes it possible to omit the explicit kotlin.srcDir... or there is some limitation of KMM project. Any idea what the fix might be?

Hamcrest and Mockk for Kotlin Multiplatform Mobile

I am trying to write tests in the shared module of a KMM project. In the shared module's build.gradle.kts file I have the following:
sourceSets {
val commonMain by getting
val commonTest by getting {
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
//TODO: Hamcrest
//implementation("junit:junit:4.12")
//implementation("org.hamcrest:hamcrest-library:1.3")
//TODO: Mockk
//implementation("io.mockk:mockk:1.10.4")
}
}
//...
}
I have also tried:
implementation(kotlin("[library]"))
with the same result: The tests are no longer recognised by the IDE and I cannot run them.
Unfortunately there isn't a mocking library that has K/N support (AFAIK).
Here's Mockk's K/N and Mockk's K/JS issue for future reference or you could also check out Touchlab's Karmok
For Hamcrest, see their issue here
Edit/Update
In 2022 above answer doesn't stand true anymore. Mockk now supports mocking in shared modules. Please check here mockk.io Add the following dependency and you should be good to go
testImplementation "io.mockk:mockk-common:{version}"
The mocking experience is seamless, just like a regular Android unit test case.
I got a response from the KMM team - thought I would put it here for reference
You can use only multiplatform dependencies that support all declared targets in common source-set, because this could will be used for compilation for all the targets. Junit is not a multiplatform library, it’s JVM, so you should add it to your jvm target source-set (androidMain if you declared android() target). Check this project: https://github.com/Kotlin/kmm-sample/blob/master/shared/build.gradle.kts for example.
The same issue relates to other dependencies - they are not multiplatform, so you can’t use them in a commons source-set.

How to include an Intellij gradle project inside another Intellij gradle project?

I have two java projects as Bukkit/Spigot plugins. Both projects are using gradle, private repositories, and one project should inherit from another.
Projects:
SpigotCore - Contains database management and utility classes. This is the "main" project.
Minigame Framework - Runs minigames. Needs database access and utility class access.
What is the best way to make the Minigame Framework inherit from the SpigotCore project using gradle? I have been unsuccessful in getting Intellij module dependency working. Any and all help is appreciated!
I solved this issue by just compiling the SpigotCore and then including the jar file in my build.gradle file.
Answer used: https://stackoverflow.com/a/20956456/2865125
Now following the answer above, it shaded the SpigotCore classes into the MinigameFramework. This is not what we want since both projects are Bukkit plugins and will be provided at runtime. So I changed "compile" to "provided" and it works great!
The change:
dependencies {
provided files("somePath/spigotcore.jar")
}