现在在正规成体系的公司项目中,咱们都会搭建和装备 CI/CD 环境来完结工程的构建,主动化测验,artifact 发布等使命。在 Github 上的开源项目也不例外,给代码 PR 或者 push 装备 CI/CD 能够主动验证希望 merge 的代码是否能经过构建及测验,也能够装备主动构建发布的工作流在远端履行这些使命并向对该项目感兴趣的人公开。

我最近在给 MMKV-Kotlin 及 SQLlin 两个 Kotlin Multiplatform 开源项目装备 actions 的时候遇到了一些坑,因而在本文中记录一下 Kotlin Multiplatform 工程一般会遇到的一些问题。至于怎么装备根本 Github Actions 的教程网上已经有很多人写过了,因而本文不再详细介绍详细关键字怎么运用。

根本装备

咱们在项目工程目录下创立 .github/workflow 目录,然后将需求的 action(yml 文件)放在该目录下即可。一般状况下我认为一个 Kotlin Multiplatform项目至少需求两个 workflow;第一个是build.yml, 在有代码 merge 或 push 的状况下触发,用于检查欲兼并的代码是否能经过构建及单元测验;另一个是publish.yml,在希望构建及发布新版的时候项目管理者手动触发,可主动完结构建、测验以及发布到 Maven Central。因而,build.yml 的’on’ 句子如下:

on:
  push:    
    branches:     
      - '*'  
  pull_request:    
    branches:          
      - '*'

publish.yml 的 ‘on’ 句子:

on:
  workflow_dispatch:

环境初始化

一个 Kotlin Multiplatform 工程一般会打出以下渠道的产品:JVM、Android、JS(Node, Browser)、Native(Android NDK, iOS, macOS, tvOS, watchOS, Linux, Windows, WASM)。Github 供给的服务器环境有 macOS、Windows、Ubuntu 三种,非原生渠道产品(JVM, Android, JS)能够在任意体系的机器上构建,但是 Native 渠道的状况就更为杂乱。例如,iOS、macOS 等 Apple 渠道的 target 构建依靠 Xcode,而 Xcode 又只能运转在 macOS 上,因而假如你的 target 中包含 Apple 渠道,则有必要在 macOS 上构建。此外 Windows 渠道产品依靠 MinGW,因而只能在 Windows 上构建;Linux 的linuxMips32及linuxMipsel32targets 只能在 Linux 上构建;64 位的 Android NDK 产品只能在 Linux 或 macOS 上构建。我将 Kotlin/Native 的产品以及构建的 host 的关系的关系总结为下表:

Target\Host macOS Windows Ubuntu
Android NDK only 32-bit
iOS
macOS
watchOS
tvOS
Linux exceptlinuxMips32 and linuxMipsel32 exceptlinuxMips32 and linuxMipsel32
Windows
WASM

MMKV-Kotlin 这个项目支持的渠道分别有:Android、iOS、macOS。因而咱们只需求装备一台 macOS 的环境即可完结整个流程的构建。

但是 SQLlin 的状况更为杂乱。它支持的渠道有:Android、iOS、macOS、tvOS、watchOS、Linux (仅 x64)、Windows。仅装备单一环境就无法彻底掩盖希望支持的渠道,因而我需求同时装备两个 job 并行履行。第一个 job 运转于 macOS,构建除了 Windows 外的一切 artifacts;第二个 job 运转于 Windows,构建 Windows artifacts:核心 yml 代码如下:

jobs:
  build-on-macos:    
    runs-on: macos-latest    
    timeout-minutes: 60
    steps: 
    #......      
      - name: Build sqllin-driver        
        run: ./gradlew :sqllin-driver:assemble
      - name: Run sqllin-driver Test
        run:./test_driver_macos.sh
      #......
  build-on-windows:    
    runs-on: windows-latest
    timeout-minutes:60
    steps:
    #......  
      -name:Buildsqllin-driver 
        run:./gradlew:sqllin-driver:mingwX64MainKlibrary&&./gradlew:sqllin-driver:mingwX86MainKlibrary
  
      -name:Runsqllin-driverTest
        run: ./test_driver_windows.sh            
      #......

主动化测验

咱们能够在 workflow 中装备并主动履行测验,一般来说 Kotlin Multiplatform 项目的单元测验能够分为:JVM、Android、JS、Native 几个大类的独立测验。咱们将测验的主要代码写在 common test source set 中,然后在各渠道相关的 source set 中编写一些渠道相关的代码,然后即可将同一套代码在不同的渠道进行主动化测验。

不过,相应渠道的单元测验只能运转在对应的 host 上。例如 macOS 的 host 上只能直接运转 macosX64 的 单元测验。对于 Android,有些 Android 的单元测验有必要运转插桩测验(instrumented test),即有必要要有 Android 环境,但现在我还未找到在 Github Actions 中敞开 Android 模拟器的方法,因而现在也无法运转。

咱们了解了 Kotlin Multiplatform 及 Github Actions 的一些限制,现在能够制定咱们的测验战略。即在 build-on-macos 完毕后运转一次 macosX64 test,在另一个并行的 job——build-on-windows 完毕 Windows artifacts 的 build 后履行一次 mingwX64 test(代码见环境初始化小节的示例)。

当然这样的测验战略并不能掩盖一切的目标体系以及一切的场景,因而假如 merge 的代码中有修改未掩盖测验的渠道 source set 中的代码,又或者在 publish 之前,项目的管理者最好在本地运转相应的单元测验。

Secrets

Kotlin Multiplatform 项目的 publish 流程有两个关键步骤——artifacts 签名以及发布到 Maven Central。这两个步骤都有一些不能公开的信息,例如签名的GPG Key 私钥,以及发布到 Maven Central 所需的 OSSRH 账号暗码。在本地咱们能够把这些信息保护到大局的 gradle.properties 文件,而在 Github Actions 中咱们能够把这些信息装备到 Github Secrets。

咱们找到项目的 Github 主页,依次点击Settings -> Secrets -> Actions -> New reposirory secret即可创立一条Secret,Name表明它的 key,Secret表明 value,将明文填写进去即可。比如说 OSSRH 账号和暗码我给它们的 key 分别起名为NEXUS_USERNAMENEXUS_PASSWORD。在将其保护到 Github Secrets 之后咱们能够在 build.gradle.kts 脚本中这样获取它们:

val NEXUS_USERNAME: String by project
val NEXUS_PASSWORD: String by project

装备 GPG Key 相对来说更为杂乱,去网上查找教程一般会教咱们下载安装 GPG Suite,然后生成自己的密钥,再将密钥发布到服务器,最终在 gradle.properties 中装备密钥 id,暗码,以及 gpg 文件的路径。在 build.gradle.kts 中导入 signing 插件再编写如下脚本即可完结签名:

signing{
    sign(publishing.publications)
}

这对本地签名是有用的,但是在 Github Actions 中,咱们无法把 gpg 文件上传到 host,因而有必要运用密钥内存签名。signing 还推荐咱们运用 GPG 子密钥来做签名,这样更安全,咱们在生成子密钥后修改 build.gradle.kts:

signing{
    valSIGNING_KEY_ID:Stringbyproject
    valSIGNING_KEY:Stringbyproject
    valSIGNING_PASSWORD:Stringbyproject
    useInMemoryPgpKeys(SIGNING_KEY_ID,SIGNING_KEY,SIGNING_PASSWORD)
    sign(publishing.publications)
}

如上所示,区别在于咱们需求把 GPG 私钥保护在SIGNING_KEY这个字段中,在 Github Actions 中也就是把它放到 Secrets 里。不过这里有一个坑,网上一般会教咱们用这条指令来导出私钥:

gpg--export-secret-keys--armor<keyid>

这样导出的私钥会有很多换行符,直接把它填入 Secrets 会报“GPG key read error”错误。这里卡了我很长时间,最终感谢霍佬找到一篇文档,文档中教咱们运用以下指令导出私钥:

gpg2--export-secret-keys--armor<keyid><pathtosecring.gpg>|grep-v'--'|grep-v'^=.'|tr-d'\n'

多加了几个参数,换行符会被消除,亲测可行。

总结

CI/CD 真香,没装备的赶紧装备。详细的代码能够参阅咱们的两个开源项目:

  • MMKV-Kotlin

  • SQLlin

我们新年快乐,新年立个compose-jb 的flag~