This page looks best with JavaScript enabled

Cross Compilation Adventures with Kotlin/Native

 ·  โ˜• 5 min read  ·  โœ๏ธ Nishant
Cross Compilation Adventures Kotlin Native

This post is part of the series Cross Compilation Adventures. Read the first part here to know about the premise of this series.

TLDR; I want to build cross platform CLI utility tools. Being able to cross compile from my laptop is a required.

I am an Android Engineer by profession, so my goto language is Kotlin. While looking at other programming languages, I also wanted to take a look at building a CLI tool using Kotlin. Thankfully, there exists Kotlin/Native that does exactly the same.

From the official website:

  • Kotlin is a modern but already mature programming language designed to make developers happier. It’s concise, safe, interoperable with Java and other languages, and provides many ways to reuse code between multiple platforms for productive programming.
  • The Kotlin/Native compiler is available for three operating systems: macOS, Linux, and Windows. It can be accessed through the command line or as part of the standard Kotlin distribution, which can be downloaded from GitHub Releases. The compiler supports various targets, including Linux, macOS, iOS, and others. Additionally, it allows cross-platform compilation, enabling developers to compile code for a different platform than the one they are using.

Sounds good! Let’s dive into building a very basic CLI tool.

You will build the same example as in the last post.

A good example to showcase would be to build a CLI tool that can convert from ยฐC to F and vice versa. Our tool will take an input for value and the unit to be converted to, then output would be converted temprature value.

NOTE: I am using macOS (M2 Pro, Apple Silicon), so the instructions follow through using that only. However the steps should work on all platform with little tweaks.

First we need to install kotlin and kotlin-native. Open your Terminal app and execute the command

1
2
brew install kotlin
brew install --cask kotlin-native

Once installed, you should have access to kotlinc-native compiler in your Terminal. If not restart your session or open a new Terminal window so it is loaded in the PATH. Follow through next steps

  • Create a file named run.kt.

    1
    
    touch run.kt
    
  • Add the below code to the run.kt file and save the file.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    
    fun celsiusToFahrenheit(celsius: Double): Double {
        return celsius * 9 / 5 + 32
    }
    
    fun fahrenheitToCelsius(fahrenheit: Double): Double {
        return (fahrenheit - 32) * 5 / 9
    }
    
    fun main(args: Array<String>) {
        if (args.size != 2) {
            println("Usage: ./run.kexe <value> <unit_to_convert_to>")
            return
        }
    
        val value = args[0].toDoubleOrNull()
        val unit = args[1].uppercase()
    
        if (value == null) {
            println("Invalid temperature value.")
            return
        }
    
        val convertedTemperature = when (unit) {
            "C" -> celsiusToFahrenheit(value)
            "F" -> fahrenheitToCelsius(value)
            else -> {
                println("Invalid unit. Please use C or F.")
                return
            }
        }
    
        println("Converted temperature: $convertedTemperature${if (unit == "C") " ยฐF" else " ยฐC"}")
    }
    

    I am not going to explain this code as it is simple and self explanatory.

    To understand and learn the language you can use Learn X in Y minutes: Kotlin ๐Ÿš€

  • Now to compile, execute the kotlinc-native compiler with -oargument with the name of output file and the run.kt source file:

    1
    
    kotlinc-native run.kt -o run
    

    You should now have a binary generated in the same directory with the same name as the kt file i.e run.kexe

    run

    NOTE: I use dust CLI tool to list files in directory with their sizes.
    TIP: You can generate an optimized binary by passing -opt flags at the time of compilation. i.e kotlinc-native run.kt -o run -opt. Result is just a smaller binary.

    run optimized

  • Time to execute our generated run.kexe binary file:

    1
    2
    
    โฏ ./run.kexe
    Usage: ./run.kexe <value> <unit_to_convert_to>
    

    Didn’t work ๐Ÿ™„, but we have a helpful message stating how to use the CLI tool ๐Ÿ˜Š

    1
    2
    
    โฏ ./run.kexe 49 C                         
    Converted temperature: 120.2ยฐC
    

Done! That was a super quick intro to working with Kotlin/Native Compiler and Kotlin Language in less than 5 mins ๐Ÿ˜…

But we aren’t done yet. This generated binary would work on *nix systems. I mentioned earlier that I would like to have cross-(platform + compilation).

Kotin/Native allows to do that easily. Since we already have *nix compatible binary i.e Linux and macOS are sorted for us. We need to cross compile to a format that Windows understands i.e exe/executable. Let’s do that next.

  • First install the mingw-w64 toolchain using homebrew for macOS:

    1
    
    brew install mingw-w64
    
  • Compile the run.kt file with -target mingw flag:

    1
    
    kotlinc-native run.kt -o run.exe -target mingw
    

    You should now have a .exe binary generated in the same directory with the same name as the kt file i.e run.exe

    run.exe

    TIP: You can generate an optimized binary by passing -opt flags at the time of compilation. i.e kotlinc-native run.kt -o run.exe -target mingw -opt. Result is just a smaller binary.

    run.exe optimized

    NOTE: In order to run this .exe file, you need to either execute this on Windows directly or if on a *nix system then make use of Wine.

Thats it. I think Kotlin/Native and Kotlin Language pretty much does what I wanted to get out of it:

โœ… Generate cross-platform binaries
โœ… Can cross-compile to platforms
โœ… Easy syntax, so maintainable code

All check boxes ticked is good ๐Ÿ˜Š

The only drawback that I saw was that the binary size (even after using the -opt flag) was considerably bigger than when I generated the same using Nim Lang. But this is not a big concern for my usecase. Being able to use a programming language that I am highly familiar with overshadows the size drawback for my usecase atleast.

BONUS: While my requirement isn’t about compiling to other platforms, but Kotlin/Native is quite capable such as compiling for Android, iOS, watchOS, tvOS, etc.

I’ll be trying this approach of evaluating more languages in the future. You can find the code for this post here.

Share on
Support the author with

Nishant Srivastava
WRITTEN BY
Nishant
๐Ÿ‘จโ€๐Ÿ’ป Android Engineer/๐Ÿงข Opensource enthusiast

What's on this Page