Setting Up a Multiplatform Project - kotlin

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

Related

Apply local jar-plugin without using maven

I'd like to load my custom plugin from a local jar. The jar file compiles fine and when I check it, the manifest and the plugin class are there.
gradlePlugin {
plugins {
create("asdf") { // <-- I really call it "asdf" in the kts script
id = "asdf"
implementationClass = "pluginTest.TestPlugin"
version = "1.4.0"
}
}
}
The plugin doesn't do anything useful yet as it should be a proof-of-concept to make sure it actually works at all:
class TestPlugin : Plugin<Project> {
override fun apply(project: Project) {
println("Hallo TestPlugin!")
}
}
I then try to use it like this in another project:
buildscript {
repositories {
jcenter()
}
dependencies {
classpath(files("..\\..\\path\\to\\pluginTest.jar"))
}
}
plugins {
id("asdf") version "1.4.0"
}
but it keeps telling me that:
Plugin [id: 'asdf', version: '1.4.0'] was not found in any of the following sources:
What am I missing here? I use Gradle v6.5.
When you have the plugin jar on the classpath, you can't have a version number in the plugin application. I guess this is because you can't have multiple jars with different versions on the classpath in the first place, so specifying a version here doesn't make any sense (except perhaps to validate that you are using the correct one). This won't fix the problem, but it is a start.
To be honest, I don't know why your approach still won't work. The buildscript block is supposed to set up dependencies for that particular script, and that should make the plugin visible to it. It doesn't for some reason.
Perhaps this is a bug or perhaps this is just an undocumented limitation on the use of the plugin {} block. Maybe you could ask over at the Gradle forums or create an issue for it. However, there are workarounds that don't involve publishing to a (local) Maven repository, which I agree can be a bit annoying.
If you use "apply from" instead of "plugins {}", it works. For some reason, the former can see the buildscript classpath whereas the latter can't:
// build.gradle (Groovy DSL)
buildscript {
dependencies {
classpath(files("..\\..\\path\\to\\pluginTest.jar"))
}
}
apply from: "asdf"
Alternatively, move the buildscript plugin from the build.gradle file to the settings.gradle file. This makes is available to the entire build classpath and will make it work with the plugin block:
// settings.gradle (Groovy DSL):
buildscript {
dependencies {
classpath(files("..\\..\\path\\to\\pluginTest.jar"))
}
}
// build.gradle (Groovy DSL)
plugins {
id("asdf")
}
Lastly, just in case you haven't considered it already, you may be able to add the plugin as a composite build. This will create a source dependency to the plugin and has the advantage that transitive dependencies will be carried over (the ones you put in the plugin's own dependency block) and that it will be built automatically if not up-to-date. I use this approach for integration testing my plugins and also sometimes to apply them to my other real projects to test them in a bigger setting before publishing new versions.
Do that with either:
// settings.gradle (Groovy DSL):
includeBuild("..\\..\\path\\to\\plugin")
// build.gradle (Groovy DSL):
plugins {
id("asdf")
}
Or without hard-coding it in the build (so you can dynamically switch between local and published versions):
// build.gradle (Groovy DSL):
plugins {
id("asdf") version "1.4.0" // Version is optional (will be ignored when the command line switch below)
}
// Run with:
./gradlew --include-build "..\\..\\path\\to\\plugin" build
With #BjørnVester's answer I figured it out!
You need to put the buildscript in settings.gradle.kts as it doesn't get executed in the build.gradle.kts even when placed before plugins.
buildscript {
repositories {
flatDir {
dirs("..\\reusable-kotlin\\build\\libs") // <-- folder with jars
}
}
dependencies {
classpath("com.hedev.kotlin:reusable-kotlin:1.4.0")
}
}
But there's a catch! You must use the file-name of the jar in the classpath's name identifier that goes like this:
group:file-name:version
The file gradle will look for will be file-name-version.jar or file-name.jar which you'll see in the error message if you make a mistake (I added the _ on purpose to trigger the error):
Could not resolve all artifacts for configuration 'classpath'.
Could not find com.hedev.kotlin:reusable-kotlin_:1.4.0. Searched in the following locations:
- file:/C:/some/path/reusable-kotlin/build/libs/reusable-kotlin_-1.4.0.jar
- file:/C:/some/path/reusable-kotlin/build/libs/reusable-kotlin_.jar
In order for this to work I also had to add the group property to the plugin itself:
gradlePlugin {
plugins {
create("asdf") {
id = "asdf"
implementationClass = "com.hedev.kotlin.gradle.TestPlugin"
version = "1.4.0"
group = "com.hedev.kotlin"
}
}
}
Finally you can apply it in build.gradle.kts with (no version here):
plugins {
id("asdf")
}

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

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>

How to share Java code between Android and JVM targets (with Kotlin Multiplatform)?

I am trying to share Java code between Android and JVM targets using Kotlin Multiplatform feature (sample project: https://github.com/dmitrykolesnikovich/accessJavaCode-issue)
Simply saying, ":library1" and ":library2" both are Kotlin multiplatform libraries targeting JVM and Android. ":library2" depends on ":library1". They both uses Kotlin and Java. ":library2" is intended to be dependency of 1) Android application and 2) desktop (JavaFX) application. That's why 1) AAR artifact and 2) JAR artifact both are needed (?) - so I use 1) Android target and 2) JVM target for both ":library1" and ":library2".
The problem is that, when I have Java code in ":library1"
public class JavaCode {} // JavaCode.java
And Kotlin code in ":library2" that depends on ":library1"
class AccessJavaCode : JavaCode() // AccessJavaCode.kt
Android target is OK with recognizing Java but JVM target is not:
> Task :library2:compileKotlinJvm FAILED
e: AccessJavaCode.kt: (3, 38): Unresolved reference: JavaCode
In gradle config I define two plugins: kotlin-multiplatform and com.android.library:
apply plugin: "kotlin-multiplatform"
apply plugin: "com.android.library"
kotlin {
targets {
jvm()
android()
}
sourceSets {
jvmMain {
dependencies {
api kotlin("stdlib-common")
api kotlin("stdlib-jdk8")
}
}
androidMain {
dependsOn jvmMain
}
}
}
android {
compileSdkVersion 28
sourceSets {
main {
java.srcDirs += "src/jvmMain/kotlin" // Android target recognizes Java with this
manifest.srcFile "src/androidMain/AndroidManifest.xml"
}
}
}
I am pretty sure it’s something simple with my gradle file. Many thanks for your help guys.
~~~~ EDIT ~~~~
Another workaround the issue without losing the ability to generate an android archive .aar for the library1 would be to make a new version of this same library depending on precompiled artefacts of a splitted version of the original library1.
So you would end up with a multi-module gradle project, something like this:
library1-jvm with java plugin enabled
library1-android with android plugin enabled
library1 which will depends on prebuilt library1-jvm.jar and library1-android.aar
You could use whatever you prefer to publish those artefacts, but a local maven repo should work just fine!
That would mean replacing:
kotlin {
targets {
jvm()
android()
}
sourceSets {
jvmMain {
dependencies {
api kotlin("stdlib-common")
api kotlin("stdlib-jdk8")
}
}
androidMain {
dependsOn jvmMain
}
}
}
with:
kotlin {
targets {
jvm()
android()
}
sourceSets {
jvmMain {
dependencies {
api kotlin("stdlib-common")
api kotlin("stdlib-jdk8")
api "com.company:library-jvm:1.0.0"
}
}
androidMain {
dependsOn jvmMain
dependencies {
api "com.company:library-android:1.0.0"
}
}
}
}
That way, you don't need the java plugin at all in the final library1, because all the java code will already be built in a separate step.
Hence library1 could keep both the JVM and Android targets
~~~~~~~~~~~~~
In order to fix your issue, you need to:
Split your build.gradle configuration in order to have one config per library, this will be needed because you cannot enable the java plugin and the android at the same time for the same Gradle project, or you will end up with the following Error: The 'java' plugin has been applied, but it is not compatible with the Android plugins
Enable the java plugin in your library1 project if you want your JVM target to recognize your Java source files.
The Java source files need to be placed in the sibling directories java of the kotlin source roots.
More infos: Kotlin docs for java-support-in-jvm-targets
I also created a pull request solving your issue.
The downside of this approach is that you will not be able to generate an android archive .aar for the library1, but I guess using the java archive .jar in your android project should not be a problem at all.
library1/build.gradle:
apply plugin: "kotlin-multiplatform"
kotlin {
jvm {
withJava()
}
sourceSets {
jvmMain {
dependencies {
api kotlin("stdlib-common")
api kotlin("stdlib-jdk8")
}
}
}
}
library2/build.gradle:
apply plugin: "kotlin-multiplatform"
apply plugin: "com.android.library"
kotlin{
jvm()
android()
sourceSets {
jvmMain.dependencies {
api project(":library1")
}
androidMain {
dependsOn jvmMain
}
}
}
android {
compileSdkVersion 28
sourceSets {
main {
java.srcDirs += "src/jvmMain/kotlin"
manifest.srcFile "src/androidMain/AndroidManifest.xml"
}
}
}

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

How to build kotlinx.coroutines in Kotlin/Native (test version 0.23.4-native-1)

This question is a continuation of this thread:
https://github.com/Kotlin/kotlinx.coroutines/issues/246#issuecomment-407023156
I am trying to use org.jetbrains.kotlinx:kotlinx-coroutines-core-native:0.23.4-native-1 in a Kotlin/Native project targeting iOS.
build.gradle:
buildscript {
repositories {
mavenCentral()
maven { url "https://dl.bintray.com/jetbrains/kotlin-native-dependencies" }
}
dependencies {
classpath 'org.jetbrains.kotlin:kotlin-native-gradle-plugin:0.8'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.2.51"
}
}
apply plugin: 'kotlin-platform-native'
repositories {
jcenter()
mavenCentral()
maven { url "https://kotlin.bintray.com/kotlinx" }
}
sourceSets {
main {
component {
target 'ios_arm32', 'ios_arm64', 'ios_x64'
outputKinds = [KLIBRARY]
}
}
}
dependencies {
expectedBy project(':common')
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core-native:0.23.4-native-1"
}
The kotlinx:kotlinx-coroutines-core-native dependency doesn't seem to work, as the produces build errors like:
error: unresolved reference: coroutines
import kotlinx.coroutines.experimental.*
^
If I manually include the artifact dependencies such as org.jetbrains.kotlinx:kotlinx-coroutines-core-native_release_ios_x64:0.10.3-native, then I get a complier exception:
exception: java.lang.IllegalStateException: Could not find "atomicfu-native"
This error persists, even if I also add org.jetbrains.kotlinx:atomicfu-native:0.10.3-native dependency.
Here is a list of things to check for (I have been through this, and finally made it work) :
Enable Gradle metadata. It's required to retrieve the coroutines dependencies. To do so, add this line in your "settings.gradle" file, after all the "include" instructions :
enableFeaturePreview('GRADLE_METADATA')
use gradle 4.7 (newer version are incompatible with the meta data of the current coroutines library, they require something with 0.4 version and the current published one uses 0.3)
In the iOS module :
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core-native:0.23.4-native-1"
In your common module :
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:0.23.4"
If you have a js module, it may fail due to the gradle metadata feature. You can fix it by adding this before each of your "repositories" blocks (https://github.com/srs/gradle-node-plugin/issues/301)
repositories.whenObjectAdded {
if (it instanceof IvyArtifactRepository) {
metadataSources {
artifact()
}
}
}
Hope this will be enough !