Reusable multi-module project template with typed Gradle conventions for Android, desktop, and shared modules.
This repository is meant to make multi-module project setup less painful:
- shared modules use a strict KMP DSL instead of ad-hoc Gradle wiring
- Android and desktop apps get their own typed receivers instead of one generic scope
- versioning, JVM level, Android defaults, lint, and module discovery stay centralized
- the same structure works both as a starter project and as a source of reusable build logic
This is not a generic build framework and not a big plugin platform.
It is an opinionated template for repositories that want:
- a clean multi-module layout
- reusable Gradle conventions
- a small public DSL surface
- a migration path from manual Gradle scripts to typed conventions
| Entry point | Use for |
|---|---|
moduleApi |
Shared KMP library with explicit API enabled |
moduleImpl |
Shared KMP library for internal implementation |
androidApp |
Android application module |
desktopApp |
Compose Desktop application module |
The DSL is intentionally strict:
- shared modules and apps use different receivers
- dependency scopes are typed by module family
- Android-only options stay under
android { ... } - project-specific escape hatches still go through
custom { ... }
The repository itself is the template:
kmp-convention-plugins/
├── android-app/
├── desktop-app/
├── core/
│ └── greeting/
│ ├── api/
│ └── impl/
├── build-logic/
├── config/
├── docs/
├── gradle/
├── build.gradle.kts
├── settings.gradle.kts
├── gradle.properties
└── version.properties
The top-level modules are not a detached demo layer. They are the reference project shape.
The template keeps common project rules in one place instead of spreading them across modules.
gradle.properties controls which roots are scanned:
convention.module.roots=core,android-app,desktop-appJava, Kotlin/JVM, Android Kotlin, and desktop runtime toolchains share one default:
convention.jvm.toolchain=21Shared modules can still override it per module:
moduleImpl {
module {
jvmToolchain(17)
}
}Android app defaults come from version.properties:
version.buildbecomes the default AndroidversionCode- the resolved root version becomes the default Android
versionName android.versionCode/android.versionNameingradle.propertiesstay available as fallbacks
logger() uses Kermit:
- in shared modules it is added to
commonMain - in
androidAppit is added to the app module directly
Use the repository as the base for a new project:
- rename
com.example.*packages and app IDs - replace
core/greeting/*with your real modules - keep the existing root wiring and conventions
- expand the module roots only when your structure actually grows
Start here:
Copy the reusable infrastructure into an existing repository:
build-logic/config/detekt/gradle/libs.versions.toml- the relevant parts of
settings.gradle.kts,build.gradle.kts,gradle.properties, andversion.properties
Then migrate modules gradually to:
moduleApimoduleImplandroidAppdesktopApp
Start here:
build-logic/is reusable infrastructurecore/,android-app/, anddesktop-app/are the reference template modulescustom {}is allowed, but it should stay the exception, not the main API- Android flavors belong in
androidApp { android { flavors { ... } } } - Android lint policy belongs in properties and typed DSL, not in hardcoded workarounds inside core conventions
- JDK 21
- Gradle 8.x+
- Android SDK for
android-app