Android源碼依賴與自動(dòng)化提交

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)的依賴最新的該組件的最新版本。

所以本文意在解決如下問題:

  1. 動(dòng)態(tài)切換本地源碼依賴與maven依賴
  2. 自動(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的提交榄檬。

ComposingBuildDemo

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)目代碼:

ComposingBuildDemo

將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的源碼市咽。

WechatIMG239.png

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)目代碼:

ComposingBuildDemo

將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)目代碼:

ComposingBuildDemo

將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)目代碼:

ComposingBuildDemo

將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的功能。

先來分析一下思路:

  1. 通常提交git要敲多次命令纱兑,除了commit message呀闻,其他的操作基本是重復(fù)的,那么可以通過編寫一個(gè)腳本來完成整個(gè)git的提交
  2. 由于Maven的上傳是通過gradle的maven插件提供的uploadArchives任務(wù)來完成的潜慎,既然是Gradle Task捡多,那么我們可以將上面的git提交腳本封裝成一個(gè)task,在將兩個(gè)task關(guān)聯(lián)起來铐炫。

3.1 編寫git提交腳本

本節(jié)項(xiàng)目代碼:

ComposingBuildDemo

將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)目代碼:

ComposingBuildDemo

將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)目代碼:

ComposingBuildDemo

將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)如圖:

WechatIMG249.png

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è)文件分別為GitCommitShellTemplateGitCommitPropertiesTemplate琳状。

  • 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.propertiesgitcommit.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)完成提交。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末御蒲,一起剝皮案震驚了整個(gè)濱河市衣赶,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌厚满,老刑警劉巖府瞄,帶你破解...
    沈念sama閱讀 211,423評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異碘箍,居然都是意外死亡遵馆,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,147評論 2 385
  • 文/潘曉璐 我一進(jìn)店門敲街,熙熙樓的掌柜王于貴愁眉苦臉地迎上來团搞,“玉大人,你說我怎么就攤上這事多艇。” “怎么了像吻?”我有些...
    開封第一講書人閱讀 157,019評論 0 348
  • 文/不壞的土叔 我叫張陵峻黍,是天一觀的道長。 經(jīng)常有香客問我拨匆,道長姆涩,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,443評論 1 283
  • 正文 為了忘掉前任惭每,我火速辦了婚禮骨饿,結(jié)果婚禮上亏栈,老公的妹妹穿的比我還像新娘。我一直安慰自己宏赘,他們只是感情好绒北,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,535評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著察署,像睡著了一般闷游。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上贴汪,一...
    開封第一講書人閱讀 49,798評論 1 290
  • 那天脐往,我揣著相機(jī)與錄音,去河邊找鬼扳埂。 笑死业簿,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的阳懂。 我是一名探鬼主播辖源,決...
    沈念sama閱讀 38,941評論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼希太!你這毒婦竟也來了克饶?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,704評論 0 266
  • 序言:老撾萬榮一對情侶失蹤誊辉,失蹤者是張志新(化名)和其女友劉穎矾湃,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體堕澄,經(jīng)...
    沈念sama閱讀 44,152評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡邀跃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,494評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蛙紫。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拍屑。...
    茶點(diǎn)故事閱讀 38,629評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖坑傅,靈堂內(nèi)的尸體忽然破棺而出僵驰,到底是詐尸還是另有隱情,我是刑警寧澤唁毒,帶...
    沈念sama閱讀 34,295評論 4 329
  • 正文 年R本政府宣布蒜茴,位于F島的核電站,受9級特大地震影響浆西,放射性物質(zhì)發(fā)生泄漏粉私。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,901評論 3 313
  • 文/蒙蒙 一近零、第九天 我趴在偏房一處隱蔽的房頂上張望诺核。 院中可真熱鬧抄肖,春花似錦、人聲如沸窖杀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽陈瘦。三九已至幌甘,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間痊项,已是汗流浹背锅风。 一陣腳步聲響...
    開封第一講書人閱讀 31,978評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鞍泉,地道東北人纺念。 一個(gè)月前我還...
    沈念sama閱讀 46,333評論 2 360
  • 正文 我出身青樓桃移,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子兆龙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,499評論 2 348

推薦閱讀更多精彩內(nèi)容