-
Notifications
You must be signed in to change notification settings - Fork 0
Compiling for "external" clients
In the end, the code you wrote in Kotlin Multiplatform will be launched somewhere: on an Android or iOS device, on local JVM, in web-browser, etc. In this case, we're concerned about launching code on an Android or iOS device.
This process roughly can be described as two steps:
- Assembling the raw platform-specific output of the Kotlin Compiler
- Transforming (usually packaging or linking) that raw output into a form, consumable by "external" clients
There are four important things to understand about platform-specific compilations of Kotlin Multiplatform:
- Common sources of the current "module" are passed as sources.
Platform-specific compilations don't use some binary output of common-sources compilation. This is an oddity in the design of Kotlin Multiplatform which holds at the time of writing this (Kotlin 1.7.20). There are plans to change that to a more conventional compilation scheme (where first common sources are compiled into some binary form - e.g., .klib
, - and then the platform compiler receives that .klib
as an input).
Example. Let's look at AssembleJvm
task called on TransitiveDependency
. You can see that the free args (sources to be compiled) are looking like this:
<...>/TransitiveDependency/src/jvmMain
<...>/TransitiveDependency/src/commonMain
Note how commonMain
-sources are passed just alongside jvmMain
-sources.
- Produced artifact is "fat" in the sense that it includes a common API of the current "module",
Convenient way to think about this is that we provide a platform-specific view on the Kotlin Multiplatform code. This transforms the
common code in an interesting way: not only all expect
s are substituted with actual
s, but also some Kotlin-stdlib declarations are substituted with their platform counterparts (if any).
Example. Let's again look at the output of AssembleJvm
called on TransitiveDependency
:
barebones-kotlin-multiplatform/buildInfra/output/TransitiveDependency/assembly/jvmMain/org/jetbrains/kotlin/transitive/
|- JustCommonKt.class
|- TransitiveJvmKt.class
L TypealiasExpansion.class
First of all, we can see that the file commonMain/justCommon.kt
with the simple function fun justCommon(): String
got compiled into respective .class-file. This illustrates the point of platform-specific artifacts being "fat" and including the common API.
Second, let's observe that the file commonMain/TransitiveCommon.kt
doesn't have a corresponding class-file. It's because the only declaration in this file is expect class TransitiveCommon
, which got replaced by actual class TransitiveCommon
, declared in transitiveJvm.kt
(and thus it placed in TransitiveJvmKt.class
). So, as we see, expect
-classes are replaced with actual
s
Finally, let's illustrate that some Kotlin-primitives are replaced with their platform counterparts as well. Let's look at bytecode of JustCommonKt
:
...
public static final java.lang.String justCommon()
...
Just as a reminder, the function was initially declared in common code as fun justCommon(): String
, where String
is obviously a kotlin.String
(as you wouldn't have a java.lang.String
in common code). We can see that this type from common code was replaced by the platform-specific counterpart for kotlin.String
during JVM compilation.
If you think about it, this is a pretty natural process: the purpose of those artifacts is to be exposed to "external" clients that don't know anything about Kotlin Multiplatform, so it makes perfect sense to put all common API there as well, and replace all traces of Kotlin Multiplatform with platform-specifics
- Platform artifacts compile against similar platform artifacts
It's pretty self-explanatory. Let's look at the example of AssembleJvm
in DirectDependency
:
-classpath <...>/buildInfra/output/TransitiveDependency/packed/jvm.jar
:<... stdlib declarations ...>
So, we're compiling platform-specific output against the (packed) similar platform-specific output of dependencies.
The raw output of K/JVM compilation consists of the usual JVM .classfiles and .kotlin_module
file in META-INF
folder. This file, along with @kotlin.Metadata
annotation in the .classfiles, carries Kotlin-specific information that is not expressible by mechanisms built-in into .classfiles (examples: Kotlin-specific modifiers like suspend
or inline
)
This output is usually distributed in form of the traditional .jar
-file. The transformation, therefore, is the trivial process of packing .classfiles into .jar
-file, and actually, it's not even that necessary, as external JVM clients can consume .classfiles just fine. For example, Kotlin Gradle Plugin uses exactly raw .classfiles when handling local dependencies from one local Kotlin Gradle Project to another Kotlin Gradle Project.
The raw output of K/Native compilation is the Kotlin-specific format called .klib
. It contains Kotlin-invented binary format of representing the code, called Kotlin IR.
As this format is Kotlin-specific, the process of transforming this output into something consumable by external Native clients (Xcode, for example), is much less trivial than .jar
-ing files. This process is called "linkage", see the LinkNative
tasks.