在本系列的上篇文章中,我們講到了如何讀懂Gradle的語法,在本篇文章中,我們將講到增量式地構建項目。
請通過以下方式下載本系列文章的Github示例代碼:
git clone https://github.com/davenkin/gradle-learning.git
如果我們將Gradle的Task看作一個黑盒子,那么我們便可以抽象出輸入和輸出的概念,一個Task對輸入進行操作,然后產生輸出。比如,在使用java插件編譯源代碼時,輸入即為Java源文件,輸出則為class文件。如果多次執行一個Task時的輸入和輸出是一樣的,那么我們便可以認為這樣的Task是沒有必要重復執行的。此時,反復執行相同的Task是冗余的,並且是耗時的。
為了解決這樣的問題,Gradle引入了增量式構建的概念。在增量式構建中,我們為每個Task定義輸入(inputs)和輸入(outputs),如果在執行一個Task時,如果它的輸入和輸出與前一次執行時沒有發生變化,那么Gradle便會認為該Task是最新的(UP-TO-DATE),因此Gradle將不予執行。一個Task的inputs和outputs可以是一個或多個文件,可以是文件夾,還可以是Project的某個Property,甚至可以是某個閉包所定義的條件。
每個Task都擁有inputs和outputs屬性,他們的類型分別為TaskInputs和TaskOutputs。在下面的例子中,我們展示了這么一種場景:名為combineFileContent的Task從sourceDir目錄中讀取所有的文件,然后將每個文件的內容合並到destination.txt文件中。讓我們先來看看沒有定義Task輸入和輸出的情況:
task combineFileContentNonIncremental { def sources = fileTree('sourceDir') def destination = file('destination.txt') doLast { destination.withPrintWriter { writer -> sources.each {source -> writer.println source.text } } } }
多次執行“gradle combineFileContentNonIncremental”時,整個Task都會反復執行,即便在第一次執行后我們已經得到了所需的結果。如果該combineFileContentNonIncremental是一個繁重的Task,那么多次重復執行勢必造成沒必要的時間耗費。
這時,我們可以將sources聲明為該Task的inputs,而將destination聲明為outputs,重新創建一個Task如下:
task combineFileContentIncremental { def sources = fileTree('sourceDir') def destination = file('destination.txt') inputs.dir sources outputs.file destination doLast { destination.withPrintWriter { writer -> sources.each {source -> writer.println source.text } } } }
相比之下,后一個Task只比前一個Task多了兩行代碼:
inputs.dir sources
outputs.file destination
當首次執行combineFileContentIncremental時,Gradle會完整地執行該Task。但是緊接着再執行一次,命令行顯示:
:combineFileContentIncremental UP-TO-DATE BUILD SUCCESSFUL Total time: 2.104 secs
我們發現,combineFileContentIncremental被標記為UP-TO-DATE,表示該Task是最新的,Gradle將不予執行。在實際應用中,你將遇到很多這樣的情況,因為Gradle的很多插件都引入了增量式構建機制。
如果我們修改了inputs(即sourceDir文件夾)中的任何一個文件或刪除掉了destination.txt,當調用“gradle combineFileContentIncremental”時,Gradle又會重新執行,因為此時的Task已經不再是最新的了。對於outputs,我們還可以使用upToDateWhen()方法來決定一個Task的outputs是否為最新的,該方法接受一個閉包作為檢查條件,感興趣的讀者可以自行了解。
在下一篇文章中,我們將講到如何自定義Project的Property。