基於Travis CI搭建Android自動打包釋出工作流

Allo發表於2015-12-15

最近付費購買了Travis CI,Travis CI的收費模式很有意思,不是按專案或者使用者,而是按工作程式收費,比如初級版本是$129/月,總共提供2個工作程式。在專案不多的情況下,除了用於跑單元測試外,不免想利用的更充分一些,因此抽空搭建了一套基於Travis CI的Android自動釋出工作流

未自動化前安卓開發總是避免不了這樣的工作流程:

  1. 開發一些新功能,提交程式碼
  2. 完成一部分功能後,打包一個測試版APK
  3. 將測試版APK上傳到QQ群 / 網盤 / Fir.im / 蒲公英
  4. 在QQ群或釋出平臺解釋當前版本所完成的功能
  5. 通知測試人員測試

實現了這套自動化釋出後,工作流程被簡化成:

  1. 開發新功能,提交程式碼
  2. 通過git tag對程式碼打一個內測版的tag,在tag的描述中對寫當前完成的功能

Tag提交後Travis CI會自動編譯程式碼,生成APK檔案並分發到Github和fir.im,Github和fir.im中會保持Tag的描述資訊,分發完成後會有郵件通知所有參與測試的人員。而作為開發人員,只需要專注於對程式碼打好一個Tag就可以了。

整個流程看似做了不少工作,其實體現在Travis CI只有數行指令而已,以下逐一講解:

對安卓專案啟用Travis CI

Travis CI應該可以算是目前最好用的持續整合服務之一了,如果程式碼庫是基於Github的話,可以很簡單的開啟。由於本文涉及到了很多Travis CI的基礎概念,建議首先對Travis CI的自定義構建一節有所瞭解。

很早前在介紹PHP專案的持續整合時也寫過如何在PHP專案中使用Travis CI。 對於安卓專案來說步驟幾乎一致:

首先準備一個.travis.yml檔案放在安卓專案根目錄下,.travis.yml中記錄了Travis CI所需的基礎資訊:

language: android

sudo: false

android:
  components:
  - build-tools-23.0.1
  - android-23
  - extra-android-m2repository
  - extra-android-support

script:
  - "./gradlew assembleRelease"

無需讀文件就可以通過上面的配置大概知道,我們要執行的是一個安卓專案,安卓SDK版本為23,專案所用的BuildTools版本為23.0.1,為編譯這個專案我們還引入了一些必須的元件,如Support Library(extra-android-support)、Android Support Repository(extra-android-m2repository)等。

當Travis CI準備好我們所需要的環境後,將自動執行yml檔案script部分所設定的指令,上例中執行的是./gradlew assembleRelease,執行成功的話會在專案的主模組下生成build/outputs/apk/app-release.apk

最後進入Travis CI主頁,使用有專案Admin許可權的Github帳號直接登入。選擇要開啟Travis CI的專案,將右邊的開關設為On即可。

Travis CI目前有2個網站:如果是開源專案,直接進入travis-ci.org即可,如果是私有付費專案,則需要進入travis-ci.com,2個網站除了域名外所有的介面及操作幾乎一模一樣。

配置中還有一行sudo: false,是為了開啟基於容器的Travis CI任務,讓編譯效率更高。

安卓自動化構建的密碼和證照安全

安卓專案釋出需要證照檔案和若干密碼,但無論是開源專案還是私有專案,任何時候都不應該將原始證照或密碼放入程式碼庫(原則上來講證照和密碼也不應該交於開發人員,而應該只能通過釋出伺服器進行編譯)。Travis CI為此提供了2種解決方案,一種是對敏感資訊、密碼、證照等進行對稱加密,在CI構建環境時解密,另一種是將密碼等通過Travis CI的控制檯(即網站)設定為構建時的環境變數。

由於前者會在Travis控制檯生成一對環境變數,所以我的做法是儘量選擇後者,但由於Travis控制檯無法上傳檔案,因此涉及到檔案加密的部分,則只能選擇前者。

說了這麼多,首先還是需要先對編譯指令碼進行改造,如果不考慮安全問題,專案的build.gradle檔案可能會是這樣:

android {
    signingConfigs {
        releaseConfig {
            storeFile file("../keys/evandroid.jks")
            storePassword "123456"
            keyAlias "evandroid_alias"
            keyPassword "654321"
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.releaseConfig
        }
    }
}

而我們最終要的效果,還是希望一份編譯指令碼既可以用於開發環境,也可以在CI環境下使用,在Travis CI中,可以通過點選專案名稱 -> Settings -> Environment Variables中設定環境變數,比如我們可以針對上面的配置,分別設定KEYSTORE_PASSALIAS_NAMEALIAS_PASS三個環境變數,在Travis CI環境下可以通過System.getenv()獲得這些環境變數。

本地開發環境中,我的做法是將這幾個變數加到gradle.properties檔案中,這樣就可以在build.gradle內直接使用了。下面是開發環境的gradle.properties

KEYSTORE_PASS=123456
ALIAS_NAME=evandroid_alias
ALIAS_PASS=654321

這樣一來build.gradle就變成了

    releaseConfig {
        storeFile file("../keys/evandroid.jks")
        storePassword project.hasProperty("KEYSTORE_PASS") ? KEYSTORE_PASS : System.getenv("KEYSTORE_PASS")
        keyAlias project.hasProperty("ALIAS_NAME") ? ALIAS_NAME : System.getenv("ALIAS_NAME")
        keyPassword project.hasProperty("ALIAS_PASS") ? ALIAS_PASS : System.getenv("ALIAS_PASS")
    }

接下來處理證照檔案,為了方便檔案加密等功能,Travis CI提供了一個基於ruby的CLI命令列工具,可以直接使用gem安裝

gem install travis

安裝後進入安卓專案根目錄,嘗試對證照檔案加密:

travis encrypt-file keys/evandroid.jks --add

如果首次執行,travis會提示需要登入,執行travis login --org並輸入Github使用者名稱密碼即可。(付費版則為travis login --pro

travis encrypt-file指令會做幾件事情:

  1. 在Travis CI控制檯自動生成一對金鑰,形如:encrypted_e41864bb9dab_key,encrypted_e41864bb9dab_iv
  2. 基於金鑰通過openssl對檔案進行加密,上例中會專案根目錄生成evandroid.jks.enc檔案
  3. .travis.yml中自動生成Travis CI環境下解密檔案的配置,上例執行後可以看到.travis.yml中多了幾行:

    before_install:

    • openssl aes-256-cbc -K $encrypted_e41864bb9dab_key -iv $encrypted_e41864bb9dab_i -in keys/evandroid.jks.enc -out keys/evandroid.jks -d

Travis CI預設在專案根目錄下執行,因此注意根據實際需求調整enc檔案的路徑。

最後別忘了在.gitignore中忽略keys/evandroid.jks以及gradle.properties並在程式碼庫中將其刪除。

Travis CI自動釋出安卓apk檔案到Github Release

Travis CI的script部分執行成功後,可以通過配置檔案進入到釋出階段。下面是一個Travis CI釋出的示例:

deploy:
  provider: releases
  user: "GITHUB USERNAME"
  password: "GITHUB PASSWORD"
  file: app/build/outputs/apk/app-release.apk
  skip_cleanup: true
  on:
    tags: true

這個例子中配置了這樣一些內容:

  • provider:釋出目標為Github Release,除了Github外,Travis CI還支援釋出到AWS、Google App Engine等數十種provider
  • Github使用者名稱和密碼,因為Travis CI要上傳APK檔案,因此需要有Github專案的寫入許可權
  • file: 釋出檔案,輸入檔案路徑即可
  • skip_cleanup: 預設情況下Travis CI在完成編譯後會清除所有生成的檔案,因此需要將skip_cleanup設定為true來忽略此操作。
  • on: 釋出的時機,這裡配置為tags: true,即只在有tag的情況下才釋出。

雖然這樣就能完成自動釋出,但是直接暴露了Github密碼是我們更加不能接受的。更好的做法是在Github -> settings -> Personal access tokens 生成一個只能訪問當前專案並只有讀取許可權的Github Access Token,並通過Travis CI將Access Token加密。聽起來有點繁瑣,好在Travis CLI中已經可以通過一行指令做好這一切:

travis setup release

根據提示填寫上述配置專案的資訊後,Travis CLI會自動在.travis.yml檔案中生成好所有的配置項:

deploy:
  provider: releases
  api_key:
    secure: XXX
  file: app/build/outputs/apk/app-release.apk
  skip_cleanup: true
  on:
    tags: true
    all_branches: true

其中api_key下的secure就是加密後的Access Token。

在執行travis setup release時有可能遇到

Invalid scheme format: git@github.com for a full error report, run travis report

這樣的報錯,看起來是Travis CLI還不支援通過金鑰訪問Github,因此可以將專案的源臨時切換為http形式,執行成功後再切換回來:

git remote set-url origin https://github.com/AlloVince/evandroid.git
git remote set-url origin git@github.com:AlloVince/evandroid.git

在實際部署過程中,發現釋出到Github Release比較坑的點是

git push
git push --tags

往往會同時生成2個Travis CI任務,但是在Travis網頁中預設介面只能看到最後跑的一個任務,而未打Tag的任務又會報

Skipping a deployment with the releases provider because this is not a tagged commit

這曾讓我一度以為自己的指令碼哪裡寫錯了,但是又找不到錯誤原因……

自動釋出APK到fir.im

自動釋出到Github對於開發人員已經足夠,但是考慮到專案實際需要以及國情,還是有必要選擇一個國內的App分發服務,fir.im、蒲公英都是不錯的選擇,不但允許遊客下載,還提供了二維碼等更適合對接手機的功能,國內下載速度也很快。由於fir.im提供了比較方便的CLI工具,因此本文以fir.im為例,在.travis.yml中新增以下幾行:

before_install:
- gem install fir-cli
after_deploy:
- fir p app/build/outputs/apk/app-release.apk -T $FIR_TOKEN -c "`git cat-file tag $TRAVIS_TAG`"

即在環境構建階段安裝fir-cli,在釋出成功後通過fir命令列工具將apk上傳到fir。

其中$FIR_TOKEN可以在fir.im的使用者->API Token中找到,然後在Travis CI控制檯中建立環境變數FIR_TOKEN並貼上即可。

這裡有個小技巧,如果我們僅僅上傳APK檔案到fir.im,看到連結的測試人員其實並不知道這次釋出所包含的變動,因此通過git cat-file tag $TRAVIS_TAG將當前釋出tag所包含的附加資訊一同上傳了。其中$TRAVIS_TAG變數是Travis CI每次執行自動附帶的環境變數,還有很多其他的Travis環境變數供我們玩出更多花樣。

釋出完畢後自動發郵件通知

雖然Travis CI也有通知功能,但不能定製模板,通知內容也僅僅為提示CI執行的結果,顯然更適合開發人員。我們還是希望最終能以更友好的方式通知團隊成員,同時考慮到郵件送達率,可以優先選擇如SubmailSendCloud等國內郵件傳送服務。

這裡以Submail為例,首先需要在Submail內建立郵件模板,比如我們可以建立這樣一封觸發式郵件模板:

Hi 親

@var(TRAVIS_REPO_SLUG)新版本@var(TRAVIS_TAG)已經發布了,功能更新:

@var(TAG_DESCRIPTION)

去下載:
http://fir.im/w13s

建立後可以得到郵件模板id,根據Submail手冊,將模板中所需要的變數置入,最終可以使用一行Curl指令傳送一封郵件:

after_deploy:- curl -d "appid=10948&to=allo.vince@gmail.com&subject=[自動通知] 安卓新版本$TRAVIS_TAG釋出&project=u2c0r2&signature=$SUBMAIL_SIGN&vars={\"TRAVIS_REPO_SLUG\":\"$TRAVIS_REPO_SLUG\",\"TRAVIS_TAG\":\"$TRAVIS_TAG\",\"TAG_DESCRIPTION\":\"$(git cat-file tag $TRAVIS_TAG | awk 1 ORS='<br>')\"}" https://api.submail.cn/mail/xsend.json

其中Submail用到的認證憑據signature同樣是通過Travis CI控制檯配置的。

總結

最終完成的示例專案在此。其實所有的yml檔案配置不到30行,就能省去繁瑣的日常工作,何樂而不為呢。最後回顧一下自動化後的日常工作:

提交程式碼:

git add .
git commit -m "這裡是註釋"
git push origin

打Tag

git tag -a v0.0.1-alpha.1 -m "這裡是Tag註釋,說清楚這個版本的主要改動,也可以省略-m引數直接寫長文字"
git push origin --tags

如果發現打錯了tag,可以刪除本地及遠端tag

git tag -d v0.0.1-alpha.1
git push origin --delete tag v0.0.1-alpha.1

大部分Tag標籤雖然僅用於內測,但是仍然建議允許版本語義化原則。

References

相關文章