最近付費購買了Travis CI爸业,Travis CI的收費模式很有意思,不是按項目或者用戶亏镰,而是按工作進程收費扯旷,比如初級版本是$129/月,總共提供2個工作進程索抓。在項目不多的情況下钧忽,除了用于跑單元測試外,不免想利用的更充分一些逼肯,因此抽空搭建了一套基于Travis CI的Android自動發(fā)布工作流耸黑。
未自動化前安卓開發(fā)總是避免不了這樣的工作流程:
- 開發(fā)一些新功能,提交代碼
- 完成一部分功能后篮幢,打包一個測試版APK
- 將測試版APK上傳到QQ群 / 網(wǎng)盤 / Fir.im / 蒲公英等
- 在QQ群或發(fā)布平臺解釋當(dāng)前版本所完成的功能
- 通知測試人員測試
實現(xiàn)了這套自動化發(fā)布后大刊,工作流程被簡化成:
- 開發(fā)新功能,提交代碼
- 通過
git tag
對代碼打一個內(nèi)測版的tag三椿,在tag的描述中對寫當(dāng)前完成的功能
Tag提交后Travis CI會自動編譯代碼缺菌,生成APK文件并分發(fā)到Github和fir.im,Github和fir.im中會保持Tag的描述信息搜锰,分發(fā)完成后會有郵件通知所有參與測試的人員伴郁。而作為開發(fā)人員,只需要專注于對代碼打好一個Tag就可以了蛋叼。
整個流程看似做了不少工作焊傅,其實體現(xiàn)在Travis CI只有數(shù)行指令而已,以下逐一講解:
對安卓項目啟用Travis CI
Travis CI應(yīng)該可以算是目前最好用的持續(xù)集成服務(wù)之一了,如果代碼庫是基于Github的話租冠,可以很簡單的開啟鹏倘。由于本文涉及到了很多Travis CI的基礎(chǔ)概念,建議首先對Travis CI的自定義構(gòu)建一節(jié)有所了解顽爹。
很早前在介紹PHP項目的持續(xù)集成時也寫過如何在PHP項目中使用Travis CI。 對于安卓項目來說步驟幾乎一致:
首先準備一個.travis.yml
文件放在安卓項目根目錄下骆姐,.travis.yml
中記錄了Travis CI所需的基礎(chǔ)信息:
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)等同规。
當(dāng)Travis CI準備好我們所需要的環(huán)境后,將自動運行yml文件script
部分所設(shè)置的指令窟社,上例中運行的是./gradlew assembleRelease
券勺,運行成功的話會在項目的主模塊下生成build/outputs/apk/app-release.apk
。
最后進入Travis CI主頁灿里,使用有項目Admin權(quán)限的Github帳號直接登錄关炼。選擇要開啟Travis CI的項目,將右邊的開關(guān)設(shè)為On即可匣吊。
Travis CI目前有2個網(wǎng)站:如果是開源項目儒拂,直接進入travis-ci.org即可,如果是私有付費項目色鸳,則需要進入travis-ci.com社痛,2個網(wǎng)站除了域名外所有的界面及操作幾乎一模一樣。
配置中還有一行sudo: false
命雀,是為了開啟基于容器的Travis CI任務(wù)蒜哀,讓編譯效率更高。
安卓自動化構(gòu)建的密碼和證書安全
安卓項目發(fā)布需要證書文件和若干密碼咏雌,但無論是開源項目還是私有項目凡怎,任何時候都不應(yīng)該將原始證書或密碼放入代碼庫(原則上來講證書和密碼也不應(yīng)該交于開發(fā)人員,而應(yīng)該只能通過發(fā)布服務(wù)器進行編譯)赊抖。Travis CI為此提供了2種解決方案统倒,一種是對敏感信息、密碼氛雪、證書等進行對稱加密房匆,在CI構(gòu)建環(huán)境時解密,另一種是將密碼等通過Travis CI的控制臺(即網(wǎng)站)設(shè)置為構(gòu)建時的環(huán)境變量。
由于前者會在Travis控制臺生成一對環(huán)境變量浴鸿,所以我的做法是盡量選擇后者井氢,但由于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
}
}
}
而我們最終要的效果,還是希望一份編譯腳本既可以用于開發(fā)環(huán)境苗分,也可以在CI環(huán)境下使用厌蔽,在Travis CI中,可以通過點擊項目名稱 -> Settings -> Environment Variables中設(shè)置環(huán)境變量摔癣,比如我們可以針對上面的配置奴饮,分別設(shè)置KEYSTORE_PASS
、ALIAS_NAME
择浊、ALIAS_PASS
三個環(huán)境變量戴卜,在Travis CI環(huán)境下可以通過System.getenv()
獲得這些環(huán)境變量。
本地開發(fā)環(huán)境中近她,我的做法是將這幾個變量加到gradle.properties
文件中叉瘩,這樣就可以在build.gradle
內(nèi)直接使用了。下面是開發(fā)環(huán)境的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
指令會做幾件事情:
- 在Travis CI控制臺自動生成一對密鑰灸撰,形如:
encrypted_e41864bb9dab_key
,encrypted_e41864bb9dab_iv
- 基于密鑰通過
openssl
對文件進行加密,上例中會項目根目錄生成evandroid.jks.enc
文件 - 在
.travis.yml
中自動生成Travis CI環(huán)境下解密文件的配置拼坎,上例運行后可以看到.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默認在項目根目錄下運行浮毯,因此注意根據(jù)實際需求調(diào)整enc文件的路徑。
最后別忘了在.gitignore
中忽略keys/evandroid.jks
以及gradle.properties
并在代碼庫中將其刪除泰鸡。
Travis CI自動發(fā)布安卓apk文件到Github Release
Travis CI的script
部分運行成功后债蓝,可以通過配置文件進入到發(fā)布階段。下面是一個Travis CI發(fā)布的示例:
deploy:
provider: releases
user: "GITHUB USERNAME"
password: "GITHUB PASSWORD"
file: app/build/outputs/apk/app-release.apk
skip_cleanup: true
on:
tags: true
這個例子中配置了這樣一些內(nèi)容:
-
provider
:發(fā)布目標為Github Release盛龄,除了Github外饰迹,Travis CI還支持發(fā)布到AWS芳誓、Google App Engine等數(shù)十種provider - Github用戶名和密碼,因為Travis CI要上傳APK文件啊鸭,因此需要有Github項目的寫入權(quán)限
-
file
: 發(fā)布文件锹淌,輸入文件路徑即可 -
skip_cleanup
: 默認情況下Travis CI在完成編譯后會清除所有生成的文件,因此需要將skip_cleanup
設(shè)置為true來忽略此操作赠制。 -
on
: 發(fā)布的時機赂摆,這里配置為tags: true
,即只在有tag
的情況下才發(fā)布钟些。
雖然這樣就能完成自動發(fā)布库正,但是直接暴露了Github密碼是我們更加不能接受的。更好的做法是在Github -> settings -> Personal access tokens 生成一個只能訪問當(dāng)前項目并只有讀取權(quán)限的Github Access Token厘唾,并通過Travis CI將Access Token加密。聽起來有點繁瑣龙誊,好在Travis CLI中已經(jīng)可以通過一行指令做好這一切:
travis setup release
根據(jù)提示填寫上述配置項目的信息后抚垃,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
在實際部署過程中逊朽,發(fā)現(xiàn)發(fā)布到Github Release比較坑的點是
git push
git push --tags
往往會同時生成2個Travis CI任務(wù)罕伯,但是在Travis網(wǎng)頁中默認界面只能看到最后跑的一個任務(wù),而未打Tag的任務(wù)又會報
Skipping a deployment with the releases provider because this is not a tagged commit
這曾讓我一度以為自己的腳本哪里寫錯了叽讳,但是又找不到錯誤原因……
自動發(fā)布APK到fir.im
自動發(fā)布到Github對于開發(fā)人員已經(jīng)足夠追他,但是考慮到項目實際需要以及國情,還是有必要選擇一個國內(nèi)的App分發(fā)服務(wù)岛蚤,fir.im邑狸、蒲公英都是不錯的選擇,不但允許游客下載涤妒,還提供了二維碼等更適合對接手機的功能单雾,國內(nèi)下載速度也很快。由于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`"
即在環(huán)境構(gòu)建階段安裝fir-cli,在發(fā)布成功后通過fir命令行工具將apk上傳到fir贿讹。
其中$FIR_TOKEN
可以在fir.im的用戶->API Token中找到渐逃,然后在Travis CI控制臺中創(chuàng)建環(huán)境變量FIR_TOKEN
并粘貼即可。
這里有個小技巧围详,如果我們僅僅上傳APK文件到fir.im朴乖,看到鏈接的測試人員其實并不知道這次發(fā)布所包含的變動祖屏,因此通過git cat-file tag $TRAVIS_TAG
將當(dāng)前發(fā)布tag所包含的附加信息一同上傳了。其中$TRAVIS_TAG
變量是Travis CI每次運行自動附帶的環(huán)境變量买羞,還有很多其他的Travis環(huán)境變量供我們玩出更多花樣袁勺。
發(fā)布完畢后自動發(fā)郵件通知
雖然Travis CI也有通知功能,但不能定制模板畜普,通知內(nèi)容也僅僅為提示CI運行的結(jié)果期丰,顯然更適合開發(fā)人員。我們還是希望最終能以更友好的方式通知團隊成員吃挑,同時考慮到郵件送達率钝荡,可以優(yōu)先選擇如Submail、SendCloud等國內(nèi)郵件發(fā)送服務(wù)舶衬。
這里以Submail為例埠通,首先需要在Submail內(nèi)創(chuàng)建郵件模板,比如我們可以創(chuàng)建這樣一封觸發(fā)式郵件模板:
Hi 親
@var(TRAVIS_REPO_SLUG)新版本@var(TRAVIS_TAG)已經(jīng)發(fā)布了逛犹,功能更新:
@var(TAG_DESCRIPTION)
去下載:
http://fir.im/w13s
創(chuàng)建后可以得到郵件模板id端辱,根據(jù)Submail手冊,將模板中所需要的變量置入虽画,最終可以使用一行Curl指令發(fā)送一封郵件:
after_deploy:- curl -d "appid=10948&to=allo.vince@gmail.com&subject=[自動通知] 安卓新版本$TRAVIS_TAG發(fā)布&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用到的認證憑據(jù)signature同樣是通過Travis CI控制臺配置的舞蔽。
總結(jié)
最終完成的示例項目在此。其實所有的yml文件配置不到30行码撰,就能省去繁瑣的日常工作渗柿,何樂而不為呢。最后回顧一下自動化后的日常工作:
提交代碼:
git add .
git commit -m "這里是注釋"
git push origin
打Tag
git tag -a v0.0.1-alpha.1 -m "這里是Tag注釋脖岛,說清楚這個版本的主要改動朵栖,也可以省略-m參數(shù)直接寫長文本"
git push origin --tags
如果發(fā)現(xiàn)打錯了tag,可以刪除本地及遠程tag
git tag -d v0.0.1-alpha.1
git push origin --delete tag v0.0.1-alpha.1
大部分Tag標簽雖然僅用于內(nèi)測鸡岗,但是仍然建議允許版本語義化原則混槐。