Android源碼依賴與自動(dòng)化提交
1.背景
我們在日常的業(yè)務(wù)開發(fā)中嘱支,經(jīng)常會(huì)遇到這種情況间驮,組件化的業(yè)務(wù)項(xiàng)目漱病,眾多的業(yè)務(wù)組件以及工具組件浊洞,眾多的組件造成我們編譯運(yùn)行一次相當(dāng)耗時(shí),慢慢的洗搂,我們將基礎(chǔ)公共工具組件放到maven庫中消返,通過maven依賴的方式引入到業(yè)務(wù)中,但是如果嘗試修改某個(gè)工具組件的某個(gè)功能并且想在當(dāng)前的項(xiàng)目中調(diào)試就會(huì)非常復(fù)雜而且成本較高耘拇。
以修改一個(gè)底層工具庫舉例撵颊,通常的做法是修改底層工具類庫的代碼,如果工具庫有運(yùn)行環(huán)境惫叛,一般會(huì)在工具庫的運(yùn)行環(huán)境中看一下修改的運(yùn)行結(jié)果倡勇,然后提交到maven,項(xiàng)目再來修改對該工具庫最新版本的依賴嘉涌,再次運(yùn)行查看效果妻熊,如果效果不滿意,還要在重復(fù)一次上面的操作仑最。另外經(jīng)常還遇到一些開發(fā)不規(guī)范的小伙伴們扔役,只上傳了maven最新的版本,但是代碼忘記提交這種情況警医,使后面維護(hù)的小伙伴一臉懵逼亿胸。
而我們理想化的狀態(tài)是,我們在寫某個(gè)業(yè)務(wù)需求的時(shí)候预皇,只編譯當(dāng)前業(yè)務(wù)的組件侈玄,其他的業(yè)務(wù)組件或者公共工具組件使用maven依賴的方式引入,這樣可以降低整體的編譯時(shí)間吟温,但是如果需要調(diào)試到其他的業(yè)務(wù)或者公共組件的時(shí)候序仙,我們希望該組件能以源碼的方式引入,并且可以在當(dāng)前的項(xiàng)目運(yùn)行環(huán)境中debug調(diào)試鲁豪,修改源文件潘悼,與當(dāng)前的項(xiàng)目一起編譯運(yùn)行提升我們的開發(fā)效率。同時(shí)在開發(fā)完成時(shí)候爬橡,我們可以自動(dòng)提交代碼并上傳到maven挥等,項(xiàng)目也自動(dòng)的依賴最新的該組件的最新版本。
所以本文意在解決如下問題:
- 動(dòng)態(tài)切換本地源碼依賴與maven依賴
- 自動(dòng)提交代碼并上傳到maven
2. 動(dòng)態(tài)切換本地源碼依賴與Maven依賴
2.1 項(xiàng)目搭建
我們用兩個(gè)項(xiàng)目來模擬實(shí)際的業(yè)務(wù)組件與工具組件的關(guān)系堤尾。
首先創(chuàng)建ComposingBuildDemo目錄肝劲,在該目錄下創(chuàng)建兩個(gè)項(xiàng)目
- ComposingBuildApp 用來模擬業(yè)務(wù)組件
- ComposingBuildLibrary 用來模擬工具庫組件
在ComposingBuildLibrary項(xiàng)目中創(chuàng)建一個(gè)module,命名為composinglibrary
目前的結(jié)構(gòu)狀態(tài)如下
ComposingBuildDemo
├── ComposingBuildApp
└── ComposingBuildLibrary
└── composinglibrary
為了方便大家理解郭宝,我在github上面創(chuàng)建了兩個(gè)項(xiàng)目辞槐,大家最好fork到自己的github,這樣方便自己的測試和修改粘室,因?yàn)楹竺嫔婕暗絞it的提交榄檬。
clone下來項(xiàng)目以后,將tag切換到step1衔统,即是本章節(jié)的代碼示例鹿榜。
git checkout step1
2.2 includeBuild初試
要使用源碼的方式引入組件海雪,我們可以使用Gradle提供的一個(gè)功能Composing builds。
感興趣的同學(xué)可以先看一遍官方的說明Composing builds舱殿,不過官方的說明并沒有具體的例子奥裸,稍微有點(diǎn)點(diǎn)晦澀。簡單的解釋一下沪袭,通常使用的Gradle是一個(gè)項(xiàng)目里面有多個(gè)子項(xiàng)目湾宙,只有根項(xiàng)目中有一個(gè)setting.gradle文件,這種成為多項(xiàng)目構(gòu)建冈绊,而Composing builds是可以在一個(gè)完成的gradle項(xiàng)目中侠鳄,引入另一個(gè)完整gradle項(xiàng)目(也就是具有setting.gradle文件的完整),從而去依賴這個(gè)項(xiàng)目的構(gòu)建結(jié)果死宣,稱為復(fù)合構(gòu)建伟恶。
使用復(fù)合構(gòu)建也非常的簡單,以上一個(gè)章節(jié)的兩個(gè)項(xiàng)目為例毅该。
本節(jié)項(xiàng)目代碼:
將tag切換到step2知押,即是本章節(jié)的代碼示例。
2.2.1 配置ComposingBuildLibrary項(xiàng)目
首先修改ComposingBuildLibrary這個(gè)項(xiàng)目鹃骂,在composinglibrary這個(gè)module的build.gradle文件中添加代碼:
// 配置group和version 使項(xiàng)目可以用本地依賴的方式通過group + 項(xiàng)目名稱 + version的方式進(jìn)行依賴
group "me.xiba.lib"
version "1.0"
為了簡單的測試效果台盯,在composinglibrary中添加一個(gè)類文件,代碼如下:
public class Constant {
public static final String TAG = "ComposingLibrary";
}
在這定義一個(gè)常量畏线,讓ComposingBuildApp能夠應(yīng)用到這個(gè)常量静盅,就表示已經(jīng)添加了composinglibrary這個(gè)module的依賴。
2.2.2 在ComposingBuildApp項(xiàng)目中添加includeBuild
接下來修改ComposingBuildApp這個(gè)項(xiàng)目寝殴,在ComposingBuildApp項(xiàng)目的setting.gradle文件中蒿叠,添加代碼:
// 通過includeBuild可以引用到ComposingBuildLibrary項(xiàng)目,參數(shù)是ComposingBuildLibrary與當(dāng)前項(xiàng)目的相對路徑
includeBuild("../ComposingBuildLibrary")
Sync之后蚣常,可以看到項(xiàng)目中已經(jīng)添加了ComposingBuildLibrary的源碼市咽。
2.2.3 在ComposingBuildApp項(xiàng)目中引入composinglibrary模塊
編輯ComposingBuildApp項(xiàng)目中app目錄下的build.gradle文件,在dependencies中添加依賴:
dependencies {
......
// 通過setting.gradle 配置的 includeBuild 可以引用到composingLibrary的aar
implementation 'me.xiba.lib:composinglibrary:1.0'
......
}
編輯ComposingBuildApp項(xiàng)目中app目錄下的MainActivity文件抵蚊,嘗試引用Constant.TAG施绎,如果引用成功,則表示本地依賴成功贞绳。代碼如下:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 引入ComposingBuildLibrary的Constant.TAG
((TextView)findViewById(R.id.tv_content)).setText(Constant.TAG);
}
}
2.3 本地maven搭建
現(xiàn)在我們已經(jīng)可以通過includeBuild使用源碼依賴進(jìn)行符合構(gòu)建谷醉,接下來要實(shí)現(xiàn)maven依賴和本地依賴之間的切換。為了模擬maven冈闭,我們先將ComposingBuildLibrary項(xiàng)目發(fā)布到本地maven俱尼。
本節(jié)項(xiàng)目代碼:
將tag切換到step3,即是本章節(jié)的代碼示例萎攒。
2.3.1 添加ComposingBuildLibrary項(xiàng)目對Maven的配置
在composinglibrary這個(gè)module的build.gradle文件中添加代碼:
apply plugin: 'maven'
······
// 配置項(xiàng)目上傳Maven的相關(guān)信息
uploadArchives {
repositories.mavenDeployer {
// 本地倉庫路徑遇八,項(xiàng)目根目錄下的 repo 的文件夾為例
repository(url: uri('../../repo'))
// groupId
pom.groupId = 'me.xiba.lib'
// artifactId 為了與本地依賴區(qū)別矛绘,添加Maven后綴
pom.artifactId = 'composinglibraryMaven'
// 版本號
pom.version = '1.0'
}
}
sync之后,可以在右側(cè)的gradle中刃永,在composinglibrary/tasks/upload中货矮,找到uploadArchives任務(wù),雙擊執(zhí)行揽碘,執(zhí)行之后可以發(fā)現(xiàn)composingBuild根目錄下次屠,多了一個(gè)repo目錄园匹。
2.3.2 使用maven來引入ComposingBuildLibrary
首先修改ComposingBuildApp項(xiàng)目的setting.gradle文件雳刺,將includeBuild("../ComposingBuildLibrary")語句注釋掉。代碼如下:
// 通過includeBuild可以引用到ComposingBuildLibrary項(xiàng)目裸违,參數(shù)是ComposingBuildLibrary與當(dāng)前項(xiàng)目的相對路徑
//includeBuild("../ComposingBuildLibrary")
編輯ComposingBuildApp項(xiàng)目中app目錄下的build.gradle文件掖桦,添加本地倉庫的引用,同時(shí)修改dependencies供汛,使用maven的方式引入ComposingBuildLibrary枪汪。代碼如下:
// 添加本地Maven倉庫
repositories {
maven { url uri('../../repo') }
}
dependencies {
......
// 通過setting.gradle 配置的 includeBuild 可以引用到composingLibrary的aar
// implementation 'me.xiba.lib:composinglibrary:1.0'
// 通過Maven引用composingLibrary的aar
implementation 'me.xiba.lib:composinglibraryMaven:1.0'
......
}
sync之后,ComposingBuildLibrary項(xiàng)目的源碼已經(jīng)不在本地了怔昨,但是MainActivity依然可以引用到Constant.TAG雀久,這時(shí)已經(jīng)使用Maven進(jìn)行依賴編譯了。
2.4 添加substitute對maven依賴進(jìn)行替換
雖然現(xiàn)在可以實(shí)現(xiàn)了源碼依賴和Maven依賴趁舀,但是每次切換的時(shí)候赖捌,都要注釋掉對應(yīng)的代碼,然后打開另外一部分代碼矮烹,如果多個(gè)組件使用了同一個(gè)庫越庇,那么這個(gè)操作會(huì)更加麻煩。
接下來我們使用substitute特性用源碼替換maven依賴奉狈。
本節(jié)項(xiàng)目代碼:
將tag切換到step4卤唉,即是本章節(jié)的代碼示例。
修改ComposingBuildApp項(xiàng)目的setting.gradle文件仁期,代碼如下:
// 通過includeBuild可以引用到ComposingBuildLibrary項(xiàng)目桑驱,參數(shù)是ComposingBuildLibrary與當(dāng)前項(xiàng)目的相對路徑
// with project為"ComposingBuildLibrary"中的"composinglibrary"項(xiàng)目
includeBuild("../ComposingBuildLibrary") {
dependencySubstitution {
substitute module('me.xiba.lib:composinglibraryMaven') with project(':composinglibrary')
}
}
sync之后,ComposingBuildLibrary項(xiàng)目回到了本地跛蛋,MainActivity所引用的Constant.TAG是本地的源碼碰纬,但是ComposingBuildApp項(xiàng)目的build.gradle文件中添加的確實(shí)maven的項(xiàng)目地址,這就是substitue的一個(gè)功能问芬。
2.5 添加配置來動(dòng)態(tài)切換本地依賴與Maven依賴
目前為止悦析,我們能夠?qū)⒈镜卦创a替換Maven依賴來編譯項(xiàng)目,但是如果想要切換的話此衅,需要不斷的重復(fù)注釋掉settings.gradle里面所對應(yīng)的設(shè)置强戴,如果項(xiàng)目過多的話亭螟,settings.gradle會(huì)顯得冗長雜亂,這里我們選擇使用通過配置文件的方式來對項(xiàng)目進(jìn)行配置骑歹,然后通過在settings.gradle文件中添加一小段代碼來動(dòng)態(tài)添加includeBuild語句预烙。
本節(jié)項(xiàng)目代碼:
將tag切換到step5,即是本章節(jié)的代碼示例道媚。
2.5.1 添加配置文件
在ComposingBuildApp項(xiàng)目中添加composingConfig.gradle文件扁掸,內(nèi)容如下:
ext {
composing_dependencies = [
composing_library = [
isLocal : true, // 是否本地依賴
projectPath : "../ComposingBuildLibrary", // 項(xiàng)目的路徑
projectName : ":composinglibrary", // 項(xiàng)目名稱
projectMaven : "me.xiba.lib:composinglibraryMaven" // 項(xiàng)目的maven地址
]
]
}
在文件中對項(xiàng)目相關(guān)的信息進(jìn)行配置,如果是本地源碼依賴最域,就將isLocal修改為true谴分,maven依賴為false。
2.5.2 在settings.gradle中讀取配置
接下來修改ComposingBuildApp項(xiàng)目中的settings.gradle文件镀脂,內(nèi)容如下:
include ':app'
rootProject.name = "ComposingBuildApp"
// 引入composing的設(shè)置
apply from: "composingConfig.gradle"
//// 通過includeBuild可以引用到ComposingBuildLibrary項(xiàng)目牺蹄,參數(shù)是ComposingBuildLibrary與當(dāng)前項(xiàng)目的相對路徑
//// with project為"ComposingBuildLibrary"中的"composinglibrary"項(xiàng)目
//includeBuild("../ComposingBuildLibrary") {
// dependencySubstitution {
// substitute module('me.xiba.lib:composinglibraryMaven') with project(':composinglibrary')
// }
//}
// 遍歷composing_dependencies中的所有配置
ext.getProperty("composing_dependencies").each { projectConfig ->
// 如果是本地依賴
if (projectConfig["isLocal"]){
// 使用本地依賴進(jìn)行替換
includeBuild(projectConfig["projectPath"]) {
dependencySubstitution {
substitute module(projectConfig["projectMaven"]) with project(projectConfig["projectName"])
}
}
}
}
首先引入之前添加的composingConfig.gradle文件,遍歷composing_dependencies屬性下的所有設(shè)置薄翅,如果項(xiàng)目被設(shè)置為本地依賴沙兰,那么添加includeBuild的相關(guān)語句,使用源碼替換本地依賴翘魄。
如果想讓項(xiàng)目為本地源碼依賴鼎天,將對象項(xiàng)目的isLocal設(shè)置為true,不過要Sync之后才能生效暑竟。
3. 自動(dòng)化git提交與Maven上傳
開發(fā)過程中有的時(shí)候會(huì)因?yàn)椴僮鞑灰?guī)范斋射,出現(xiàn)git倉庫的代碼與maven不一致的問題,出現(xiàn)的原因大部分是因?yàn)樘峤涣舜a但是沒有上傳到Maven光羞,或者上傳了Maven但是沒有提交代碼的問題绩鸣。本章節(jié)主要完成一鍵提交git并上傳Maven的功能。
先來分析一下思路:
- 通常提交git要敲多次命令纱兑,除了commit message呀闻,其他的操作基本是重復(fù)的,那么可以通過編寫一個(gè)腳本來完成整個(gè)git的提交
- 由于Maven的上傳是通過gradle的maven插件提供的uploadArchives任務(wù)來完成的潜慎,既然是Gradle Task捡多,那么我們可以將上面的git提交腳本封裝成一個(gè)task,在將兩個(gè)task關(guān)聯(lián)起來铐炫。
3.1 編寫git提交腳本
本節(jié)項(xiàng)目代碼:
將tag切換到step6垒手,即是本章節(jié)的代碼示例。
在ComposingBuildLibrary目錄下添加一個(gè)shell文件倒信,命名為gitcommit.sh科贬,內(nèi)容如下:
#!/bin/bash
# 傳遞參數(shù)
if [ ! $1 ]
then
echo "########## 請輸入提交信息 ##########"
exit 1;
fi
echo "########## 開始提交 ##########"
echo "commitMessage: $1"
git add -A
# 獲取git status的結(jié)果
statusResult=`git status`
# 如果返回內(nèi)容包含'nothing to commit'說明沒有要返回的內(nèi)容,直接返回
if [[ $statusResult == *"nothing to commit"* ]]
then
echo "########## 沒有需要提交的內(nèi)容 ##########"
exit 1
fi
echo "########## 請輸入提交信息 ##########"
git commit -m "$1"
if [ $? -ne 0 ]
then
echo "git commit 錯(cuò)誤"
exit 1
fi
git fetch
if [ $? -ne 0 ]
then
echo "git fetch 錯(cuò)誤"
exit 1
fi
git rebase
if [ $? -ne 0 ]
then
echo "git rebase 錯(cuò)誤"
exit 1
fi
git push -u origin
if [ $? -ne 0 ]
then
echo "git push 錯(cuò)誤"
exit 1
fi
echo "########## 提交結(jié)束 ##########"
shell主要是接受了一個(gè)參數(shù)作為提交用的CommitMessage,這里使用的是fetch榜掌,rebase的流程优妙,如果使用其他的流程,可以自行修改腳本的內(nèi)容憎账。
執(zhí)行腳本套硼,需要在終端中進(jìn)入到composingBuildLibrary目錄下,輸入:
bash gitcommit.sh "commitMessage"
3.2 編寫Gradle task執(zhí)行腳本
本節(jié)項(xiàng)目代碼:
將tag切換到step7胞皱,即是本章節(jié)的代碼示例邪意。
編輯ComposingBuildLibrary/composinglibrary項(xiàng)目的build.gradle文件,添加一個(gè)task來執(zhí)行shell腳本反砌,代碼如下:
android {
······
}
// 自動(dòng)提交代碼的task
task gitcommit(type: Exec){
description("git push task")
doFirst {
println "gitpush task running"
// 執(zhí)行g(shù)itcommit.sh腳本
commandLine("bash", "../gitcommit.sh", "commitMessage")
}
doLast(){
println("gitpush task done")
}
}
dependencies {
······
}
這里有個(gè)細(xì)節(jié)要注意雾鬼,task的type要使用Exec類型,點(diǎn)這里查看Exec API于颖,Exec內(nèi)部封裝了執(zhí)行commandLine的操作呆贿,同時(shí)他也提供了切換工作目錄和獲取輸出結(jié)果的功能嚷兔。
sync之后森渐,可以在右側(cè)Gradle任務(wù)列表中找到gitcommit任務(wù),具體的路徑為:ComposingBuildLibrary/composinglibrary/Tasks/other/gitcommit冒晰。雙擊執(zhí)行任務(wù)同衣,即可出發(fā)提交。當(dāng)然也可以通過命令行的方式執(zhí)行壶运,命令如下:
./gradlew gitcommit
在執(zhí)行任務(wù)的過程中耐齐,commandLine的最后一個(gè)參數(shù)是提交信息,每次提交的時(shí)候都需要修改commitMessage蒋情,但是由于修改build.gradle會(huì)出發(fā)sync埠况,因此這里我們使用properties文件的方式來處理commitMessage,以避免出發(fā)build.gradle的sync棵癣。
在ComposingBuildLibrary/composinglibrary項(xiàng)目下添加文件辕翰,命名為gitcommit.properties,內(nèi)容如下:
commitMessage=add gitcommit.properties
接下來修改gitcommit task的代碼
android {
······
}
// 自動(dòng)提交代碼的task
task gitcommit(type: Exec){
description("git push task")
doFirst {
println "gitpush task running"
// 讀取配置文件
def composingProperties = new Properties()
composingProperties.load(new FileInputStream(file("./gitcommit.properties")))
// 執(zhí)行g(shù)itcommit.sh腳本
commandLine("bash", "../gitcommit.sh", composingProperties.get("commitMessage"))
}
doLast(){
println("gitpush task done")
}
}
dependencies {
······
}
主要是添加了對gitcommit.properties文件的加載狈谊,并讀取commitMessage屬性作為commandLine的參數(shù)喜命,這樣每次提交就避免了引發(fā)gradle sync。
3.3 在上傳Maven前執(zhí)行commit
本節(jié)項(xiàng)目代碼:
將tag切換到step8河劝,即是本章節(jié)的代碼示例壁榕。
現(xiàn)在我們有了可以提交git的Gradle task,那么如何將他與Maven的uploadArchives任務(wù)關(guān)聯(lián)起來呢赎瞎?
這里介紹兩種方式牌里,一種是使用dependsOn,另外一種是使用mustRunAfter务甥。
先貼出代碼牡辽,編輯ComposingBuildLibrary/composinglibrary項(xiàng)目的build.gradle文件贪染,內(nèi)容如下:
android {
······
}
// 自動(dòng)提交代碼的task
task gitcommit(type: Exec){
description("git push task")
doFirst {
println "gitpush task running"
// 讀取配置文件
def composingProperties = new Properties()
composingProperties.load(new FileInputStream(file("./gitcommit.properties")))
// 執(zhí)行g(shù)itcommit.sh腳本
commandLine("bash", "../gitcommit.sh", composingProperties.get("commitMessage"))
}
doLast(){
println("gitpush task done")
}
}
// 通過taskName獲取uploadArchives任務(wù)
Task uploadTask = project.tasks.getByName("uploadArchives")
// 如果用depensOn,那么uploadArchives每次執(zhí)行前都會(huì)先執(zhí)行g(shù)itcommit催享,無法獨(dú)立運(yùn)行
uploadTask.dependsOn(gitcommit)
// 如果想要uploadArchives更加獨(dú)立杭隙,也可以使用shouldRunAfter,這樣提交和上傳兩個(gè)task完全是獨(dú)立的
// 但是如果同時(shí)執(zhí)行兩個(gè)task的時(shí)候因妙,uploadArchives會(huì)在commit任務(wù)執(zhí)行完成之后執(zhí)行痰憎。
// 在終端輸入 ./gradlew gitcommit uploadArchives
uploadTask.mustRunAfter(gitcommit)
dependencies {
······
}
如果是使用dependsOn的話,代碼為:
uploadTask.dependsOn(gitcommit)
這句代碼的意思是攀涵,uploadTask每次執(zhí)行的時(shí)候铣耘,都會(huì)先執(zhí)行g(shù)itcommit。uploadTask無法獨(dú)立運(yùn)行以故,但是gitcommit可以獨(dú)立運(yùn)行蜗细。
使用dependsOn的方式,直接運(yùn)行uploadArchives
如果是使用mustRunAfter的話怒详,代碼為:
uploadTask.mustRunAfter(gitcommit)
這句代碼的意思是炉媒,uploadTask可以單獨(dú)運(yùn)行,gitcommit也可以單獨(dú)運(yùn)行昆烁,但是如果同時(shí)執(zhí)行兩個(gè)的話吊骤,那么uploadTask必須在gitCommit執(zhí)行完成之后再執(zhí)行。
使用mustRunAfter的方式静尼,需要在終端輸入:
./gradlew gitcommit uploadArchives
兩種方式可以按照需求二選一白粉。
3.4 將commit task封裝為插件
目前我們雖然打通了主流程的功能,但是還是有一些細(xì)節(jié)沒有處理鼠渺,比如說如果沒有g(shù)itcommit.properties文件怎么辦鸭巴?如果gitcommit執(zhí)行出錯(cuò),但是依然上傳了maven怎么辦拦盹?如果想要在所有組件都添加gitcommit鹃祖,是不是要不斷的復(fù)制粘貼代碼?
為了解決上述問題掌敬,本節(jié)我們來編輯一個(gè)Gradle Plugin來解決惯豆。
3.4.1 創(chuàng)建Gradle Plugin
為了避免每個(gè)項(xiàng)目都貼一份相同的代碼,我們開發(fā)一個(gè)gradle plugin奔害,在插件中創(chuàng)建對應(yīng)的提交task楷兽。
創(chuàng)建Gradle Plugin有幾種方式,為了方便演示华临,這里選擇使用buildSrc的方式創(chuàng)建Gradle Plugin芯杀。當(dāng)然大家也可以選擇創(chuàng)建一個(gè)獨(dú)立的Gradle Plugin項(xiàng)目。
關(guān)于如何創(chuàng)建編寫一個(gè)Gradle Plugin,可以參考官方的文檔Developing Custom Gradle Plugins揭厚。
在ComposingBuildLibrary項(xiàng)目中創(chuàng)建buildSrc目錄却特,在buildSrc目錄中添加git_commit_plugin目錄、build.gradle文件筛圆、setting.gradle文件裂明。
setting.gradle文件內(nèi)容:
// buildSrc中,文件目錄的名稱
include "git_commit_plugin"
build.gradle文件內(nèi)容:
subprojects {
apply plugin: "groovy"
apply plugin: 'java-gradle-plugin'
apply plugin: 'kotlin'
sourceCompatibility = "1.8"
targetCompatibility = "1.8"
dependencies {
implementation localGroovy()
implementation gradleApi()
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.72"
implementation 'com.android.tools.build:gradle:4.0.1'
}
rootProject.dependencies {
runtime project(path)
}
}
allprojects {
repositories {
google()
jcenter()
}
}
apply plugin: 'java-gradle-plugin'
apply plugin: 'kotlin'
buildscript {
ext.kotlin_version = '1.3.72'
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
repositories {
mavenCentral()
}
dependencies {
compileOnly gradleApi()
compileOnly localGroovy()
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
}
由于代碼是使用的kotlin太援,所以加了很多kotlin引入相關(guān)的配置闽晦。
composing_plugin目錄下主要有兩部分,一個(gè)是源碼部分提岔,一個(gè)是resources的配置部分仙蛉。
由于使用的是kotlin進(jìn)行編輯,所以源碼部分創(chuàng)建目錄/src/main/java碱蒙,在java目錄下創(chuàng)建包/me/xiba/gitcommit/plugin荠瘪,在包下添加kt文件
- GitCommitPlugin:用于在項(xiàng)目的build.gradle中添加gitcommit任務(wù)
后面我們在來介紹文件的內(nèi)容。
而resources的目錄有一點(diǎn)點(diǎn)講究赛惩,要在/main目錄下創(chuàng)建/resources/META-INF/gradle-plugins/me.xiba.gitcommit.properties文件哀墓,其中me.xiba.gitcommit就是引入插件時(shí)使用的名稱,使用apply plugin: "me.xiba.gitcommit" 就可以使用該插件坊秸。
me.xiba.gitcommit.properties文件內(nèi)容如下:
implementation-class=me.xiba.gitcommit.plugin.GitCommitPlugin
整體的項(xiàng)目結(jié)構(gòu)如圖:
3.4.2 添加文件模板
如果一個(gè)項(xiàng)目引入了插件但是沒有創(chuàng)建相關(guān)的gitcommit.properties文件和gitcommit.sh文件麸祷,會(huì)有些尷尬澎怒,為了避免此類場景褒搔,我們在gradle的準(zhǔn)備階段,檢測項(xiàng)目中是否有相關(guān)的文件喷面,如果沒有星瘾,使用模板創(chuàng)建相關(guān)的文件。
首先創(chuàng)建包/me/xiba/gitcommit/filetemplate惧辈,在包下創(chuàng)建兩個(gè)文件分別為GitCommitShellTemplate和GitCommitPropertiesTemplate琳状。
- GitCommitShellTemplate:gitcommit.sh文件的模板
- GitCommitPropertiesTemplate:gitcommit.properties文件的模板
GitCommitShellTemplate的代碼如下:
package me.xiba.gitcommit.filetemplate
/**
* @Description: gitcommit.sh模板文本
*/
class GitCommitShellTemplate {
companion object {
const val GIT_COMMIT_SHELL_TEMPLATE = "#!/bin/bash\n" +
"\n" +
"# 傳遞參數(shù)\n" +
"\n" +
"if [ ! \$1 ]\n" +
"then\n" +
" echo \"########## 請輸入提交信息 ##########\"\n" +
" exit 1;\n" +
"fi\n" +
"\n" +
"echo \"########## 開始提交 ##########\"\n" +
"\n" +
"echo \"commitMessage: \$1\"\n" +
"\n" +
"git add -A\n" +
"\n" +
"# 獲取git status的結(jié)果\n" +
"statusResult=`git status`\n" +
"\n" +
"# 如果返回內(nèi)容包含'nothing to commit'說明沒有要返回的內(nèi)容,直接返回\n" +
"if [[ \$statusResult == *\"nothing to commit\"* ]]\n" +
"then\n" +
" echo \"########## 沒有需要提交的內(nèi)容 ##########\"\n" +
" exit 1\n" +
"fi\n" +
"\n" +
"echo \"########## 請輸入提交信息 ##########\"\n" +
"\n" +
"git commit -m \"\$1\"\n" +
"\n" +
"if [ \$? -ne 0 ]\n" +
"then\n" +
" echo \"git commit 錯(cuò)誤\"\n" +
" exit 1\n" +
"fi\n" +
"\n" +
"git fetch\n" +
"if [ \$? -ne 0 ]\n" +
"then\n" +
" echo \"git fetch 錯(cuò)誤\"\n" +
" exit 1\n" +
"fi\n" +
"\n" +
"git rebase\n" +
"if [ \$? -ne 0 ]\n" +
"then\n" +
" echo \"git rebase 錯(cuò)誤\"\n" +
" exit 1\n" +
"fi\n" +
"\n" +
"git push -u origin\n" +
"if [ \$? -ne 0 ]\n" +
"then\n" +
" echo \"git push 錯(cuò)誤\"\n" +
" exit 1\n" +
"fi\n" +
"\n" +
"echo \"########## 提交結(jié)束 ##########\""
}
}
GitCommitPropertiesTemplate的代碼如下:
package me.xiba.gitcommit.filetemplate
/**
* @Description: gitcommit.properties模板文本
*/
class GitCommitPropertiesTemplate {
companion object{
const val GIT_COMMIT_PROPERTIES_TEMPLATE = "#\n" +
"commitMessage=edit your commit message"
}
}
接下來開始編寫插件盒齿,這里直接貼出GitCommitPlugin的代碼:
package me.xiba.gitcommit.plugin
import me.xiba.gitcommit.filetemplate.GitCommitPropertiesTemplate.Companion.GIT_COMMIT_PROPERTIES_TEMPLATE
import me.xiba.gitcommit.filetemplate.GitCommitShellTemplate.Companion.GIT_COMMIT_SHELL_TEMPLATE
import org.gradle.api.Plugin
import org.gradle.api.Project
import java.io.BufferedWriter
import java.io.File
import java.io.OutputStreamWriter
/**
* @Description: git提交插件
*/
class GitCommitPlugin : Plugin<Project> {
companion object{
const val FILENAME_GIT_COMMIT_SHELL = "gitcommit.sh"
const val FILENAME_GIT_COMMIT_PROPERTIES = "gitcommit.properties"
}
override fun apply(project: Project) {
println("CommitUploadPlugin: ---------------------")
// 判斷是否有g(shù)itcommit.sh念逞,如果沒有創(chuàng)建文件
// 由于shell文件基本一致,只需要生成一份即可边翁,所以生成在 項(xiàng)目的根目錄下
var gitCommitFile = File("${project.rootProject.projectDir}/$FILENAME_GIT_COMMIT_SHELL")
checkAndGenerateFile(gitCommitFile, GIT_COMMIT_SHELL_TEMPLATE, FILENAME_GIT_COMMIT_SHELL)
// 判斷是否有g(shù)itcommit.properties翎承,如果沒有創(chuàng)建文件
// 由于項(xiàng)目使用的是同一個(gè)代碼倉庫,所以也可以使用同一個(gè)gitcommit.properties文件
// 可自行按照需求確定位置
var gitCommitProperties = File("${project.rootProject.projectDir}/$FILENAME_GIT_COMMIT_PROPERTIES")
checkAndGenerateFile(gitCommitProperties, GIT_COMMIT_PROPERTIES_TEMPLATE, FILENAME_GIT_COMMIT_PROPERTIES)
}
/**
* 檢測文件是否存在符匾,如果不存在叨咖,用模板生成文件
*/
private fun checkAndGenerateFile(file: File, template: String, fileName: String){
// 判斷文件是否存在,如果不存在,使用模板創(chuàng)建文件
if (!file.exists()){
println("CommitUploadPlugin: 正在創(chuàng)建${fileName}文件")
// 使用模板創(chuàng)建文件
file.createNewFile()
// 為了避免中文亂碼
var gitCommitPropertiesFileWriter = BufferedWriter(
OutputStreamWriter(
file.outputStream(), "UTF-8")
)
gitCommitPropertiesFileWriter.write(template)
gitCommitPropertiesFileWriter.flush()
gitCommitPropertiesFileWriter.close()
}
}
}
接下來引入插件甸各,在/ComposingBuildLibrary/composinglibrary項(xiàng)目的build.gradle文件中垛贤,添加apply plugin: 'me.xiba.gitcommit'即可引入插件。Sync之后趣倾,如果之前沒有gitcommit.properties和gitcommit.sh文件的話聘惦,會(huì)生成文件,可以先將兩個(gè)文件刪除儒恋,然后Sync即可生成部凑。這里說明一下,其實(shí)使用gitcommit.sh是因?yàn)槊總€(gè)提交流程不太一樣碧浊,而且有些提交可能還需要添加reviewer涂邀,所以為了方便配置,使用shell文本的方式箱锐,這樣可以根據(jù)自己的流程修改shell的內(nèi)容比勉,但是理論上使用gradle task完全可以替代插件,只是靈活度不高驹止。
3.4.3 生成gitcommit task
GitCommitPlugin添加一個(gè)方法createCommitTask用來創(chuàng)建提交任務(wù)浩聋,代碼如下:
companion object{
const val FILENAME_GIT_COMMIT_SHELL = "gitcommit.sh"
const val FILENAME_GIT_COMMIT_PROPERTIES = "gitcommit.properties"
const val TASKNAME_COMMIT_UPLOAD = "gitcommit"
const val PROPERTIES_KEY_COMMIT_MESSAGE = "commitMessage"
}
/**
* 創(chuàng)建提交任務(wù)
* 使用命令行執(zhí)行shell文件
*/
private fun createCommitTask(project: Project): Task {
// 創(chuàng)建任務(wù)
var task = project.tasks.create("${project.name}_$TASKNAME_COMMIT_UPLOAD", Exec::class.java)
// 任務(wù)的Group
task.group = "GitCommit"
// 創(chuàng)建輸出流,用來讀取命令行的輸出
var out = ByteArrayOutputStream()
// 執(zhí)行前的設(shè)置
task.doFirst {
// 讀取properties文件臊恋,獲取commitMessage
var gitCommitPropertiesFile = File("${project.rootProject.projectDir}/$FILENAME_GIT_COMMIT_PROPERTIES")
// 創(chuàng)建一個(gè)Properties
var gitCommitProperties = Properties()
// 如果文件存在 讀取文件內(nèi)容到gitCommitProperties
if (gitCommitPropertiesFile.exists()){
// 避免中文亂碼
gitCommitProperties.load(
InputStreamReader(gitCommitPropertiesFile.inputStream(), "UTF-8")
)
} else {
println("You need create composing.properties file in your project dir!")
}
// 獲取commitMessage
var commitMessage = gitCommitProperties.getProperty(PROPERTIES_KEY_COMMIT_MESSAGE)
// 設(shè)置工作目錄
task.workingDir(project.projectDir)
// 設(shè)置非0 依然正常運(yùn)行
task.isIgnoreExitValue = true
// 設(shè)置命令行輸出
task.standardOutput = out
// 執(zhí)行提交腳本
task.commandLine("bash", FILENAME_GIT_COMMIT_SHELL, commitMessage)
}
// 執(zhí)行后的設(shè)置
task.doLast {
// 獲取退出值
println("CommitUploadPlugin: ${project.name}_$TASKNAME_COMMIT_UPLOAD : 執(zhí)行結(jié)果: ${task.execResult?.exitValue}")
// 獲取命令行的輸出
println("CommitUploadPlugin: ${project.name}_$TASKNAME_COMMIT_UPLOAD : 執(zhí)行輸出: \n\n${out.toString("UTF-8")}")
// 如果腳本返回非0衣洁,異常退出
if (task.execResult?.exitValue != 0){
throw Exception("exitValue not 0")
}
}
return task
}
代碼很簡單,首先創(chuàng)建了一個(gè)Exec類型的task抖仅,并添加分組GitCommit坊夫,這樣右側(cè)的gradle的任務(wù)列表中,就會(huì)多一個(gè)GitCommit的分組撤卢;讀取gitcommit.properties文件的commitMessage屬性环凿,獲取提交內(nèi)容,然后執(zhí)行腳本放吩。
注意這里我們通過task.execResult?.exitValue得到了shell執(zhí)行的結(jié)果智听,一般0為執(zhí)行成功推出,而非0則視為異常退出渡紫。在gitcommit.sh腳本中到推,如果操作不成功,則會(huì)使用exit 1的方式退出腳本惕澎,其中的'1'就是執(zhí)行結(jié)果莉测,在任務(wù)的doLast中,獲取任務(wù)的執(zhí)行結(jié)果集灌,如果是非0悔雹,那么拋出異常复哆,這樣,后續(xù)的uploadArchives任務(wù)也不會(huì)得到執(zhí)行腌零。
最后在apply方法中調(diào)用創(chuàng)建任務(wù)的方法梯找,并與uploadArchives任務(wù)關(guān)聯(lián):
override fun apply(project: Project) {
println("CommitUploadPlugin: ---------------------")
// 判斷是否有g(shù)itcommit.sh,如果沒有創(chuàng)建文件
// 由于shell文件基本一致益涧,只需要生成一份即可锈锤,所以生成在 項(xiàng)目的根目錄下
var gitCommitFile = File("${project.rootProject.projectDir}/$FILENAME_GIT_COMMIT_SHELL")
checkAndGenerateFile(gitCommitFile, GIT_COMMIT_SHELL_TEMPLATE, FILENAME_GIT_COMMIT_SHELL)
// 判斷是否有g(shù)itcommit.properties,如果沒有創(chuàng)建文件
// 由于項(xiàng)目使用的是同一個(gè)代碼倉庫闲询,所以也可以使用同一個(gè)gitcommit.properties文件
// 可自行按照需求確定位置
var gitCommitProperties = File("${project.rootProject.projectDir}/$FILENAME_GIT_COMMIT_PROPERTIES")
checkAndGenerateFile(gitCommitProperties, GIT_COMMIT_PROPERTIES_TEMPLATE, FILENAME_GIT_COMMIT_PROPERTIES)
// 創(chuàng)建提交任務(wù)
var commitTask = createCommitTask(project)
// 如果用depensOn久免,那么uploadArchives每次執(zhí)行都會(huì)出發(fā)提交,無法獨(dú)立運(yùn)行
project.tasks.getByName("uploadArchives").dependsOn(commitTask)
// // 如果想要uploadArchives更加獨(dú)立扭弧,也可以使用shouldRunAfter阎姥,這樣提交和上傳兩個(gè)task完全是獨(dú)立的
// // 但是如果同時(shí)執(zhí)行兩個(gè)task的時(shí)候,uploadArchives會(huì)在commit任務(wù)執(zhí)行完成之后執(zhí)行鸽捻。
// // 在終端輸入 ./gradlew composinglibrary_commitUpload uploadArchives
// var uploadTask = project.tasks.getByName("uploadArchives")
// uploadTask.mustRunAfter(commitTask)
}
現(xiàn)在執(zhí)行uploadArchives任務(wù)的時(shí)候呼巴,會(huì)自動(dòng)完成提交。