Kotlin是目前最通用的编程语言之一,这在很大程度上是因为Kotlin团队专注于将其带到尽可能多的平台上。它是开发Android应用程序的主要语言,在JVM后端很受欢迎。Kotlin还具有用Kotlin/Native进行本地二进制编译和通过Kotlin/JS进行网络编译的特点。它最有前途的功能之一是能够针对多个平台进行编译。
这就是Kotlin多平台移动(KMM)的作用:它可以让你在Android和iOS应用程序中编写和重复使用相同的Kotlin代码。由于移动客户端通常以功能对等为目标,这是一种避免重复工作的聪明方法,而我们都知道开发者是如何喜欢避免重复工作的。相信我,我是一个开发者,我知道这有多真实。
总之,回到Kotlin多平台。这是与Flutter和React Native等跨平台工具不同的方法,这些工具在一切之上提供统一的UI。KMM让你编写通用的业务逻辑,同时将用户界面和体验牢牢保持在其原生平台的领域内。在许多情况下,这种方法对应用程序的用户,也就是你的客户来说是比较好的。
本文将告诉你如何在CI/CD管道中开始构建KMM项目,并将其纳入你的团队的开发工作流程。
跟随的注意事项
本文假设你了解Kotlin,并熟悉Android或iOS中的至少一种,最好是两种平台。本文不会教你KMM。为此,请查看Kotlin网站上现有的教程,或样本程序的自述。
另外请注意:KMM目前是Alpha版本。技术和API在不断变化和发展,我们正在尽最大努力保持样本的工作和更新,但除了样本中的API版本外,应该没有任何保证。
在CI/CD管道中构建多平台项目
要在CI/CD的背景下建立一个多平台项目,可以把它看作是为每个目标平台建立不同的项目。我们的项目是基于Kotlin自己的多平台样本--一个RSS阅读器应用程序。它包含两个应用程序,以及一个以Kotlin库形式存在的共享Kotlin代码库。共享库保持在Kotlin中,以便在Android上进行编译,并将其编译为本地代码,以便在ARM64上为iOS目标运行。
Android应用程序的代码位于androidApp ,并以熟悉的Kotlin代码库编写。iOS应用程序是用Swift编写的,位于iosApp 。要了解KMM是如何使一切工作的,请查看官方KMM文档文件和Kotlin的GitHub repo上的示例项目。
构建一个基本的流水线
KMM项目最简单的CI/CD流水线在一个工作流中包含2个作业,分别为每个平台执行构建。CircleCI中的作业是一连串的命令或步骤,它们在预先确定的环境中执行。这个环境是为不同平台构建应用程序的关键。对于iOS,我们需要在Mac硬件上构建它,传入macos ,作为执行器。安卓系统则更为宽松,但我们仍然可以挑选一个预建环境,其中包含所有的SDK和其他一切内置的东西。在本教程中,我们将使用由android orb提供的android Docker镜像。
工作本身是直接的,与本教程并不相关:检查代码、编译、也许运行一些测试、构建,甚至可能部署。作为本指南的一部分,我们将不把重点放在作业上。相反,我们将花一些时间来设置构建的环境和工作流程。如果你想了解更多关于设置Android的构建和测试工作,你可以在这篇文章中阅读。如果你想了解更多关于在iOS上设置构建和测试的信息,你可以在CircleCI文档中的这个教程中阅读更多。
version: 2.1
orbs:
android: circleci/android@1.0.3
jobs:
build-android:
executor: android/android
steps:
- checkout
- android/restore-build-cache
- android/restore-gradle-cache
- run:
name: Run Android tests
command: ./gradlew androidApp:testDebugUnitTest
- android/save-gradle-cache
- android/save-build-cache
build-ios:
macos:
xcode: 12.4.0
steps:
- checkout
- run:
name: Allow proper XCode dependency resolution
command: |
sudo defaults write com.apple.dt.Xcode IDEPackageSupportUseBuiltinSCM YES
rm ~/.ssh/id_rsa || true
for ip in $(dig @8.8.8.8 bitbucket.org +short); do ssh-keyscan bitbucket.org,$ip; ssh-keyscan $ip; done 2>/dev/null >> ~/.ssh/known_hosts || true
for ip in $(dig @8.8.8.8 github.com +short); do ssh-keyscan github.com,$ip; ssh-keyscan $ip; done 2>/dev/null >> ~/.ssh/known_hosts || true
- run:
name: Install Gem dependencies
command: |
cd iosApp
bundle install
- run:
name: Fastlane Tests
command: |
cd iosApp
fastlane scan
workflows:
build-all:
jobs:
- build-android
- build-ios
构建Android应用程序
开始构建Android应用程序的最好方法是使用Android orb。这给你一些内置的工作和命令,使其更容易构建。在这个练习中,我们要安装Gradle依赖项,并运行Gradle命令来运行单元测试。
Kotlin多平台带来的主要区别是,应用程序不是顶级项目。相反,它位于androidApp 模块中。这个模块对shared 有依赖性,所以我们需要调用Gradle命令,前缀为androidApp: 。
orbs:
android: circleci/android@1.0.3
jobs:
build-android:
executor: android/android
steps:
- checkout
- android/restore-build-cache
- android/restore-gradle-cache
- run:
name: Run Android tests
command: ./gradlew androidApp:testDebugUnitTest
- android/save-gradle-cache
- android/save-build-cache
- store_artifacts:
path: androidApp/build/outputs/apk/debug
构建iOS应用程序
正如我们上面提到的,构建iOS应用程序需要安装XCode的Mac硬件。对于build-ios 工作,我们将使用macos 执行器,其中我们传递xcode版本作为参数。在我们的例子中,它是:xcode: 12.4.0 。
其余的步骤是共同的,无论你是使用CocoaPods还是Swift Package Manager。你通常会安装一些依赖项,然后你可以使用Fastlane构建应用程序并运行测试。在本教程中,我们将只运行一些测试。
当我们用Fastlane启动构建时,共享的Kotlin代码会自动构建为一个框架,所以这里没有什么需要做的。
jobs:
...
build-ios:
macos:
xcode: 12.4.0
steps:
- checkout
- run:
name: Allow proper XCode dependency resolution
command: |
sudo defaults write com.apple.dt.Xcode IDEPackageSupportUseBuiltinSCM YES
rm ~/.ssh/id_rsa || true
for ip in $(dig @8.8.8.8 bitbucket.org +short); do ssh-keyscan bitbucket.org,$ip; ssh-keyscan $ip; done 2>/dev/null >> ~/.ssh/known_hosts || true
for ip in $(dig @8.8.8.8 github.com +short); do ssh-keyscan github.com,$ip; ssh-keyscan $ip; done 2>/dev/null >> ~/.ssh/known_hosts || true
- run:
name: Install Gem dependencies
command: |
cd iosApp
bundle install
- run:
name: Fastlane Tests
command: |
cd iosApp
fastlane scan
创建一个具有动态配置的高级流水线
这个管道样本在每次有人提交版本库时都能很好地构建和测试你的KMM应用程序的两个目标平台。但我们可以做得更好。 移动开发的现实是,我们有一些专家把他们的工作集中在各自的平台上。有些人喜欢并使用安卓系统,喜欢为安卓系统制作优秀的用户体验,并且对该平台了如指掌。另一方面,也有一些开发者以同样的方式专注于iOS。在共同的代码库中,总是会有一些交叉的工作。在大多数团队中,仍然会有一些人专门从事一种或另一种平台的工作。
开发的进展通常是在这些配置中的一个形成的。
- 只有Android前端代码库
- 仅限iOS前端代码库
- 共享的KMM代码库,被两个前端代码库所使用
对于前两种情况,只为正在工作的平台构建更有意义,可以节省时间、信用和效率。
为了优化这一流程,我们可以使用CircleCI的动态配置功能,它可以帮助我们更有效地协调这样的项目。动态配置让你只建立你所改变的应用程序的部分。
动态配置通过一个设置工作流工作,在第一步评估代码库,检测哪些地方发生了变化(后面会详细介绍),然后才运行真正的工作流,即构建应用程序的相关部分。
设置工作流仍然使用CircleCI用户可能熟悉的config.yml 。被执行的工作流使用了一个路径过滤兽皮,它检测与指定分支相比的变化。这里是完整的配置。
version: 2.1
setup: true
orbs:
# the path-filtering orb is required to continue a pipeline based on
# the path of an updated fileset
path-filtering: circleci/path-filtering@0.0.2
workflows:
select-for-build:
jobs:
- path-filtering/filter:
base-revision: master
config-path: .circleci/continue-config.yml
mapping: |
shared/.*|^(?!shared/.*|iosApp/.*|androidApp/.*).* build-all true
androidApp/.* build-android true
iosApp/.* build-ios true
下面是这个配置的一个细分。
setup:true 表示CircleCI,我们正在使用动态配置,这是管道运行的第一部分。设置工作流是这个配置的唯一工作流。它有一个工作, ,它来自 orb。该作业需要几个参数。path-filtering/filter path-filtering
base-revision表示要对比的分支mapping确定要比较的路径(后面会有更多介绍)config-path指向continue-config.yml,这是在这个设置工作流之后要评估的下一个配置。在我们的例子中,我们是指向一个静态文件,但我们也可以事先以编程方式构建这个新的配置文件。
要使用mapping ,你要定义项目的路径,与基本修订版中的代码库进行比较。每行有一个路径。然后,设置一个管道参数,以传递给后续的管道,以及它的值。所有这些都用空格隔开。
第2行和第3行是相当直接的。
androidApp/.* build-android true
在这种情况下,我们感兴趣的路径是androidApp/ 目录中的所有文件和子目录。这就是安卓专用前端的代码库所在。管道参数,build-android ,被设置为true 。
然而,第一条匹配线是比较复杂的,因为有一个长得多的regex匹配器。
shared/.*|^(?!shared/.*|iosApp/.*|androidApp/.*).* build-all true
它匹配shared/ 目录中的路径,这是我们的Kotlin多平台代码所在的地方,以及任何不在shared/ 、iosApp/ 、androidApp/ 目录中的东西,这包括任何其他顶级文件。我们已经将管道参数build-all 设置为true ,这将触发两个平台的应用程序的构建。
正如在continue-config.yml ,在映射评估了所有的变化并设置了所有相关的管道参数后,CircleCI停止了这项工作,并启动了这个动态工作流程的第二部分。这里是完整的文件。
version: 2.1
orbs:
android: circleci/android@1.0.3
parameters:
build-all:
type: boolean
default: false
build-android:
type: boolean
default: false
build-ios:
type: boolean
default: false
jobs:
build-android:
executor: android/android
steps:
- checkout
- android/restore-build-cache
- android/restore-gradle-cache
- run:
name: Build Android app
command: ./gradlew androidApp:assembleDebug
- android/save-gradle-cache
- android/save-build-cache
build-ios:
macos:
xcode: 12.4.0
steps:
- checkout
- run:
name: Allow proper XCode dependency resolution
command: |
sudo defaults write com.apple.dt.Xcode IDEPackageSupportUseBuiltinSCM YES
rm ~/.ssh/id_rsa || true
for ip in $(dig @8.8.8.8 bitbucket.org +short); do ssh-keyscan bitbucket.org,$ip; ssh-keyscan $ip; done 2>/dev/null >> ~/.ssh/known_hosts || true
for ip in $(dig @8.8.8.8 github.com +short); do ssh-keyscan github.com,$ip; ssh-keyscan $ip; done 2>/dev/null >> ~/.ssh/known_hosts || true
- run:
name: Install Gem dependencies
command: |
cd iosApp
bundle install
- run:
name: Fastlane Tests
command: |
cd iosApp
fastlane scan
workflows:
run-android:
when:
or:
- << pipeline.parameters.build-android >>
- << pipeline.parameters.build-all >>
jobs:
- build-android
run-ios:
when:
or:
- << pipeline.parameters.build-ios >>
- << pipeline.parameters.build-all >>
jobs:
- build-ios
这里没有包括setup:true 行,这表明这是一个 "标准 "的CircleCI 配置文件。它确实包含了parameters 部分,其中有我们在前一阶段定义的管道参数。
parameters:
build-all:
type: boolean
default: false
build-android:
type: boolean
default: false
build-ios:
type: boolean
default: false
要使用管道参数,我们首先需要在管道中定义它们。我们在前面使用了它们。现在你指定它们的类型和默认值:所有boolean ,并设置为false 。
workflows:
run-android:
when:
or:
- << pipeline.parameters.build-android >>
- << pipeline.parameters.build-all >>
jobs:
- build-android
run-ios:
when:
or:
- << pipeline.parameters.build-ios >>
- << pipeline.parameters.build-all >>
jobs:
- build-ios
一旦参数被定义,我们就可以在过滤工作流时使用它们。与上一节所示的简单得多的配置不同,我们需要将作业分成2个工作流。
- 安卓系统的工作被命名为
run-android - 针对iOS的工作被命名为
run-ios
我们可以使用when stanza结合逻辑运算符来指定何时运行特定工作流的过滤器。对于Android,它是一个build-all 或build-android 管道参数。对于iOS,它是一个build-ios 或build-all 。
这个工作流程根据运行与工作流程相关的作业的变更文件,有选择地运行。这种设置可以让你的团队更快、更有效地运作,同时还可以利用KMM的所有多平台功能。
总结
在本教程中,你已经学会了如何设置一个在CircleCI上构建Kotlin多平台移动项目的管道。 它与构建任何其他项目相似,只是要知道先为哪个平台构建。移动平台通常是单独开发的,所以我们使用CircleCI的动态配置功能,只构建开发人员目前正在进行的那部分应用。