在CI/CD管道中构建Kotlin多平台项目的教程

286 阅读10分钟

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-allbuild-android 管道参数。对于iOS,它是一个build-iosbuild-all

这个工作流程根据运行与工作流程相关的作业的变更文件,有选择地运行。这种设置可以让你的团队更快、更有效地运作,同时还可以利用KMM的所有多平台功能。

总结

在本教程中,你已经学会了如何设置一个在CircleCI上构建Kotlin多平台移动项目的管道。 它与构建任何其他项目相似,只是要知道先为哪个平台构建。移动平台通常是单独开发的,所以我们使用CircleCI的动态配置功能,只构建开发人员目前正在进行的那部分应用。