Setelah memahami pengujian KMP, kita siap membangun library untuk dipublikasi. Library KMP berbeda dari library JVM biasa—satu kode sumber menghasilkan banyak artifact: library-jvm.jar, library-android.aar, library-iosarm64.klib, library-js.ir, dan library-metadata.jar. Pengguna library hanya perlu mendeklarasikan satu dependency di build.gradle.kts mereka, dan Gradle akan otomatis memilih artifact yang sesuai dengan target mereka. Pada chapter ini kita akan membedah struktur library KMP, melihat konfigurasi build.gradle.kts dengan plugin maven-publish, dan memahami proses publikasi ke Maven Central.
Struktur Library KMP #
Library KMP mengikuti pola source set yang sama dengan aplikasi: commonMain berisi kode bersama, source set spesifik berisi kode platform. Perbedaan utama: library juga menghasilkan artifact metadata yang berisi deklarasi commonMain—digunakan konsumen untuk resolusi target. Tanpa artifact metadata, Gradle tidak dapat menemukan artifact jvm atau iosArm64 saat konsumen menambahkan dependency.
my-library/
├── build.gradle.kts
├── settings.gradle.kts
└── src/
├── commonMain/kotlin/com/example/Util.kt
├── commonTest/kotlin/com/example/UtilTest.kt
├── jvmMain/kotlin/com/example/Platform.jvm.kt
├── androidMain/kotlin/com/example/Platform.android.kt
├── iosMain/kotlin/com/example/Platform.ios.kt
└── jsMain/kotlin/com/example/Platform.js.ktDeklarasi Target dan Plugin #
File build.gradle.kts untuk library KMP memerlukan dua plugin: kotlin("multiplatform") untuk kompilasi lintas platform, dan maven-publish untuk publikasi artifact. Plugin signing ditambahkan bila ingin mempublikasi ke Maven Central yang mensyaratkan signature GPG.
plugins {
kotlin("multiplatform") version "2.0.21"
`maven-publish`
signing
}
group = "id.example.belajar"
version = "1.0.0"
kotlin {
androidTarget { publishLibraryVariants("release") }
iosX64()
iosArm64()
iosSimulatorArm64()
jvm()
js { browser(); nodejs() }
linuxX64()
}Setiap target menghasilkan artifact dengan format berbeda—jvm() menghasilkan JAR, androidTarget() menghasilkan AAR, iosX64() menghasilkan framework atau KLIB. Konsumen library cukup menambahkan satu baris dependency, dan Gradle akan otomatis memilih artifact sesuai target mereka.
Simulasi Publikasi Library #
Demo berikut mensimulasikan struktur library KMP yang dipublikasi ke Maven Central dan beberapa repository lain. Model data menggambarkan target publikasi, source set, dan metadata POM yang dibutuhkan.
// kotlin_multiplatform_library.kt
// Membangun dan mempublikasi library KMP untuk Maven Central.
// Demo: simulasi struktur library + target artifacts + metadata publikasi.
data class TargetPublikasi(
val name: String,
val artifactId: String,
val outputType: String,
val sinkronisasiKe: String
)
data class LibraryMetadata(
val groupId: String,
val artifactId: String,
val version: String,
val license: String,
val scmUrl: String,
val developer: String
)
data class SourceSetArtifact(
val sourceSet: String,
val artifactName: String,
val ukuranKB: Int,
val jumlahKotlinFiles: Int
)
class PublikasiLibrary(
private val metadata: LibraryMetadata,
private val targets: List<TargetPublikasi>,
private val sourceSets: List<SourceSetArtifact>
) {
fun gambarStruktur(): String {
val sb = StringBuilder()
sb.appendLine("Library: ${metadata.groupId}:${metadata.artifactId}:${metadata.version}")
sb.appendLine("License: ${metadata.license}")
sb.appendLine("SCM : ${metadata.scmUrl}")
sb.appendLine("Author : ${metadata.developer}")
sb.appendLine()
sb.appendLine("Source sets:")
for (s in sourceSets) {
sb.appendLine(" ${s.sourceSet.padEnd(22)} -> ${s.artifactName} (${s.ukuranKB} KB, ${s.jumlahKotlinFiles} file)")
}
sb.appendLine()
sb.appendLine("Target artifacts yang dipublikasi:")
for (t in targets) {
sb.appendLine(" ${t.name.padEnd(20)} artifact=${t.artifactId.padEnd(40)} type=${t.outputType}")
}
return sb.toString().trimEnd()
}
fun gambarGradleKts(): String {
return """
plugins {
kotlin("multiplatform") version "2.0.21"
`maven-publish`
signing
}
group = "${metadata.groupId}"
version = "${metadata.version}"
kotlin {
androidTarget { publishLibraryVariants("release") }
iosX64()
iosArm64()
iosSimulatorArm64()
jvm()
js { browser(); nodejs() }
linuxX64()
sourceSets {
val commonMain by getting
val commonTest by getting
}
}
publishing {
publications.withType<MavenPublication> {
pom {
name.set("${metadata.artifactId}")
description.set("Library KMP untuk demonstrasi publikasi")
url.set("${metadata.scmUrl}")
licenses {
license {
name.set("${metadata.license}")
}
}
developers {
developer { id.set("${metadata.developer}") }
}
scm {
url.set("${metadata.scmUrl}")
}
}
}
}
""".trimIndent()
}
fun verifikasiSinkronisasi(): List<Pair<String, Boolean>> {
return targets.map { t ->
t.name to (t.sinkronisasiKe.isNotBlank())
}
}
}
fun main() {
val meta = LibraryMetadata(
groupId = "id.example.belajar",
artifactId = "kmp-util",
version = "1.0.0",
license = "Apache-2.0",
scmUrl = "https://github.com/example/kmp-util",
developer = "budi"
)
val targets = listOf(
TargetPublikasi("jvm", "kmp-util-jvm", "JAR", "Maven Central"),
TargetPublikasi("android", "kmp-util-android", "AAR", "Maven Central"),
TargetPublikasi("iosArm64", "kmp-util-iosarm64", "Framework", "Maven Central + CocoaPods"),
TargetPublikasi("iosX64", "kmp-util-iosx64", "Framework", "Maven Central + CocoaPods"),
TargetPublikasi("iosSimArm", "kmp-util-iossimulatorarm64", "Framework", "Maven Central + CocoaPods"),
TargetPublikasi("js", "kmp-util-js", "NPM package", "npm registry"),
TargetPublikasi("linuxX64", "kmp-util-linuxx64", "KLIB", "Maven Central"),
TargetPublikasi("metadata", "kmp-util-metadata", "KLIB (common)", "Maven Central")
)
val sourceSets = listOf(
SourceSetArtifact("commonMain", "kmp-util-metadata", 18, 4),
SourceSetArtifact("commonTest", "kmp-util-metadata", 6, 2),
SourceSetArtifact("jvmMain", "kmp-util-jvm", 12, 2),
SourceSetArtifact("androidMain", "kmp-util-android", 10, 2),
SourceSetArtifact("iosMain", "kmp-util-iosarm64", 8, 2),
SourceSetArtifact("iosX64Main", "kmp-util-iosx64", 8, 1),
SourceSetArtifact("iosArm64Main", "kmp-util-iosarm64", 8, 1),
SourceSetArtifact("iosSimulatorArm64Main", "kmp-util-iossimulatorarm64", 8, 1),
SourceSetArtifact("jsMain", "kmp-util-js", 7, 1),
SourceSetArtifact("linuxX64Main", "kmp-util-linuxx64", 7, 1)
)
val lib = PublikasiLibrary(meta, targets, sourceSets)
println(lib.gambarStruktur())
println()
println("=== Ringkasan publikasi ===")
println("Jumlah artifact publikasi : ${targets.size}")
println("Jumlah source set : ${sourceSets.size}")
println("Total ukuran source : ${sourceSets.sumOf { it.ukuranKB }} KB")
println()
println("=== Verifikasi sinkronisasi ke repository ===")
for ((name, sinkron) in lib.verifikasiSinkronisasi()) {
val status = if (sinkron) "siap publikasi" else "PERLU SETUP"
println(" ${name.padEnd(20)} : $status")
}
println()
println("=== Konfigurasi build.gradle.kts (ringkas) ===")
println(lib.gambarGradleKts())
}Output yang Dihasilkan:
Library: id.example.belajar:kmp-util:1.0.0
License: Apache-2.0
SCM : https://github.com/example/kmp-util
Author : budi
Source sets:
commonMain -> kmp-util-metadata (18 KB, 4 file)
commonTest -> kmp-util-metadata (6 KB, 2 file)
jvmMain -> kmp-util-jvm (12 KB, 2 file)
androidMain -> kmp-util-android (10 KB, 2 file)
iosMain -> kmp-util-iosarm64 (8 KB, 2 file)
iosX64Main -> kmp-util-iosx64 (8 KB, 1 file)
iosArm64Main -> kmp-util-iosarm64 (8 KB, 1 file)
iosSimulatorArm64Main -> kmp-util-iossimulatorarm64 (8 KB, 1 file)
jsMain -> kmp-util-js (7 KB, 1 file)
linuxX64Main -> kmp-util-linuxx64 (7 KB, 1 file)
Target artifacts yang dipublikasi:
jvm artifact=kmp-util-jvm type=JAR
android artifact=kmp-util-android type=AAR
iosArm64 artifact=kmp-util-iosarm64 type=Framework
iosX64 artifact=kmp-util-iosx64 type=Framework
iosSimArm artifact=kmp-util-iossimulatorarm64 type=Framework
js artifact=kmp-util-js type=NPM package
linuxX64 artifact=kmp-util-linuxx64 type=KLIB
metadata artifact=kmp-util-metadata type=KLIB (common)
=== Ringkasan publikasi ===
Jumlah artifact publikasi : 8
Jumlah source set : 10
Total ukuran source : 92 KB
=== Verifikasi sinkronisasi ke repository ===
jvm : siap publikasi
android : siap publikasi
iosArm64 : siap publikasi
iosX64 : siap publikasi
iosSimArm : siap publikasi
js : siap publikasi
linuxX64 : siap publikasi
metadata : siap publikasi
=== Konfigurasi build.gradle.kts (ringkas) ===
plugins {
kotlin("multiplatform") version "2.0.21"
`maven-publish`
signing
}
group = "id.example.belajar"
version = "1.0.0"
kotlin {
androidTarget { publishLibraryVariants("release") }
iosX64()
iosArm64()
iosSimulatorArm64()
jvm()
js { browser(); nodejs() }
linuxX64()
sourceSets {
val commonMain by getting
val commonTest by getting
}
}
publishing {
publications.withType<MavenPublication> {
pom {
name.set("kmp-util")
description.set("Library KMP untuk demonstrasi publikasi")
url.set("https://github.com/example/kmp-util")
licenses {
license {
name.set("Apache-2.0")
}
}
developers {
developer { id.set("budi") }
}
scm {
url.set("https://github.com/example/kmp-util")
}
}
}
}Mari kita bedah bagian kunci dari kode di atas:
LibraryMetadata: metadata POM yang wajib untuk Maven Central—groupId,artifactId,version,license,scmUrl, dandeveloper. Maven Central menolak artifact tanpa informasi ini.TargetPublikasi: model untuk satu target publikasi—nama target, artifact ID, jenis output (JAR/AAR/Framework/KLIB), dan repository tujuan.SourceSetArtifact: model untuk satu source set dengan nama artifact dan ukuran. Pada proyek nyata, ukuran dihitung setelah build—demo ini memakai nilai statis.PublikasiLibrary.gambarStruktur(): mencetak ringkasan library—metadata, source set, target artifacts. Format ini menggambarkan apa yang akan dilihat pengguna di halaman Maven Central.PublikasiLibrary.gambarGradleKts(): menghasilkan konfigurasibuild.gradle.ktsyang setara dengan demo. Perhatikan blokpublishing { publications.withType<MavenPublication> { pom { ... } } }yang menambahkan metadata POM.verifikasiSinkronisasi(): daftar status per target—siap publikasi atau perlu setup. Bila satu target belum dikonfigurasi repository tujuan, library belum dapat dirilis.- Output menunjukkan 8 artifact:
jvm,android, tigaiosArm64/iosX64/iosSimulatorArm64,js,linuxX64, danmetadata. Artifact metadata berisi kodecommonMainyang dipakai Gradle untuk resolusi target.
Persyaratan Maven Central #
Maven Central mensyaratkan beberapa hal yang tidak ada di repository lokal. Pertama, artifact harus di-sign dengan GPG—plugin signing melakukan ini otomatis bila signing.keyId dan signing.password di-set di ~/.gradle/gradle.properties. Kedua, POM wajib berisi name, description, url, license, developers, dan scm. Ketiga, perlu akun Sonatype OSSRH dengan namespace yang sesuai (mis. id.example.belajar untuk domain belajar.example.id).
signing {
useInMemoryPgpKeys(
findProperty("signingKey") as String?,
findProperty("signingPassword") as String?
)
sign(publishing.publications)
}Referensi Lanjutan #
- Multiplatform library — panduan membuat pustaka KMP yang dapat dikonsumsi lintas platform
- Publish libraries — dokumentasi publikasi pustaka KMP ke repository Maven
Penutup #
Membangun library KMP untuk Maven Central memerlukan konfigurasi yang lebih kompleks daripada library JVM biasa—multiple target, artifact metadata, dan signature GPG. Namun, bagi konsumen library, penggunaan tetap sederhana: satu baris dependency, Gradle yang memilih artifact. Chapter berikutnya akan masuk ke topik konkret pertama: networking dengan ktor-client, bagaimana satu kode HTTP dapat berjalan di JVM, Android, dan iOS.