DEV Community

Cover image for Testing Compose Desktop apps: preparations
Thomas Künneth
Thomas Künneth

Posted on

3

Testing Compose Desktop apps: preparations

Welcome to the fifth article in a series of tips and tricks about Compose Multiplatform. The content is based on a sample app called CMP Unit Converter. It runs on Android, iOS, and the Desktop. As its name suggests, you can convert between various units and scales. While this may provide some value, the main goal of the app and this series is to show how to use Compose Multiplatform and a couple of other multiplatform libraries while focusing on platform integration. This time, we look at how to test Compose Multiplatform apps on one particular target: the desktop.

What is the Desktop anyway?

Kotlin Multiplatform is based on the idea of compiling for different targets. Targets are defined by an operating system or platform and, for native targets, a processor architecture. In the build.gradle.kts file inside the composeApp module, you can see a reference to the Desktop target inside the kotlin {} block: the invocation of jvm("desktop"). So, when referring to the Desktop, we mean the Java platform and its components.

Java's astonishing success lies in the ability to write code that can run on several operating systems; among others, macOS, Windows, Linux, AIX and z/OS. The key to this ability is the Java Virtual Machine, a runtime environment that executes programs represented in a machine language called bytecode. The JVM integrates the program into the host operating system and provides means for communicating with the system, and for using its resources.

The JVM allows us to run bytecode programs on any operating system or platform the JVM is available on. Compose Multiplatform narrows this down a little. Version 1.8.0 of the JetBrains framework supports macOS, Windows, Windows on ARM64, Linux, and Linux on ARM64. That's three operating systems. Each of them has - or had - support for Intel/AMD and ARM architectures.

Let that trickle in for a while.

Does that mean I should test my Compose Desktop app on each of these?

Glad you are asking. Well, in a way it's your choice, depending on how many users your app is targeting. At the time of writing, Apple still officially supports some Intel-based Macs with software updates. Therefore, if your app targets macOS, you may want to provide installation packages for both Intel and Apple Silicon Macs. And if you do that, you probably also want to test them.

The same is true for Windows. While Windows has been an Intel/AMD stronghold for decades, we have seen a significant increase in ARM-powered PCs in recent years. Many Copilot+ PCs run Windows for ARM, so, if your app targets Windows, you likely want your app to also run on them.

Finally, Linux. While I don't have statistics regarding the number of Linux for ARM installations, it feels safe to say that there aren't many. Therefore, if you want to provide your app for Linux, you might be tempted to focus on Intel/AMD. On the other hand, any additional user can make a difference, so you need to balance both costs and benefits of providing a Linux on ARM installation archive.

Before we look at what happens along the way of creating an installation archive, let's focus a little more on operating systems and processor architectures. Taking into account that the JVM is the runtime environment for your app, why would you need to worry about processor architectures? Isn't bytecode a machine language in its own right? And isn't the JVM isolating your app from the rest of the system? While the JVM executes the bytecode (or compiles it ahead of time to native code so that the physical processor can run it), it does not isolate your app from the rest of the system, but rather integrates it. For example, you can easily do native calls using JNI - which certainly depends on the processor architecture. Even if your app does no native calls directly, referenced libraries might. Jetpack Compose relies on Skia, after all.

Consequently, you should test your app on at least the primary processor architecture of an operating system or platform. If you provide versions of your app for additional processor architectures, you certainly need to test these, too. I'll return to this later.

How to create an installation archive

Kotlin and Compose Multiplatform projects heavily rely on Gradle. As you can see in the following screenshot, you can choose from quite a few tasks to create native installation archives.

Screenshot of the Gradle tool window in IntelliJ

Some of them can be used only on the platform you want to create an installation archive for, for example packageDmg on macOS, and packageMsi on Windows. Here's why: under the hood, the jpackage tool is executed. It invokes other executables from the Java Development Kit, like jlink, but also some native installer tools like the WiX Toolset on Windows (which is not supported on macOS and Linux).

jlink creates a minimal, application-specific Java runtime environment by including only the necessary modules from a full JDK. But which JDK is used? While there is a corresponding command line parameter, in a Kotlin Multiplatform project you can specify the JDK like this:

compose.desktop {
    application {
        mainClass = "your.main.Class"
        javaHome = 
    }
}
Enter fullscreen mode Exit fullscreen mode

If you don't set the javaHome property explicitly, the jpackage tool which is invoked by, for example, the createMsi task, will typically use the JDK that Gradle itself is using.

Let's recap: to have your app using a particular Java Runtime, a straightforward way to achieve this is to set the javaHome property in the build.gradle.kts file of your composeApp module.

At this point, you may be asking yourself why this is important. Kindly recall that an operating system may support more than one processor architecture. You would then have two different JDKs, each of which you could easily select using the technique described above. Here's an example: Windows on ARM has a built-in emulation layer that allows the seamless execution of Intel/AMD-based apps although the primary processor architecture is ARM64. Consequently, you can install the JDK for ARM64 and Intel/AMD at the same time. To build your app, you need to specify which one to use.

macOS running on Apple Silicon and Linux on ARM also allow the execution of Intel/AMD-based apps, but through different mechanisms and with varying degrees of compatibility and performance. Apple uses a translation layer called Rosetta 2. On Linux you would likely be using QEMU User-Mode Emulation. ...or something else that I will be mentioning a little later...

How straightforward - or complicated - it is to install two JDKs with the same version number but a different processor architecture depends on the underlying operating system. On macOS, Java Development Kits are usually put inside /Library/Java/JavaVirtualMachines. Each version resides in a subdirectory, for example amazon-corretto-17.jdk. Since the name does not contain architecture-related information, you need to put the second one somewhere else, which implies that without further modifications, macOS won't know it's there. If that second version is only referenced in the build.gradle.kts file, that certainly does not matter.

Wrap-up

This has been quite a ride, hasn't it? Let's recap. Your developer machine is running an operating system that may or may not be able to run apps targeting a different processor architecture:

  • macOS on Intel hardware can only run apps made for macOS on Intel
  • macOS on Apple Silicon can run ARM64 and Intel apps (through Rosetta 2)
  • Windows on Intel/AMD can only run apps made for Intel/AMD
  • Windows on ARM can run both Intel/AMD (through emulation) and ARM64 apps natively
  • Linux on Intel/AMD can only run apps made for Intel/AMD
  • Linux on ARM can natively run ARM64 apps. Running Intel/AMD apps is possible through emulation, but this requires specific setup and may have performance implications

Regardless of the processor architecture, to test software made for a particular operating system, you need a box that runs this OS. That box can either be a physical or virtual one. Virtualization on Windows for Intel/AMD has come a long way. There are quite a few well-known products that allow you to virtualize both Windows and Linux. On Linux, the most common hypervisor is KVM (Kernel-based Virtual Machine). It allows you to easily and seamlessly virtualize both Windows and Linux. What about macOS - can it be virtualized, too? While technically possible, macOS must only be used on an Apple-branded computer.

On several occasions, I mentioned that, using Parallels Desktop, you can easily test your Compose Desktop app on macOS, Linux and Windows. Now that I have laid the theoretical groundwork, we'll get practical in the next installment of this series.

AWS GenAI LIVE image

Real challenges. Real solutions. Real talk.

From technical discussions to philosophical debates, AWS and AWS Partners examine the impact and evolution of gen AI.

Learn more

Top comments (0)

Billboard image

Create up to 10 Postgres Databases on Neon's free plan.

If you're starting a new project, Neon has got your databases covered. No credit cards. No trials. No getting in your way.

Try Neon for Free →