Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Kotlin cross-compilation for multiple targets in single build run

Default Kotlin native project gradle configuration looks like:

plugins {
    kotlin("multiplatform") version "1.4.10"
}
group = "org.example"
version = "1.0-SNAPSHOT"

repositories {
    mavenCentral()
}
kotlin {
    val hostOs = System.getProperty("os.name")
    val isMingwX64 = hostOs.startsWith("Windows")
    val nativeTarget = when {
        hostOs == "Mac OS X" -> macosX64("native")
        hostOs == "Linux" -> linuxX64("native")
        isMingwX64 -> mingwX64("native")
        else -> throw GradleException("Host OS is not supported in Kotlin/Native.")
    }

    nativeTarget.apply {
        binaries {
            executable {
                entryPoint = "main"
            }
        }
    }
    sourceSets {
        val nativeMain by getting
        val nativeTest by getting
    }
}

Such configuration can produce binaries only for single target. How can be the configuration adjusted so that single build will produce 3 binary for all mentioned targets: Windows, Linux and MacOS?

like image 894
czerny Avatar asked Oct 16 '25 20:10

czerny


2 Answers

You can just set a number of targets, and then running the assemble task will produce binaries for all available on your host machine. It is important because one cannot create a binary for macOS anywhere but on macOS host, Windows target also has some complex preparations(see this issue). Also, there could be some problem with source sets - if their contents are identical, maybe it worth connecting them as in the example below:

kotlin {
    macosX64("nativeMac")
    linuxX64("nativeLin")
    mingwX64("nativeWin")

     targets.withType<org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget> {
        binaries {
            executable {
                entryPoint = "main"
            }
        }
    }

    sourceSets {
        val nativeMacMain by getting //let's assume that you work on Mac and so put all the code here
        val nativeLinMain by getting {
            dependsOn(nativeMacMain)
        }
    }
}
like image 90
Artyom Degtyarev Avatar answered Oct 18 '25 17:10

Artyom Degtyarev


Here is a build.gradle.kts that configures Gradle to build a Kotlin Multiplatform project with arm64 and x86-64 targets on Linux, arm64 and x86-64 targets on macOS, and a single x86-64 (mingw) target on Windows.

plugins {
    val kotlinVersion = "1.9.21"
    kotlin("multiplatform") version kotlinVersion
}

group = "org.example"
version = "1.0.0-SNAPSHOT"

repositories {
    mavenCentral()
}

kotlin {
    // Initialize all target platforms.
    // Incompatible targets will be automatically skipped with a warning;
    // we suppress such warnings by adding a line to gradle.properties:
    // kotlin.native.ignoreDisabledTargets=true
    val linuxArm64 = linuxArm64()
    val linuxX64 = linuxX64()
    val macosArm64 = macosArm64()
    val macosX64 = macosX64()
    val windows = mingwX64("windows")

    // Configure which native targets to build, based on current platform.
    val hostOs = System.getProperty("os.name")
    val nativeTargets = when {
        hostOs == "Linux" -> listOf(linuxArm64, linuxX64)
        hostOs == "Mac OS X" -> listOf(macosArm64, macosX64)
        hostOs.startsWith("Windows") -> listOf(windows)
        else -> throw GradleException("Host OS is not supported in Kotlin/Native.")
    }

    // Define dependencies between source sets.
    // We declare an intermediate source set called posix, which
    // the Linux and macOS sources extend, but Windows does not.
    // For further details, see:
    // https://kotlinlang.org/docs/multiplatform-advanced-project-structure.html#declaring-custom-source-sets
    sourceSets {
        val posixMain by creating {
            dependsOn(commonMain.get())
        }
        val macosArm64Main by getting {
            dependsOn(posixMain)
        }
        val macosX64Main by getting {
            dependsOn(posixMain)
        }
        val linuxArm64Main by getting {
            dependsOn(posixMain)
        }
        val linuxX64Main by getting {
            dependsOn(posixMain)
        }
    }

    // Build the binaries for all activated native targets.
    nativeTargets.forEach {
        it.apply {
            binaries {
                executable {
                    entryPoint = "main"
                }
            }
        }
    }
}

It assumes the following directory structure for sources:

src
├── commonMain
│   ├── kotlin
├── linuxArm64Main
│   └── kotlin
├── linuxX64Main
│   └── kotlin
├── macosArm64Main
│   └── kotlin
├── macosX64Main
│   └── kotlin
├── posixMain
│   └── kotlin
└── windowsMain
    └── kotlin

where src/posixMain/kotlin is an area to put intermediate sources consumed by both Linux and macOS targets; I find is useful since Kotlin Native's platform.posix library functions largely identically across those two platforms. (In my experience, platform.posix is also very close when used with mingw, but there are more differences that can be problematic, so I like to implement my Windows-specific code using the platform.windows library instead.)

I don't have an answer for cross-compiling all platforms at once, since as Artyom Degtyarev mentioned, there are extra challenges with that. But you can cross-compile CPU architecture using a structure like the above, building both arm64 and x86-64 for Linux and macOS, which allows to target more platforms using a CI pipeline such as GitHub Actions with an x86-64-only matrix configuration, and then assemble the final binaries into a single output artifact at the end.

like image 33
ctrueden Avatar answered Oct 18 '25 19:10

ctrueden



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!