Skip to content

sureshg/kotlin-mpp-playground

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

🎨 Kotlin Multiplatform Playground!

GitHub Workflow Status OpenJDK Version Kotlin release Maven Central Version Ktor Compose MP Style guide

This repo shows a Gradle multi-project build structure that uses the Kotlin Multiplatform to build a JVM, JS, Desktop and Compose Web (wasm) applications.

Install OpenJDK EA Build

# Mac OS
$ curl -s "https://get.sdkman.io" | bash
$ sdk i java 25.ea-open
$ sdk u java 25.ea-open

Build & Run

$ ./gradlew build [-Pskip.test]

# Run the app
$ ./gradlew :backend:jvm:run

# Publish to local repo
$ ./gradlew buildAndPublish

Publishing

Push a new tag to trigger the release workflow and publish the artifacts. That's it πŸŽ‰. The next version will be based on the semantic version scope (major, minor, patch)

$ ./gradlew pushSemverTag "-Psemver.scope=patch"

# To see the current version
# ./gradlew v

# Print the new version
# ./gradlew printSemver "-Psemver.scope=patch"
Multiplatform Targets

JVM

  • Build and Run

    $ ./gradlew :backend:jvm:run
    $ ./gradlew :backend:jvm:build
    $ ./gradlew :backend:jvm:jdeprscan
    $ ./gradlew :backend:jvm:printModuleDeps
    $ ./gradlew :shared:jvmRun
    
    # Benchmark
    $ ./gradlew :benchmark:benchmark
  • GraalVM Native Image

    $ sdk u java graalvm-ce-dev
    $ ./gradlew :backend:jvm:nativeCompile
    $ backend/jvm/build/native/nativeCompile/jvm
    
    # To generate the native image configurations
    $ ./gradlew :backend:jvm:run -Pagent
    $ curl http://localhost:8080/shutdown
    $ ./gradlew :backend:jvm:metadataCopy
    
  • Containers

    $ docker run \
             -it \
             --rm \
             --pull always \
             --workdir /app \
             --publish 8080:8080 \
             --publish 8081:8081 \
             --name kotlin-mpp-playground \
             --mount type=bind,source=$(pwd),destination=/app,readonly \
             openjdk:25-slim /bin/bash -c "printenv && nohup jwebserver -b 0.0.0.0 -p 8081 -d / & backend/jvm/build/libs/jvm-app"
    
     $ ./gradlew :backend:jvm:jibDockerBuild
     $ docker run -it --rm --name jvm-app -p 8080:8080 -p 9898:9898 sureshg/jvm
     $ docker stats
  • OpenTelemetry

     # Install otel-desktop-viewer or Jaeger
     $ brew tap CtrlSpice/homebrew-otel-desktop-viewer
     $ brew install otel-desktop-viewer
     $ otel-desktop-viewer
    
    
     # or run the Jaeger collector
     $ docker run -it --rm --pull=always \
                  -e COLLECTOR_OTLP_ENABLED=true \
                  -p 4317:4317 \
                  -p 16686:16686 \
                  jaegertracing/all-in-one:latest
     $ open http://localhost:16686
    
     # Run otel tui
     $ brew install ymtdzzz/tap/otel-tui
     $ otel-tui
    
     # Run the app
     $ docker run -it --rm \
                  --name jvm \
                  -p 8080:8080 \
                  -p 9898:9898 \
                  -e OTEL_JAVAAGENT_ENABLED=true \
                  -e OTEL_TRACES_EXPORTER="otlp" \
                  -e OTEL_EXPORTER_OTLP_PROTOCOL="grpc" \
                  -e OTEL_EXPORTER_OTLP_ENDPOINT="http://host.docker.internal:4317" \
                  -e OTEL_DROP_SPANS="/swagger" \
                  sureshg/jvm:latest
     $ curl -v -X GET http://localhost:8080/trace
    
     # Change/Reset log level
     $ curl -v -X POST http://localhost:8080/loglevel/dev.suresh.http/debug
     $ curl -v -X POST http://localhost:8080/loglevel/reset
  • JVM Agents

    # Normal agent with Launcher-Agent-Class
    $ ./gradlew :backend:agent:jfr:build
    $ backend/agent/jfr/build/libs/jfr-app
    
    # Custom OpenTelemetry agent
    $ ./gradlew :backend:agent:otel:build
  • AOT Linking

    # Training Run
    $ java --enable-preview \
           -XX:+UnlockExperimentalVMOptions \
           -XX:+UseCompactObjectHeaders \
           -XX:AOTMode=record -XX:AOTConfiguration=app.aotconf \
           -jar backend/jvm/build/libs/jvm-all.jar
    
    # Create AOT archive
    $ java --enable-preview \
       -XX:+UnlockExperimentalVMOptions \
       -XX:+UseCompactObjectHeaders \
       -XX:AOTMode=create -XX:AOTConfiguration=app.aotconf -XX:AOTCache=app.aot \
       -jar backend/jvm/build/libs/jvm-all.jar
    
    # Run with AOT
    $ java --enable-preview \
       -XX:+UnlockExperimentalVMOptions \
       -XX:+UseCompactObjectHeaders \
       -XX:AOTCache=app.aot \
       -jar backend/jvm/build/libs/jvm-all.jar
  • Tests

    $ ./gradlew :backend:jvm:test -PktorTest
    $ ./gradlew :backend:jvm:test -Pk8sTest
    $ ./gradlew :backend:jvm:jvmRun -DmainClass=dev.suresh.lang.SysCallKt --quiet
  • Binary Compatibility

    $ ./gradlew :backend:security:apiDump
    $ ./gradlew :backend:security:apiCheck

Wasm/JS

$ ./gradlew :web:jsBrowserProductionRun -t
$ ./gradlew :web:wasmJsBrowserProductionRun -t
$ ./gradlew kotlinUpgradePackageLock

# Kobweb
$ kobweb run -p compose/web
$ ./gradlew :compose:html:kobwebStart -t
$ ./gradlew :compose:html:kobwebStop

Native

$ ./gradlew :backend:native:build
$ find backend/native/build/bin -type f -perm +111 -exec ls -lh {} \; | awk '{print $9 ": " $5}'

# Arch specific binaries
$ ./gradlew :backend:native:macosArm64Binaries
$ ./gradlew :backend:native:macosX64Binaries
$ ./gradlew :backend:native:macOsUniversalBinary

# Native container image
$ ./gradlew :backend:native:jibDockerBuild
$ docker run -it --rm --name native-app sureshg/native

# Debug distroless image
# docker run -it --entrypoint=sh gcr.io/distroless/cc-debian12:debug

# Test linux binary on ARM64 MacOS
$ ./gradlew :backend:native:linuxArm64Binaries
$ docker run  \
         -it \
         --rm \
         --publish 8080:80 \
         --mount type=bind,source=$(pwd),destination=/app,readonly \
         debian:stable-slim
  # /app/backend/native/build/bin/linuxArm64/releaseExecutable/native.kexe
  # libtree -v /app/backend/native/build/bin/linuxArm64/releaseExecutable/native.kexe

# Build native binaries on container
$ docker run \
         --platform=linux/amd64 \
         -it \
         --rm \
         --pull always \
         --workdir /app \
         --name kotlin-native-build \
         --mount type=bind,source=$(pwd),destination=/app \
         --mount type=bind,source=${HOME}/.gradle,destination=/root/.gradle \
         openjdk:25-slim /bin/bash
# apt update && apt install libtree tree
# ./gradlew --no-daemon :backend:native:build
#  backend/native/build/bin/linuxX64/releaseExecutable/native.kexe

Compose

# Compose Desktop
$ ./gradlew :compose:cmp:runDistributable
$ ./gradlew :compose:cmp:packageDistributionForCurrentOS
$ ./gradlew :compose:cmp:packageReleaseUberJarForCurrentOS
$ ./gradlew :compose:cmp:suggestModules

# Compose Web
$ ./gradlew :compose:cmp:wasmJsBrowserProductionRun -t

# Compose multiplatform tests
$ ./gradlew :compose:cmp:allTests
$ ./gradlew :compose:cmp:jvmTest

Publishing

$ ./gradlew publishAllPublicationsToLocalRepository

# Publishing to all repo except Maven Central
$ ./gradlew buildAndPublish

# Maven Central Publishing
# https://central.sonatype.org/publish/publish-portal-gradle/#alternatives
# https://vanniktech.github.io/gradle-maven-publish-plugin/central/#in-memory-gpg-key
$ gpg --export-secret-keys --armor XXXXXXXX | grep -v '\-\-' | grep -v '^=.' | tr -d '\n'
# OR
$ gpg --export-secret-keys --armor XXXXXXXX | awk 'NR == 1 { print "SIGNING_KEY=" } 1' ORS='\\n'

$ export ORG_GRADLE_PROJECT_mavenCentralUsername=<Username from https://central.sonatype.com/account>
$ export ORG_GRADLE_PROJECT_mavenCentralPassword=<Token from https://central.sonatype.com/account>
$ export ORG_GRADLE_PROJECT_signingInMemoryKeyId=<GPG Key ID>
$ export ORG_GRADLE_PROJECT_signingInMemoryKeyPassword=<Password>
$ export ORG_GRADLE_PROJECT_signingInMemoryKey=$(gpg --export-secret-keys --armor ${ORG_GRADLE_PROJECT_signingInMemoryKeyId} | grep -v '\-\-' | grep -v '^=.' | tr -d '\n')

# For aggregated publication (preferred) to Central
$ ./gradlew publishAggregatedPublicationToCentralPortal

# For all publications (separate publications)
$ ./gradlew publishAllPublicationsToCentralPortal

Misc

# Dependency Insight
$ ./gradlew dependencies
$ ./gradlew :shared:dependencies --configuration testRuntimeClasspath

$ ./gradlew -q :build-logic:dependencyInsight --dependency kotlin-compiler-embeddable --configuration RuntimeClasspath
$ ./gradlew -q :shared:dependencyInsight --dependency slf4j-api --configuration RuntimeClasspath

$ ./gradlew :backend:jvm:listResolvedArtifacts

# KMP hierarchy and module graphs
$ ./gradlew :shared:printHierarchy
$ ./gradlew createModuleGraph
$ ./gradlew generateChangelog

# Clean
$ ./gradlew cleanAll

# Gradle Daemon Toolchain
$ ./gradlew updateDaemonJvm

# Gradle Best Practices
$ ./gradlew -p gradle/build-logic :bestPracticesBaseline
$ ./gradlew checkBuildLogicBestPractices

# GitHub Actions lint
$ actionlint

Deployed App and Docs

Verifying Artifacts

The published artifacts are signed using this key. The best way to verify artifacts is automatically with Gradle.

Resources

Module Dependency Graph

Module Dependency

%%{
  init: {
    'theme': 'neutral'
  }
}%%
graph LR
    subgraph :backend
        :backend:native["native"]
        :backend:data["data"]
        :backend:profiling["profiling"]
        :backend:jvm["jvm"]
        :backend:security["security"]
    end
    subgraph :compose
        :compose:desktop["desktop"]
        :compose:html["html"]
    end
    subgraph :dep-mgmt
        :dep-mgmt:bom["bom"]
        :dep-mgmt:catalog["catalog"]
    end
    subgraph :meta
        :meta:compiler["compiler"]
        :meta:ksp["ksp"]
    end
    subgraph :meta:compiler
        :meta:compiler:plugin["plugin"]
    end
    subgraph :meta:ksp
        :meta:ksp:processor["processor"]
    end
    subgraph :web
        :web:js["js"]
        :web:wasm["wasm"]
    end
    :web:js --> :shared
    :benchmark --> :shared
    :backend:native --> :shared
    :web:wasm --> :shared
    :compose:desktop --> :shared
    :meta:compiler:plugin --> :shared
    :meta:ksp:processor --> :shared
    :backend:data --> :shared
    :backend:profiling --> :shared
    :compose:html --> :shared
    : --> :backend
    : --> :benchmark
    : --> :compose
    : --> :meta
    : --> :shared
    : --> :web
    : --> :backend:data
    : --> :backend:jvm
    : --> :backend:native
    : --> :backend:profiling
    : --> :backend:security
    : --> :compose:desktop
    : --> :compose:html
    : --> :meta:compiler
    : --> :meta:ksp
    : --> :web:js
    : --> :web:wasm
    : --> :meta:compiler:plugin
    : --> :meta:ksp:processor
    : --> :dep-mgmt:bom
    : --> :dep-mgmt:catalog
    :backend:jvm --> :shared
    :backend:jvm --> :backend:data
    :backend:jvm --> :backend:profiling
    :backend:jvm --> :web:js
    :backend:jvm --> :web:wasm
    :backend:security --> :shared
Loading