Slide 1

Slide 1 text

@IsuruKusumal KotlinConf’23 Amsterdam How to Publish Your First Multiplatform Library
 Isuru Rajapakse Android Developer, Motorola Solutions

Slide 2

Slide 2 text

Introduction https: // github.com/xxfast/KStore

Slide 3

Slide 3 text

Introduction > Lifecycle of a library

Slide 4

Slide 4 text

Lifecycle Incubate Develop Publish Maintain Introduction > Lifecycle of a library

Slide 5

Slide 5 text

@v1 @v2 @v3 Introduction > Lifecycle of a library

Slide 6

Slide 6 text

Top 10 Essential Tools YOU NEED! to Publish Your Own Multiplatform Library! Introduction

Slide 7

Slide 7 text

Develop Lifecycle of a library Incubate

Slide 8

Slide 8 text

1. In ation cub spir Incubate > Inspire

Slide 9

Slide 9 text

Incubate > Inspire

Slide 10

Slide 10 text

interface ValueStore { fun get(): Maybe fun observePut(value: T): Single fun observe(): Observable?> fun observeClear(): Completable class ValueUpdate(val value: T?) } RxStore Incubate > Inspire

Slide 11

Slide 11 text

class KStore { suspend fun get(): T? suspend fun put(value: T) val updates: Flow suspend fun clear() } KStore Incubate > Inspire

Slide 12

Slide 12 text

ate ncub sol 2. I Incubate > Isolate

Slide 13

Slide 13 text

Incubate > Isolate YourProject YourLibrary

Slide 14

Slide 14 text

YourProject YourLibrary commonMain kotlin au.com.myproject screens feature-1 feature-2 utils MyLibrary Incubate > Isolate

Slide 15

Slide 15 text

@RequiresOptIn(message = "This API is experimental”) @Retention(BINARY) @Target(CLASS, FUNCTION,PROPERTY) annotation class ExperimentalMyLibraryApi feature-1 feature-2 utils MyLibrary YourProject YourLibrary Incubate > Isolate https: // github.com/Kotlin/api-guidelines ExperimentalMyLibraryApi

Slide 16

Slide 16 text

@ExperimentalMyLibraryApi fun interface NewApi { fun doSomethingNew() = TODO() } feature-1 feature-2 utils MyLibrary YourProject YourLibrary Incubate > Isolate https: // github.com/Kotlin/api-guidelines ExperimentalMyLibraryApi @OptIn(ExperimentalMyLibraryApi :: class) val myApi = NewApi { . . }

Slide 17

Slide 17 text

Develop Lifecycle of a library Incubate

Slide 18

Slide 18 text

Lifecycle of a library Develop

Slide 19

Slide 19 text

3. Setup CI Early! Develop > Continuous Integration

Slide 20

Slide 20 text

Github Actions Develop > Continuous Integration

Slide 21

Slide 21 text

name: Build on: .. jobs: check: .. build: .. release: if: ${{ github.event_name != 'pull_request' }} needs: build publish: if: ${{ github.event_name != 'pull_request' }} needs: build check release publish build Develop > Continuous Integration

Slide 22

Slide 22 text

build name: Build on: .. jobs: check: .. build: strategy: matrix: config: [ { target: android, os: ubuntu-latest, .. }, { target: apple, os: macos-latest, .. }, { target: windows, os: windows-latest, .. } { target: linux, os: ubuntu-latest, .. }, { target: js, os: ubuntu-latest, .. }, { target: desktop, os: ubuntu-latest, .. }, ] runs-on: ${{ matrix.config.os }} check release publish ▾ Matrix: build Build android Build apple Build desktop Build js Build linux Develop > Continuous Integration

Slide 23

Slide 23 text

build name: Build on: .. jobs: check: .. build: strategy: matrix: config: [ { os: ubuntu-latest, tasks: testReleaseUnitTest .. }, { os: macos-latest, tasks: appleTest, .. }, { os: windows-latest, tasks: windowsTest, .. } { os: ubuntu-latest, tasks: linuxTest, .. }, { os: ubuntu-latest, tasks: jsTest .. }, { os: ubuntu-latest, tasks: desktopTest, .. }, ] runs-on: ${{ matrix.config.os }} check release publish ▾ Matrix: build Build android Build apple Build desktop Build js Build linux Develop > Test

Slide 24

Slide 24 text

4. Declare Public API! Develop > Public API

Slide 25

Slide 25 text

YourLibrary Public API 🤏 Develop > Public API

Slide 26

Slide 26 text

YourLibrary explicitApi() @SinceKotlin("1.4.0") explicitApi() Public API Develop > Public API

Slide 27

Slide 27 text

YourLibrary interface NewApi { fun doSomethingNew() = TODO() } Public API Develop > Public API

Slide 28

Slide 28 text

YourLibrary Public API public interface NewApi { public fun doSomethingNew(): Nothing = TODO() } Develop > Public API

Slide 29

Slide 29 text

5. Don’t break Compatibility! Develop > Compatibility

Slide 30

Slide 30 text

Develop > Compatibility 1.0.1 2.1.2 1.1.0 2.0.0 YourProject YourLibrary +minor/patch +major 2.1.3 +minor/patch https: // semver.org/

Slide 31

Slide 31 text

kotlinlang.org/docs/jvm-api-guidelines-backward-compatibility.html Develop > Compat . . “Backward-compatible code allows clients of newer API versions to use the same API code that they used with an older API version.”

Slide 32

Slide 32 text

Develop > Compatibility https: / / github.com/Kotlin/binary-compatibility-validator

Slide 33

Slide 33 text

Develop > Compatibility

Slide 34

Slide 34 text

Develop > Compatibility

Slide 35

Slide 35 text

Develop > Compatibility build check ▾ Matrix: build Build android Build apple Build desktop Build js Build linux name: Build on: .. jobs: check: steps: - name: Check api run: ./gradlew apiCheck release publish

Slide 36

Slide 36 text

Develop > Compatibility

Slide 37

Slide 37 text

Publish Lifecycle of a library Develop

Slide 38

Slide 38 text

Lifecycle of a library Publish

Slide 39

Slide 39 text

6. Document everything! Publish / Documentation

Slide 40

Slide 40 text

plugins { kotlin("multiplatform") id("org.jetbrains.dokka") version "1.8.10" } Publish > Documentation https: // github.com/Kotlin/dokka

Slide 41

Slide 41 text

Publish > Documentation

Slide 42

Slide 42 text

name: Build jobs: publish: if: ${{ github.event_name != 'pull_request' }} steps: - name: Generate docs with dokka run: ./gradlew dokkaHtml - name: Upload artifact uses: actions/upload-pages-artifact@v1 with: path: ${{ github.workspace }}/kstore/build/dokka/html - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v1 check release publish build Publish > Documentation ▾ Matrix: build

Slide 43

Slide 43 text

7. Release the Kraken! Publish / Release

Slide 44

Slide 44 text

Publish / Release repositories { mavenCentral() } /** Warning ⚠ Incoming lots of configuration! * 1. Setup and link your OSSRH repository * 2. Include sources + documentation * 3. Sign your artefacts * */ https: // kotlinlang.org/docs/multiplatform-publish-lib.html

Slide 45

Slide 45 text

publishing { repositories { maven { val isSnapshot = version.toString().endsWith("SNAPSHOT") val destination = if (!isSnapshot) "https: // s01.oss.sonatype.org/service/local/stagi else "https: // s01.oss.sonatype.org/content/repositories/snapshots" url = uri(destination) credentials { username = gradleLocalProperties(rootDir).getProperty("sonatypeUse password = gradleLocalProperties(rootDir).getProperty("sonatypePas } plugins { id("maven-publish") id("signing") } Publish / Release https: // central.sonatype.org/publish/publish-guide/ // 1. Link OSSRH repository

Slide 46

Slide 46 text

credentials { username = gradleLocalProperties(rootDir).getProperty("sonatypeUse password = gradleLocalProperties(rootDir).getProperty("sonatypePas } } } publishing { val javadocJar = tasks.register("javadocJar") { dependsOn(tasks.dokkaHtml) archiveClassifier.set("javadoc") from("$buildDir/dokka") } publications { withType { artifact(javadocJar) pom { name.set("KStore") description.set("A tiny Kotlin multiplatform library that assists licenses { Publish / Release https: // central.sonatype.org/publish/publish-guide/ // 2. Include sources + documentation

Slide 47

Slide 47 text

signing { useInMemoryPgpKeys( gradleLocalProperties(rootDir).getProperty("gpgKeySecret"), gradleLocalProperties(rootDir).getProperty("gpgKeyPassword"), ) sign(publishing.publications) } Publish / Release https: // central.sonatype.org/publish/publish-guide/ // 3. Sign your artefacts

Slide 48

Slide 48 text

Publish / Release https: // central.sonatype.org/publish/publish-guide/

Slide 49

Slide 49 text

release: if: ${{ github.event_name != 'pull_request' }} runs-on: macos-latest needs: - build steps: - name: Write secrets to local.properties if: ${{ github.event_name != 'pull_request' }} run: | echo sonatypeUsername="${SONATYPE_USERNAME}" >> "local. echo sonatypePassword="${SONATYPE_PASSWORD}" >> "local. echo gpgKeyPassword="${GPG_KEY_PASSWORD}" >> "local.pro echo gpgKeySecret="${GPG_KEY_SECRET}" >> "local.propert env: SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} GPG_KEY_PASSWORD: ${{ secrets.GPG_KEY_PASSWORD }} GPG_KEY_SECRET: ${{ secrets.GPG_KEY_SECRET }} - name: Release to sonatype run: ./gradlew publishAllPublicationsToMavenRepository check release publish build Publish / Release ▾ Matrix: build

Slide 50

Slide 50 text

Publish / Release

Slide 51

Slide 51 text

Maintain Lifecycle of a library Publish

Slide 52

Slide 52 text

Lifecycle of a library Maintain

Slide 53

Slide 53 text

8. Coverage w/ Kover Maintain / Code Coverage

Slide 54

Slide 54 text

No content

Slide 55

Slide 55 text

Maintain > Code Coverage https: // github.com/Kotlin/kotlinx-kover

Slide 56

Slide 56 text

Maintain > Code Coverage plugins { kotlin("multiplatform") id("org.jetbrains.kotlinx.kover") version "0.6.1" }

Slide 57

Slide 57 text

name: Build jobs: check: steps: - name: Generate kover coverage report run: ./gradlew koverXmlReport - name: Add coverage report to PR if: ${{ github.event_name == 'pull_request' }} id: kover uses: mi-kas/kover-report@v1 with: path: ${{ github.workspace }}/kstore/build/reports/kover/x token: ${{ secrets.GITHUB_TOKEN }} title: Code Coverage update-comment: true min-coverage-overall: 80 min-coverage-changed-files: 80 check release publish build Maintain > Code Coverage ▾ Matrix: build https: // github.com/mi-kas/kover-report

Slide 58

Slide 58 text

Maintain > Code Coverage

Slide 59

Slide 59 text

9. Let the bots do the bumps! Maintain / Dependency Management

Slide 60

Slide 60 text

Maintain > Dependency Mgmt .. https: // docs.gradle.org/current/userguide/platforms.html

Slide 61

Slide 61 text

Maintain > Dependency Management

Slide 62

Slide 62 text

https: // github.com/joreilly/Confetti Maintain > Dependency Management

Slide 63

Slide 63 text

Maintain > ? 10. Have fun with(kotlin)!

Slide 64

Slide 64 text

Thank you,
 and don’t forget
 to vote @IsuruKusumal KotlinConf’23 Amsterdam