Slide 1

Slide 1 text

Kotlin Multiplatform: Structure, Versioning and Ci/Cd

Slide 2

Slide 2 text

Table of Contents Character Introduction Project Structures How should the players interact? Who are the key players? Versioning CI/CD Pipeline How to automate everything? How to keep track of the players? 01 02 03 04

Slide 3

Slide 3 text

Character Introduction 01

Slide 4

Slide 4 text

Marco Valentino! ● Graduated with Master’s Degree from University of Illinois Urbana Champaign ● Joined teamLab in April 2019 doing iOS (0.5 years) and Android (1.5 years) development ● Started working with Kotlin Multiplatform since early 2020

Slide 5

Slide 5 text

Characters iOS Kotlin Multiplatform Android iOS KMP Android

Slide 6

Slide 6 text

Characters iOS Android [iOS Project] [Android Project] [Server] UI Domain Data Data Domain UI

Slide 7

Slide 7 text

Characters iOS KMP Android [iOS Project] [Android Project] [Server] UI Domain Data UI [KMP Project]

Slide 8

Slide 8 text

Project Structure 02

Slide 9

Slide 9 text

Mono-Repository iOS KMP Android [Repository]

Slide 10

Slide 10 text

Mono-Repository iOS Android [Repository] KMP

Slide 11

Slide 11 text

Mono-Repository iOS KMP Android [Repository]

Slide 12

Slide 12 text

Multi-Repository iOS KMP Android [Repository] [Repository] [Repository]

Slide 13

Slide 13 text

Multi-Repository iOS Android [Repository] [Repository] [Repository] KMP

Slide 14

Slide 14 text

Multi-Repository iOS Android [Repository] [Repository] [Repository] KMP

Slide 15

Slide 15 text

Multi-Repository iOS KMP Android [Repository] [Repository] [Repository]

Slide 16

Slide 16 text

Multi-Repository iOS KMP Android [Repository] [Repository] [Repository]

Slide 17

Slide 17 text

Multi-Repository iOS KMP Android [Repository] [Repository] [Repository] [Cocoapods] [Maven]

Slide 18

Slide 18 text

Multi-Repository iOS KMP Android [Repository] [Repository] [Repository] [Cocoapods] [Maven]

Slide 19

Slide 19 text

Multi-Repository iOS KMP Android [Repository] [Repository] [Repository] [Cocoapods] [Maven] v 0.1 v 0.2

Slide 20

Slide 20 text

Multi-Repository iOS KMP Android [Repository] [Repository] [Repository] [Cocoapods] [Maven] v 0.1 v 0.2

Slide 21

Slide 21 text

Versioning 03

Slide 22

Slide 22 text

- Delegate to Github versioning for releases and pull requests - Use tags for releases - Use commit hashes for snapshots Versioning

Slide 23

Slide 23 text

- Delegate to Github versioning for releases and pull requests - Use tags for releases - Use commit hashes for snapshots Versioning git fetch --tags --quiet && git describe --tags --always --first-parent releases: 0.1.0 snapshots: 0.1.0-1-gf4e3f96 [sh]

Slide 24

Slide 24 text

latest tag - Delegate to Github versioning for releases and pull requests - Use tags for releases - Use commit hashes for snapshots Versioning git fetch --tags --quiet && git describe --tags --always --first-parent releases: 0.1.0 snapshots: 0.1.0-1-gf4e3f96 # of commits since tag commit hash [sh]

Slide 25

Slide 25 text

- Delegate to Github versioning for releases and pull requests - Use tags for releases - Use commit hashes for snapshots Versioning environment { VERSION = sh([script:’git fetch --tags --quiet && git describe --tags --always --first-parent’, returnStdout:true]).trim() ... } [CI/CD: Jenkinsfile] allprojects { version = System.getenv(“VERSION”) ... } [KMP: root/build.gradle]

Slide 26

Slide 26 text

CI/CD Pipeline 04

Slide 27

Slide 27 text

Goals for Ci/Cd Versioning System Push Artifacts to a Repository Maven for Android, Cocoapods for iOS Releases for tags, Snapshots for Pull Requests Parallel Builds Run platform specific tasks on different nodes 01 02 03 Hosted on private Github repository Do not use other package managers (Github Packages, Bintray, ect.) 04

Slide 28

Slide 28 text

Ci/Cd Pipeline Setup Lint Test Publish Assemble Report

Slide 29

Slide 29 text

Ci/Cd Pipeline Setup Lint Test Publish Assemble Report

Slide 30

Slide 30 text

Create place to store artifacts locally /artifacts/maven for Android /artifacts/cocoapods for iOS Add gradle tasks to create necessary artifacts 01 02 Artifacts Overview

Slide 31

Slide 31 text

Artifacts plugins { apply(“maven-publish”) ... } ... publishing { repositories { maven(“${project.rootDir}/artifacts/maven”) } } [KMP: build.gradle] Android ./gradlew publishKotlinMultiplatformPublicationToMavenRepository ./gradlew publishAndroidDebugPublicationToMavenRepository ./gradlew publishAndroidReleasePublicationToMavenRepository [sh]

Slide 32

Slide 32 text

Create ‘iosmodule’ module Add gradle tasks to create universal framework Setup Cocoapods Repo Add gradle tasks to create podspec 01 02 03 04 Artifacts iOS

Slide 33

Slide 33 text

Create ‘iosmodule’ module Add gradle tasks to create universal framework Setup Cocoapods Repo Add gradle tasks to create podspec 01 02 03 04 Artifacts iOS

Slide 34

Slide 34 text

Artifacts kotlin { ... configure(listOf(iosArm64(), iosX64()) { binaries.framework { ... cinterop settings ... export(“::module1”) export(“::module2”) ... } } ... } [KMP: iosmodule/build.gradle] iOS - Creating an ‘iosmodule’ module

Slide 35

Slide 35 text

Artifacts kotlin { ... sourceSets { val commonMain by getting { dependencies { api(“::module1”) api(“::module2”) ... } } ... } ... } [KMP: iosmodule/build.gradle] iOS - Creating an ‘iosmodule’ module

Slide 36

Slide 36 text

Create ‘iosmodule’ module Add gradle tasks to create universal framework Setup Cocoapods Repo Add gradle tasks to create podspec 01 02 03 04 Artifacts iOS

Slide 37

Slide 37 text

Artifacts kotlin { val frameworkName = “common” val artifactsDir = rootDir.resolve(“artifacts/cocoapods”) val debugFramework = tasks.create("debugFramework", FatFrameworkTask::class) { baseName = frameworkName destinationDir = artifactsDir.resolve("debug") from( iosArm64.binaries.getFramework("DEBUG"), iosX64.binaries.getFramework("DEBUG") ) } } [KMP: iosmodule/build.gradle] iOS - Create gradle task to build framework

Slide 38

Slide 38 text

Create ‘iosmodule’ module Add gradle tasks to create universal framework Setup Cocoapods Repo Add gradle tasks to create podspec 01 02 03 04 Artifacts iOS

Slide 39

Slide 39 text

Artifacts iOS - Setup Cocoapods Repo https://guides.cocoapods.org/making/private-cocoapods.html

Slide 40

Slide 40 text

Create ‘iosmodule’ module Add gradle tasks to create universal framework Setup Cocoapods Repo Add gradle tasks to create podspec 01 02 03 04 Artifacts iOS

Slide 41

Slide 41 text

Artifacts iOS - Generate Podspec Pod::Spec.new do |spec| spec.name = "" spec.version = "" spec.license = "" spec.homepage = "" spec.authors = "" spec.summary = "" spec.source = { :git => '[email protected]:/Spec.git, :branch => '' } spec.platform = :ios, "" spec.vendored_frameworks = 'cocoapods/debug/.framework' spec.static_framework = true spec.libraries = "c++", "sqlite3", ... spec.module_name = "#{spec.name}_umbrella" spec.pod_target_xcconfig = { 'KOTLIN_TARGET[sdk=iphonesimulator*]' => 'ios_x64', 'KOTLIN_TARGET[sdk=iphoneos*]' => 'ios_arm' ... } end [Podspec]

Slide 42

Slide 42 text

Artifacts iOS - Generate Podspec val generatePodspec = tasks.create("generatePodspec") { val podspecText = “”” Pod::Spec.new do |spec| spec.name = "" spec.version = "$version" spec.license = "" spec.homepage = "" spec.authors = "" spec.summary = "" spec.source = { :git => '[email protected]:/Spec.git’, :branch => '$version' } spec.platform = :ios, "" spec.vendored_frameworks = 'cocoapods/debug/$frameworkName.framework' spec.static_framework = true spec.libraries = "c++", "sqlite3", ... spec.module_name = "#{spec.name}_umbrella" spec.pod_target_xcconfig = { 'KOTLIN_TARGET[sdk=iphonesimulator*]' => 'ios_x64', 'KOTLIN_TARGET[sdk=iphoneos*]' => 'ios_arm' ... } end “””.trimIndent() artifactsDir.resolve(".podspec”).writeText(podspecText) } [KMP: iosmodule/build.gradle]

Slide 43

Slide 43 text

Artifacts iOS - Generate Podspec val generatePodspec = tasks.create("generatePodspec") { val podspecText = “”” Pod::Spec.new do |spec| spec.name = "" spec.version = "$version" spec.license = "" spec.homepage = "" spec.authors = "" spec.summary = "" spec.source = { :git => '[email protected]:/Spec.git’, :branch => '$version' } spec.platform = :ios, "" spec.vendored_frameworks = 'cocoapods/debug/$frameworkName.framework' spec.static_framework = true spec.libraries = "c++", "sqlite3", ... spec.module_name = "#{spec.name}_umbrella" spec.pod_target_xcconfig = { 'KOTLIN_TARGET[sdk=iphonesimulator*]' => 'ios_x64', 'KOTLIN_TARGET[sdk=iphoneos*]' => 'ios_arm' ... } end “””.trimIndent() artifactsDir.resolve(".podspec”).writeText(podspecText) } [KMP: iosmodule/build.gradle]

Slide 44

Slide 44 text

Artifacts iOS - Generate Podspec val generatePodspec = tasks.create("generatePodspec") { val podspecText = “”” Pod::Spec.new do |spec| spec.name = "" spec.version = "$version" spec.license = "" spec.homepage = "" spec.authors = "" spec.summary = "" spec.source = { :git => '[email protected]:/Spec.git’, :branch => '$version' } spec.platform = :ios, "" spec.vendored_frameworks = 'cocoapods/debug/$frameworkName.framework' spec.static_framework = true spec.libraries = "c++", "sqlite3", ... spec.module_name = "#{spec.name}_umbrella" spec.pod_target_xcconfig = { 'KOTLIN_TARGET[sdk=iphonesimulator*]' => 'ios_x64', 'KOTLIN_TARGET[sdk=iphoneos*]' => 'ios_arm' ... } end “””.trimIndent() artifactsDir.resolve(".podspec”).writeText(podspecText) } [KMP: iosmodule/build.gradle]

Slide 45

Slide 45 text

Artifacts stage(‘Artifacts’) { parallel { stage(‘Android’) { steps { node(label: 'android') { sh "./gradlew publishKotlinMultiplatformPublicationToMavenRepository" sh "./gradlew publishAndroidDebugPublicationToMavenRepository" sh "./gradlew publishAndroidReleasePublicationToMavenRepository" stash(name: 'androidArtifacts', includes: '**/artifacts/maven/**') } stage(‘Ios’) { steps { node(label: 'ios') { sh "./gradlew universalFramework” sh "./gradlew generateCocoapodsFile” stash(name: 'iosArtifacts', includes: '**/artifacts/cocoapods/**') } } } } } [CI/CD: Jenkinsfile] KMP

Slide 46

Slide 46 text

Push artifacts to GitHub 01 Publish Overview Run Cocoapod commands 02

Slide 47

Slide 47 text

Push artifacts to GitHub 01 Publish Overview Run Cocoapod commands 02

Slide 48

Slide 48 text

Publish plugins { id("org.ajoberstar.git-publish") version "3.0.0" } extensions.getByType().run { repoUri.set() branch.set("$version") commitMessage.set("Release $version") contents { from("artifacts") } } [KMP: build.gradle.kts] - Use Gradle-Git-Publish plugin (https://github.com/ajoberstar/gradle-git-publish)

Slide 49

Slide 49 text

Push artifacts to GitHub 01 Publish Overview Run Cocoapod commands 02

Slide 50

Slide 50 text

Publish pod repo remove || true pod repo add pod repo push artifacts/cocoapods/.podspec [sh] - Add Cocoapods commands (https://guides.cocoapods.org/making/private-cocoapods.html)

Slide 51

Slide 51 text

Publish stage(‘Publish’) { unstash 'androidArtifacts' unstash 'iosArtifacts' sh ``` ./gradle gitPublishPush pod repo remove || true pod repo add pod repo push artifacts/cocoapods/.podspec ``` } [CI/CD: Jenkinsfile]

Slide 52

Slide 52 text

Publish

Slide 53

Slide 53 text

Thanks for listening!

Slide 54

Slide 54 text

CREDITS: This presentation template was created by Slidesgo, including icons by Flaticon, infographics & images by Freepik Credits!