搭建 RN 项目的 CI/CD
July 26, 2019 • ☕️☕️☕️☕️ 18 min read
本篇文章主要讲如何使用 Jenkins 为 React Native
项目搭建 CI/CD
。
涉及内容如下:
- gitflow
- Jenkins 搭建
- 权限控制
- 发布到测试平台
- Android:打包、加固、zipalign、签名、多渠道包
- iOS:证书、打包、发布
- 自动生成 tag、release note
当然,上面的一些内容本身是为了项目的特定需求而做的,不一定适用其他项目。
另外,因为我自身并不是 DevOps 和原生 App 开发者,所以可能存在一些错误使用。
需求
任何功能的产生必定从需求开始,开发者根据明确的需求,来做技术调研和实现方案。
对于一家普通的小公司,CI/CD
的需求可能如下:
- 实时得对提交到
Github Repo
的代码执行单元测试,并反馈结果到Commit
、PR
- 代码 review 阶段可以触发打包,如
PR
下评论 - 有不同的开发环境,因此需要打不同环境的包(
dev
、staging
、production
) - 将打好的包发布到测试平台,并通知相关人员进行测试
- 如果是 production 的包,需要自动提交到应用商店
- Android 需要发布多个平台,因此需要多渠道包
对于去中心化钱包,有更严格的安全需求:
- 避免代码泄露,禁止第三方可触达
- 权限管理,控制触发打包的人员权限
因此,出于以上安全需求的考虑,不使用第三方的服务,而是购买一台 Mac Pro 通过 Jenkins
自己搭建 CI
,还能节省不少支出。
概览
选择使用监听仓库更新的方式,而不是通过 webhooks
,主要出于安全考量。
触发方式
CI
本身是被动的执行者,执行任务的过程是由触发开启的,然后周而复始的工作着,
替代人力去做重复的事。
提高效率的同时,也为了避免一些人容易犯的低级错误。
上图中,我们主要通过两种方式触发 CI
:
1. push commit
当有新的代码提交时,希望能够实时的反馈该修改是否对原有的代码造成破坏,
因此触发 CI
执行单元测试任务。
2. PR 下评论
当有人创建 Pull Request
时,相关人员会进行 code review
,
并可能指派人员打包给到 QA 进行测试。
此时,可通过在 PR 下评论 build dev
| build staging
| build prod
,
来触发 CI
基于此 PR 打对应环境的包。
触发方式需要根据团队的开发规范、gitflow 来适配,以上是基于前文的团队开发规范。
Jenkins 搭建
基础
首先,我们需要一个可编译 RN 项目的环境,因此需在 Mac 上安装一些基础软件:
- Xcode
- Android Studio
- Homebrew
- pod:
sudo gem install cocoapods
- java:
brew cask install caskroom/versions/java8
- gradle:
brew install gradle
- git:
brew install git
- nodejs:
brew install node
- yarn:
brew install yarn
- fastlane:
gem install fastlane -NV
编译配置
这里就不展开讲如何配置编译 RN 了,可以参考以下链接:
iOS:
Android:
安装 Jenkins
推荐使用 brew 安装
brew install jenkins
启动 Jenkins
jenkins --httpPort=4567
浏览器打开 http://localhost:4567/,创建账户(注意保管)。
安装 Plugin
进入 Jenkins 插件管理页面,http://localhost:4567/pluginManager/
只安装必要的插件,其他通过自己编写脚本实现,这是为了:
- 不过度依赖 Jenkins,即使没有 Jenkins,也可在自己电脑执行
- 更灵活,方便迁移到其他 CI
因此,只需要装以下两个插件即可。
1. GitHub Plugin
我们需要对 github 仓库做 clone、push 等操作,首先安装 GitHub Plugin。
配置:Manage Jenkins
-> Configure System
-> GitHub
需要去 github 设置页面生成 personal access token。
2. GitHub pull request builder plugin
监听 PR 的评论,执行相关的指令,并且需要控制权限,可安装 GitHub pull request builder plugin。
配置:Manage Jenkins
-> Configure System
-> GitHub pull requests builder
创建项目
创建一个 Freestyle 项目
以打包 RN 为例:
- 配置 github 仓库信息:仓库地址;触发
branch
使用**
- 设置构建触发器
Build Triggers
触发与权限控制
设置 build triggers
- 选择
GitHub Pull Request Builder
,Jenkins 会监听PR
的操作,例如添加评论。 - 设置
Admin list
:非 Admin 人员在 github 创建PR
时,Jenkins 会在PR
下添加一条评论Can one of the admins verify this patch?
- 设置
Trigger phrase
:当在PR
下评论该触发词,就会触发后续的构建脚本 - 设置轮询时间
Crontab line
- 设置权限:只有白名单人员在
PR
下评论才能触发
执行脚本
通过 Jenkins Execute shell
执行编写好的构建脚本。
自动重启
CI
机器可能意外关机重启,例如:公司断电、不小心触碰插线板等。
因此,需要在重启打包机器后,自动重启 CI
,在 Mac 重启启动项里设置执行启动脚本:
jenkins --httpPort=4567
脚本
编写的脚本应该使用最基础的语言,例如 bash
、make
,以及基础 nodejs
。
不应该使用 nodejs
的一些第三方库,增加脚本依赖与复杂度。
测试
单元测试:推荐使用 Jest
e2e:推荐使用 detox
项目里编写好执行脚本,在 Jenkins Execute shell
下配置即可。
打包
当在 PR
下评论 build dev
| build staging
| build prod
时,
就会触发:
- 自动升级版本号(build + 1)
- 将
1
的修改,提交 commit 到仓库 - 打出相应的版本
- 根据 commit 生成 changelog
- 将 apk 和 ipa 文件上传到 fir
- 将版本信息通知到 slack,供 QA 下载测试
可通过 fastlane 解决升级版本号、根据 commit 生成 changelog、提交到 fir 等问题。
可用的插件,如下:
打包的具体流程 iOS 和 Android 有很大不同。
iOS:
iOS 相对来说简单很多,配置好证书问题,完全可通过 fastlane 生成 ipa 文件, 并发布到 fir 和 testflight。
Android:
Android 复杂很多,涉及以下流程,并且需要手动编写脚本解决。
1. 生成 apk
app/build.gradle
文件配置后,执行
./gradlew assembleRelease
2. 加固
加固是对应用程序进行深度加密处理,防止应用在上线后被反编译、破解、恶意篡改、二次打包和内存截取等多种威胁,保护数据信息不轻易被黑客窃取。
可使用「360 加固保」,官网下载地址: Mac 版本。
将文件解压,我们真正需要的不是加固助手客户端,而是 jiagu
目录下的 jar
包。
有了 jar
包之后,我们就可以编写脚本来完成加固,而不需要通过客户端上传。
apkPath=$1
apkName=$2
java -jar ~/work/jiagu/jiagu.jar -jiagu ${apkPath}/${apkName}.apk ${apkPath}
mv ${apkPath}/${apkName}_*_jiagu.apk ${apkPath}/${apkName}-jiagu-sign.apk
执行加固之后,生成 app-release-jiagu-sign.apk
3. zipalign
zipalign 是 Android SDK 自带的工具,用于优化应用与 Android 系统的交互效率,提高应用运行性能。
上架 Google Play 必须经过 zipalign 工具优化
const zipalign = '~/Library/Android/sdk/build-tools/27.0.3/zipalign'
const zalign = (apkName, apkPath) => {
console.log('zipalign...')
const suffix = 'jiagu-zipalign.apk'
execSync(`${zipalign} -v 4 ${apkPath}/${apkName}-${jiaguSuffix} ${apkPath}/${apkName}-${suffix}`)
console.log('rename zipalign apk...')
execSync(`mv ${apkPath}/${apkName}-${suffix} ${apkPath}/${apkName}-${jiaguSuffix}`)
}
执行 zipalign 生成 app-release-jiagu-zipalign.apk
,重命名为 app-release-jiagu-sign.apk
。
4. 签名
经过 加固
和 zipalign
后的 apk,需要重新签名,签名可参考 Sign your app。
签名过程需要交互式的输入 keystore
密码,脚本使用 expect
编写,简单教程可见 Basic principles of using tcl-expect scripts。
#!/usr/bin/expect
set user root
set passwd [lindex $argv 0]
set signer [lindex $argv 1]
set keystore [lindex $argv 2]
set apkPath [lindex $argv 3]
spawn ${signer} sign -ks ${keystore} ${apkPath}
expect "Keystore password for signer #1:"
send "${passwd}\r"
interact
5. 多渠道包
如果在 build.gradle
文件配置 productFlavors
渠道信息,那么生成每个渠道包都必须重新执行一次编译。
也就是说如果有 10 个渠道,一个渠道包需要 4 分钟,那么就需要 40 分钟完成一次打包,十分耗时,必须寻找其他方式。
目前生成渠道包的方案用的是美团的 walle,原理是在 APK Signing Block
区块写入渠道信息,
然后在统计的时候,读取渠道信息。
更多详细信息可参考: 新一代开源 Android 渠道包生成工具 Walle。
使用
walle
后,渠道信息不能再以AndroidManifest.xml
配置为准,必须从APK Signing Block
区块获取渠道信息后,动态设置。
6. 发布到应用商店
由于国内应用商店不开放接口,所以大部分还是需要手动上传。
通知结果
当生成的 apk/ipa 包上传到 fir 后,通过 fir 接口,获取新上传的包信息, 将信息组装成 slack 接收的格式,发送到 slack channel。
生成 release note
为了生成可读性强的 release note,commit message
需要遵循约定好的规范。
开源社区有很多不错的规范,这里比较推荐 Angular Commit Message Guidelines,文档齐全、详细。
并且,我们可以利用 conventional-changelog 来生成如下可读性不错的 changelog。
Features
- support dynamic directive arguments for v-on, v-bind and custom directives (#9373)
Improvements
- improve scoped slots change detection accuracy (#9371)
Bug Fixes
- fix checkbox event edge case in Firefox (#1868)
我在文章 团队开发规范 里有更多说明,这里就不展开说了。
生成 changelog 脚本 gen-release-note.js
如下:
const version = process.argv[2] || process.env.VERSION
const cc = require('conventional-changelog')
const file = `./RELEASE_NOTE${version ? `_${version}` : ``}.md`
const fileStream = require('fs').createWriteStream(file)
cc({
preset: 'angular',
pkg: {
transform(pkg) {
pkg.version = `v${version}`
return pkg
}
}
}).pipe(fileStream).on('close', () => {
console.log(`Generated release note at ${file}`)
})
生成 tag
新版本发布之后,自动生成 git tag,并将 tag 和 release note 更新至 github。
#!/bin/bash
set -e
if [[ -z $1 ]]; then
echo "Enter new version(SemVer): "
read -r VERSION
else
VERSION=$1
fi
read -p "Releasing $VERSION - are you sure? (y/n) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
echo "Releasing $VERSION ..."
# generate release note
node ./scripts/gen-release-note $VERSION
# format message
rNote="$(cat RELEASE_NOTE_$VERSION.md)"
rNote=${rNote//\#\#\#/👉}
rNote=${rNote/\#\#/👇👇}
# add tag version
git tag -a "v$VERSION" -m "$rNote"
# publish
git push origin refs/tags/v"$VERSION"
fi
总结
在如上的流程中,基本实现了针对 RN 项目的自动化构建,能有效提升开发效率,使开发流程更可靠。
当然,还有很多优化的点,例如:
- 使用 docker 将环境隔离开
- Android 各个渠道自动发布
- 自动提交版本日志到应用商店
- …