封面 《さくらの雲*スカアレットの恋》
前言
因为写了一个基于 react native 的 app,想要使用 github action 来进行自动继承和自动部署。因为我这里没有 ios 开发环境,因此就只写 android 方面的自动部署
准备
在进行脚本部署之前先看一下 react native 给的文档,我们需要对 android 生成的 apk 进行签名,在 windows 上执行以下脚本
1
| keytool -genkeypair -v -storetype PKCS12 -keystore my-upload-key.keystore -alias my-key-alias -keyalg RSA -keysize 2048 -validity 10000
|
把 my-upload-key.keystore
和 my-key-alias
换成你自己需要的名字。记住自己的 alias 和密码,并且保存好 keystore 文件,不要上传到版本库或泄露了。
整体流程
我们的 action 总体流程如下
1
| 拉取代码 -> 加载yarn和gradle缓存 -> 安装依赖 -> 编译代码 -> 对包进行签名 -> 发布到release
|
拉取代码
这里直接使用 action/checkout
加载缓存
这里使用 action/cache,使用 cache 存 yarn 和 gradle 的缓存
这里不存 node_modules
是由于不同 node 版本可能不一样,同时对 npm ci
不起作用
gradle 的缓存时要注意关闭 gradle 的守护进程,因为 gradle 有锁,可能导致缓存创建失败,用 gradle <task> --no-daemon
进行关闭
可以看到使用了缓存后总体速度快了很多,
缓存限制
一个仓库最多 10G 缓存,超过上限会将最早的缓存去除,另外上周的缓存也会去除。
安装依赖
这里直接 yarn
就行
编译代码
按照 react native 官方给的教程,记得加上 --no-daemon
。可以生成 aab
格式或者 apk
格式,aab
格式无法直接安装,需要谷歌商店,所以我们生成 apk
格式。两者区别可以看这
1
| cd android && ./gradlew assembleRelease --no-daemon
|
对于编译 debug 代码
如果要生成 aab
格式
签名
在 react-native 给出的例子里,我们需要修改 build.gradle
文件填写我们前面生成的 keystore 等信息,不过在 GitHub Action 里面我们可以以字符串的形式来存放需要保密的信息,而文件形式比较麻烦。
其中一种方法是加密成字符串后然后在 github action 里面解密
1 2
| gpg -c --armor release.keystore
|
1 2 3 4 5 6
| - name: checkout uses: actions/checkout@v3 run: | echo "${{secrets.RELEASE_KEYSTORE}}" > release.keystore.asc gpg -d --passphrase "${{secrets.RELEASE_KEYSTORE_PASSWORD}}" --batch release.keystore.asc > andorid/app/release.keystore
|
我们在这里使用别人写好的 r0adkll/sign-android-release@v1
这个 action 用的是 base64 进行加密
1
| openssl base64 < release.keystore | tr -d '\n' | tee release.keystore.base64.txt
|
配置如下
1 2 3 4 5 6 7 8 9 10 11
| steps: - uses: r0adkll/sign-android-release@v1 name: Sign app APK id: sign_app with: releaseDirectory: app/build/outputs/apk/release signingKeyBase64: ${{ secrets.SIGNING_KEY }} alias: ${{ secrets.ALIAS }} keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }} keyPassword: ${{ secrets.KEY_PASSWORD }}
|
签名后的文件输出在 ${{steps.sign_app.outputs.signedReleaseFile}}
发布到 release
这里使用 softprops/action-gh-release@v1。
在发布之前我们可能想改一下文件名,加上 package.json
里面的版本号,这里通过一条命令获取 package.json
的版本号
1
| cat package.json | jq '.version' | tr -d '"'
|
如果要让下一个 step 获取之前的 step 输出,则要在要输出的 step 中加上 id,要输出的 step 中执行。在其他的 step 中要用时使用 ${{steps.step_id.outputs.name}}
,不要忘记里 outputs
1
| echo "{name}={value}" >> $GITHUB_OUTPUT
|
完整 action 文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| name: Assemble Android Release
on: push: tags: - "v*" branches: - "master"
jobs: assemble: environment: release runs-on: ubuntu-latest steps: - name: checkout uses: actions/checkout@v3
- name: Get yarn cache directory path id: yarn-cache-dir-path run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3 id: yarn-cache with: path: ${{ steps.yarn-cache-dir-path.outputs.dir }} key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} restore-keys: | ${{ runner.os }}-yarn-
- name: Install Dependencies run: | yarn
- name: cache gradle uses: actions/cache@v3 with: path: | ~/.gradle/caches ~/.gradle/wrapper key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} restore-keys: | ${{ runner.os }}-gradle-
- name: Make Gradlew Executable run: cd android && chmod +x ./gradlew
- name: Generate App APK run: | cd android && ./gradlew assembleRelease --no-daemon
- name: Sign app APK uses: r0adkll/sign-android-release@v1 id: sign_app with: releaseDirectory: android/app/build/outputs/apk/release signingKeyBase64: ${{ secrets.SIGNING_KEY }} alias: ${{ secrets.KEY_ALIAS }} keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }} keyPassword: ${{ secrets.KEY_PASSWORD }} - name: get version id: rn_version run: | echo "version=$(cat package.json | jq '.version' | tr -d '"')" >> $GITHUB_OUTPUT
- name: rename file with version id: rename run: | mv ${{steps.sign_app.outputs.signedReleaseFile}} android/app/build/outputs/apk/release/app-release-${{steps.rn_version.outputs.version}}.apk echo "releaseFile=android/app/build/outputs/apk/release/app-release-${{steps.rn_version.outputs.version}}.apk" >> $GITHUB_OUTPUT
- name: Upload release assets uses: softprops/action-gh-release@v1 if: startsWith(github.ref, 'refs/tags/') with: files: | ${{steps.rename.outputs.releaseFile}}
|
执行 action
现在可以执行 action 来看看运行结果,目前的触发方式有 push、创建 v*
开头的 tag,或者手动触发。我们这里创建一个 v1.0.0
的 tag,可以看到 release 里面加上了编译后的 apk。
参考文献
Publishing to Google Play Store
action/checkout
action/cache
gradle daemon
Difference between apk (.apk) and app bundle (.aab)
How to store a Android Keystore safely on GitHub Actions
How do I get the output of a specific step in GitHub Actions?
react-native-ci-cd-github-action