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 24.ea-open
$ sdk u java 24.ea-open

Build & Run

$ ./gradlew build [-Pskip.test]
# Run the app
$ ./gradlew :backend:jvm:run
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:24-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 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
  • AppCDS

    # Run with AppCDS
    $ java -Xlog:class+load:file=/tmp/cds.log:uptime,level,tags,pid \
           -XX:+AutoCreateSharedArchive \
           -XX:SharedArchiveFile=/tmp/app.jsa \
           -jar backend/jvm/build/libs/jvm-all.jar
    
    # cds-log-parser.jar --logFile=/tmp/cds.log
  • 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:24-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: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 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)
$ ./gradlew publishAggregatedPublicationToCentralPortal
# For all 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

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