How to set up Kotlin PSI for IntelliJ IDEA Gradle Plugin? - kotlin

I'm building a Plugin for the IntelliJ IDE to manipulate Kotlin files in a project. I've been able to write a bunch of tests to take a Kotlin file and generate a new file based on its contents. When I run this plugin in the IDE I'm unable to detect files as Kotlin files. When looking at the debugger my file says that it is a KtFile from the org.jetbrains.kotlin.psi library. But if I try to cast it to a KtFile I get an error:
java.lang.ClassCastException: org.jetbrains.kotlin.psi.KtFile cannot be cast to org.jetbrains.kotlin.psi.KtFile
So apparently the library version is off between runtime and compile time. What do I have to do to configure my plugin to use the correct Kotlin PSI at plugin runtime?
My plugin.xml looks like this:
<idea-plugin>
<id>...</id>
<name>...</name>
<vendor email="..." url="...">...</vendor>
<description><...</description>
<depends>com.intellij.modules.all</depends>
<depends>org.jetbrains.kotlin</depends>
<actions>...</actions>
</idea-plugin>
My build.gradle.kts looks like:
plugins {
id("org.jetbrains.intellij") version "0.4.16"
kotlin("jvm") version "1.3.61"
}
group = "..."
version = "..."
repositories {
mavenCentral()
}
dependencies {
implementation(kotlin("stdlib-jdk8"))
implementation(kotlin("compiler-embeddable", "1.3.61"))
implementation(kotlin("gradle-plugin", "1.3.61"))
testImplementation(group = "junit", name = "junit", version = "4.12")
}
buildscript {
repositories { mavenCentral() }
dependencies {
classpath(kotlin("compiler-embeddable", "1.3.61"))
classpath(kotlin("gradle-plugin", "1.3.61"))
}
}
intellij {
version = "2019.1.4"
setPlugins("Kotlin")
}
tasks {
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}
}
tasks.getByName<org.jetbrains.intellij.tasks.PatchPluginXmlTask>("patchPluginXml") {
changeNotes("...")
}
I already am aware of How to include Kotlin PSI classes (e.g. KtClass) in Intellij IDEA Gradle plugin project written in Kotlin? and How to add Kotlin PSI source files to IDEA Plugin project configuration which is essentially what I want to be answered but haven't gotten anything to fix my issue. Maybe there is some documentation on this issue but it evades my searches.

Your dependencies should include implementation(kotlin("reflect")) and plugin.xml should include <depends>org.jetbrains.kotlin</depends>

Related

"Could not find method wrapper()" in build.gradle file generated by openapi-generator kotlin client

I have a simple spring boot project using the kotlin gradle dsl. I want to generate an OpenApi client using the openapi client generator gradle Plugin. I have successfully done so, using this configuration. Until now, this was a single project build. But when i try to include it, i get an error message "Could not find method wrapper()".
This is how i generated the client and added it's file into my source sets:
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("org.springframework.boot") version "3.0.2"
id("io.spring.dependency-management") version "1.1.0"
kotlin("jvm") version "1.7.22"
kotlin("plugin.spring") version "1.7.22"
id("org.openapi.generator") version "6.3.0"
}
// other dependencies
openApiGenerate {
generatorName.set("kotlin")
inputSpec.set("src/main/openapi/my-api.yml")
outputDir.set("$buildDir/generated/my-api")
packageName.set("com.myapi")
}
kotlin.sourceSets["main"].kotlin.srcDir("$buildDir/generated/my-api/src/main/kotlin")
Now i want to use this generated client in my project. It comes with it's own build.gradle (in groovy) which loads the necessary dependencies etc.
I have modified my settings.gradle.kts file accordingly:
rootProject.name = "myapp"
include("build:generated:my-api")
When i reload gradle, i get the follwing error:
> Could not find method wrapper() for arguments [build_gdswinwcvulw9afq79kj4v6h$_run_closure1#582f32f7] on project ':build:generated:my-api' of type org.gradle.api.Project.
This is due to the build.gradle file generated by the generator looking like this:
group 'org.openapitools'
version '1.0.0'
wrapper {
gradleVersion = '7.5'
distributionUrl = "https://services.gradle.org/distributions/gradle-$gradleVersion-all.zip"
}
buildscript {
ext.kotlin_version = '1.7.21'
repositories {
maven { url "https://repo1.maven.org/maven2" }
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
apply plugin: 'kotlin'
apply plugin: 'maven-publish'
repositories {
maven { url "https://repo1.maven.org/maven2" }
}
test {
useJUnitPlatform()
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
implementation "com.squareup.moshi:moshi-kotlin:1.13.0"
implementation "com.squareup.moshi:moshi-adapters:1.13.0"
implementation "com.squareup.okhttp3:okhttp:4.10.0"
testImplementation "io.kotlintest:kotlintest-runner-junit5:3.4.2"
}
I am using Gradle 7.6 and i am a bit out of ideas here since i am pretty new to Gradle.

How can I apply a plugin to itself using Kotlin DSL?

We have an existing plugin project which configures various things (including static analysis), where we want to apply the plugin to the project itself.
The way this currently works for plugins written in Java is, you add the Java src dir to the buildSrc project, and then classes built there can be used in the main project. So I'm trying to get the same thing working for plugins written as Kotlin scripts.
But when I try to build it, compiling buildSrc fails with:
e: C:\Users\Trejkaz\Documents\test\self-applying-gradle-plugin\src\main\kotlin\example.common.gradle.kts: (1, 1): Unresolved reference: allprojects
> Task :buildSrc:compileKotlin FAILED
What's missing in order to make this work?
Further investigation:
If I put a copy of the files in buildSrc/src/main/kotlin, that works.
If I put a copy of the files in buildSrc/src/main/kotlin2 and use srcDirs to set that directory, that fails too. So it really looks like something isn't letting me relocate sources at all.
I pushed a repo to play with this here but what follows is the contents of the build scripts in case it's ever deleted.
The main build.gradle.kts:
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
`java-gradle-plugin`
`kotlin-dsl`
// Matching version in Gradle
kotlin("jvm") version "1.5.31"
}
apply(from = "common-build.gradle.kts")
apply(plugin = "example.common") // 👈 trying to apply the compiled plugin here
group = "org.example"
version = "1.0-SNAPSHOT"
tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "11"
}
In buildSrc/build.gradle.kts, we have this - note that it adds a source dir for the sources in the main directory:
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
`java-gradle-plugin`
`kotlin-dsl`
// Matching version in Gradle
kotlin("jvm") version "1.5.31"
}
apply(from = "../common-build.gradle.kts")
tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "11"
}
kotlin {
sourceSets["main"].kotlin.srcDir("../src/main/kotlin")
}
common-build.gradle.kts has everything common to both build scripts which we've figured out how to move to a common location (notably, the KotlinCompile isn't there, later I'll figure out why I can't move that as well):
repositories {
mavenCentral()
}
dependencies {
// Needed to compile Kotlin stuff but not added by the plugin for some reason
"implementation"("org.jetbrains.kotlin:kotlin-scripting-jvm")
}
The plugin script, src/main/kotlin/example.common.gradle.kts, contains:
allprojects {
// Configure something
}
This turns out to be a bug in Gradle's kotlin-dsl plugin.
The workaround is to add the source dirs before applying the plugin.
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
`java-gradle-plugin`
`kotlin-dsl` apply false
// Matching version in Gradle
kotlin("jvm") version "1.5.31"
}
apply(from = "../common-build.gradle.kts")
tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "11"
}
kotlin {
sourceSets["main"].kotlin.srcDir("../src/main/kotlin")
}
// Workaround for https://github.com/gradle/gradle/issues/21052 -
// apply kotlin-dsl plugin last, because it erroneously fetches source dirs eagerly.
apply(plugin = "org.gradle.kotlin.kotlin-dsl")

Kotlin Gradle include runtime when building jar

I'm trying to include the kotlin runtime when building my jar with gradle. I cannot find an update to date thread anywhere online that shows how to do this.
Currently I have tried the following
plugins {
kotlin("jvm") version "1.5.10"
}
repositories {
mavenCentral()
}
tasks.withType<KotlinCompile>().configureEach {
kotlinOptions {
freeCompilerArgs = freeCompilerArgs + "-include-runtime"
jvmTarget = "16"
}
}
This builds the jar fine but doesn't include the runtime like I would expect. How can I configure gradle to do this?
You can use the shadowJar plugin. Add
id("com.github.johnrengelman.shadow") version "7.0.0"
To your plugins, then run ./gradlew shadowJar to create a jar file in build/lib containing the kotlin runtime as well as any of your runtime dependencies

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 }
}

Setting Up a Multiplatform Project

considering Kotlin 1.2 introduced kotlin-platform-common I'm trying to build my first common .class file, so I did the below:
main.kt:
package hello
fun main() {
println("kotlin!")
}
gradle.build:
group 'h'
version 'prn'
buildscript {
ext.kotlin_version = '1.2.0'
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
apply plugin: 'kotlin-platform-common'
repositories {
mavenCentral()
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-common:$kotlin_version"
}
sourceSets {
main.kotlin.srcDirs += 'src/kotlin'
main.resources.srcDirs += 'src/resources'
}
jar {
manifest {
attributes 'Main-Class': 'hello.MainKt'
}
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
}
Then I run gradle build
Q1- I coud not find any .class file generated? how to get it generated and where should I find it?
Q2- I got a .jar file generated, but once I tried to run it, I got an error:
Error: Could not find or load main class hello.MainKt
I tried to run it using the below 2 option, but got the same error for bot:
Option 1:
kotlin -cp <filename>.jar hello.MainKt
Option 2:
java -jar <filename>.jar
Project structure, and errors are as in this pic below:
With Kotlin 1.2, common modules don't generate binaries (like .class files), only metadata files. This is true even if you don't use the expect keyword anywhere.
So you have to create another module which includes something like this:
apply plugin: 'kotlin-platform-jvm'
The JVM modules will also need an expectedBy entry in dependencies for your common module. Plus you'll probably want a corresponding JS module of course.
It's probably best to let IntelliJ create the whole multiplatform project for you and then edit the Gradle files to suit, but this page lists the manual steps: https://kotlinlang.org/docs/reference/multiplatform.html#setting-up-a-multiplatform-project