之前只會用 make,第一次接觸 gradle 時,明顯感受和 make 的套路很不同。若是說 make 是通用構建系統,那 gradle 構建腳本第一眼看上去甚至感受跟構建毫無關聯。html
本文章解決的疑問: 對比 make/ant, 爲何 gradle 被稱爲構建系統以及和其餘構建系統的類似之處。java
make 是一個很是古老且經久不衰的構建系統。shell
make 的關鍵: shell/文件。apache
make 是基於 shell 的構建系統。api
make 的規則很是簡單,一個簡單的用法就是把長命令精簡爲短命令. 下面是一個簡單的例子。bash
mycli: echo "this is a very very long command!" echo "this is another very very long command!"
將上面的內容保存到當前目錄的 makefile 文件,而後測試如下命令。jvm
$ make mycli echo "this is a very very long command!" this is a very very long command! echo "this is another very very long command!" this is another very very long command!
這種用法下, make 至關於給一組命令提供了快捷方式,當執行 make <shortcut>
的時候,
就會執行對應的命令,並且會把執行的命令打印出來。
不過通常狀況下,這樣回顯命令不太好看,能夠加上 -s
取消回顯。
(在每條命令前面加上 @ , 也能夠防止命令回顯)maven
$ make -s mycli this is a very very long command! this is another very very long command!
這個例子中 mycli
能夠看做是make 的 目標
.ide
聲明目標時,能夠冒號後面聲明依賴的子目標。
語法以下:函數
<target>: <subtarget1> <subtarget2> command1 command2 ... <subtarget1>: ... ...
上面聲明的 target 依賴了 subtarget1/subtarget2
make 真正的做用是做爲構建系統,而 make 的目標有另一層含義,
目標通常來講並非一個簡單的字符串,而是一個文件名。
下面看一個真實的例子:
# 過濾出 test.txt 裏面全部非空行, 生成 content.txt content.txt: test.txt grep -E ".+" test.txt > content.txt
測試:
$ make content.txt grep -E ".+" test.txt > content.txt $ make content.txt make: 「content.txt」已經是最新。
能夠看到,第一次生成 content.txt 後,第二次執行 make 會跳過命令,
它的原理是比較目標和依賴的時間戳,只有發現依賴(test.txt)更新的狀況下
纔會執行下面的 grep 命令生成(content.txt)目標。
即 make 經過文件系統時間戳來表示構建目標的狀態。
這另外一個例子,組合了上面全部特性。
backup.tar.gz: content.txt tar -zcvf backup.tar.gz userlist.txt content.txt: test.txt grep -E '.*' text.txt > content.txt
apache ant 是一個相似 make 的構建系統,主要優點在於使用 java 編寫,跨平臺。
ant 與 make 對比
_ | make | ant |
---|---|---|
目標 | 文件 | 字符串 |
腳本 | makefile | xml |
指令 | shell | java class |
一個簡單的 ant 腳本(build.xml):
<project name="MyProject"> <target name="MyTarget" depends="SubTarget"> <echo message="This is target message." level="error"/> </target> <target name="SubTarget"> <echo message="This is sub-target message1." /> <echo message="This is sub-target message2." /> </target> </project>
運行效果:
$ ant MyTarget Buildfile: D:\sphinx\record\docs\gradle\build.xml SubTarget: [echo] This is sub-target message1. [echo] This is sub-target message2. MyTarget: [echo] This is target message. BUILD SUCCESSFUL Total time: 0 seconds
這個組織結構和 makefile 很像。
xml 中分爲三層: project、target、task。
其中 target 即對應 makefile 中的目標,重點是 task。
makefile 中 task 通常對應一條 shell 命令,而 ant 的 task 通常對應執行了一個 java class。
例如 <echo message="This is target message." level="error"/>
, 能夠翻譯成代碼:new Echo("This is target message", "error").execute()
,
echo 僞代碼以下:
public class Echo extends Task { Echo(String message, String level) { this.message = message; this.level = level; } void execute() { System.out.println("[%s] %s", this.level, this.message); } }
ant 中內置了不少這種經常使用 task 。並且用戶也能夠本身實現自定義 Task。
從 make 和 apache ant 中能夠大體感知到, 構建系統兩個重要功能就是:
gradle 是一個更偏向 Java 定製化的構建工具。
使用的話,最好使用新版的 gradle,若是要跟 jetbrains、kotlinDsl 結合使用,最好用 gradle-all 版本。
下面的例子所有使用 kotlin-dsl 舉例,構建腳本爲 build.gradle.kts
注意: 使用 jetbrains idea 能夠對 gradle 構建腳本有更好的提示。
gradle 使用 groovy/kotlin 語言做爲構建語言,能夠理解爲是一個構建腳本。
Ant 中存在 project、target、task 的概念,gradle 也擁有 project/task 的概念。對好比下
概念 | ant | gradle |
---|---|---|
項目 | project | project |
構建目標 | target | task |
構建指令 | task | groovy/kotlin代碼 |
須要注意的是 gradle 的 task 對應了 ant 的 target。
腳本中能夠經過相似與引用全局變量的方式引用 tasks。
// build.gradle.kts tasks.register("hello") { doLast { println("Hello world!") } } tasks.register("intro") { dependsOn("hello") doLast { println("I'm Gradle") } }
運行:
$ gradle intro Hello world! I'm Gradle
task.register(name: String)
: 定義指定名稱的 task, 大括號體包含構建這個 task 的代碼。doLast
: task 構建完成後的執行函數。dependsOn
: 本 task 依賴的其餘 task。也能夠編寫任意複雜的腳本。
repeat(4) { tasks.register("intro${it}") { doLast { println("I'm ${it}") } } }
對於 ant 來講,整個 build.xml 的頂層元素就是 project; gradle 也相似。
對整個 build.gradle.kts 來講, 整個文件都處在 project 的做用域中,
能夠經過 this 訪問 project 自己及其成員、方法, 不過通常都省略 this,
好比用 repositories {...}
, 而不是 this.repositories {...}
。
如上面的例子,整個構建流程相似於下面的僞代碼:
interface Task { fun doLast(); fun dependsOn(); ... } class TaskContainer extends Set<Task> { fun register(name: String): Task } class Project { val tasks: TaskContainer fun build() { // Start: 將 build.gradle.kts 的內容在這裏展開 + tasks.register("hello") { + doLast { + println("Hello world!") + } + } + tasks.register("intro") { + dependsOn("hello") + doLast { + println("I'm Gradle") + } + } // End: } } new Project().build()
Project 具體聲明見: org.gradle.api.Project
除了 tasks 外,幾乎構建腳本中全部調用的方法都綁定於 Project,
這些方法的做用是設置 Project 屬性。
這相似於 Ant, Ant 也有不少針對 Project 的屬性設置。
好比 defaultTasks
設置默認構建目標, repositories
設置項目 maven 源, dependencies
設置項目依賴的第三方包, group
設置項目包名。
tasks 是 Project 的成員。能夠經過 tasks
訪問,其底層是一個容器(Set<Task>),
不過提供了不少自定義方法作其餘管理操做。
tasks 具體聲明見: org.gradle.api.tasks.TaskContainer
基本操做
// 建立 task: 默認類型爲 DefaultTask tasks.register("myTask") { // 能夠在裏面作一些操做 doLast {...} } // 也能夠在後期引用,而後進行其餘設置 tasks.named("myTask") { dependsOn(...) } // 建立指定類型的 Task tasks.registor<Copy>("copyTask") { from(file("srcDir")) to(...) }
建立 task 能夠指定類型,如上面的 Copy
,
此時能夠調用 Copy 的專有方法,如 from()
。
對於 kotlin-dsl, 構建腳本中可使用任何 kotlin 代碼,也可使用kotlin 標準庫。
可是構建腳本默認沒法使用第三方庫,若是須要第三方庫,則要在 buildScripts 中引入.
buildscript { repositories { mavenCentral() } dependencies { "classpath"(group = "commons-codec", name = "commons-codec", version = "1.2") } } tasks.register("encode") { doLast { val encodedString = Base64().encode("hello world\n".toByteArray()) println(String(encodedString)) } }
buildScripts.dependencies 表示構建腳本自己的第三方依賴,而不是用戶項目的依賴。
gradle 腳本中默認做用域就是 Project, 因此構建腳本中能夠隨意調用 Project 的方法。但 Project 中的方法是有限的,可能知足不了業務需求。
gradle 插件能夠擴充 Project 的方法,或者增長其餘功能、task 等。
plugin 的使用:
plugins { id("org.jetbrains.kotlin.jvm") version "1.4.10" }
plugins 和 dependencies 的聲明方法差很少,
因爲 maven 下載速度可能很慢,能夠新建 settings.gradle.kts 設置 plugins 源。
pluginManagement { repositories { maven(url="https://maven.aliyun.com/repository/public/") maven(url="https://maven.aliyun.com/repository/gradle-plugin") } }
完整文檔: gradle 安裝目錄的 docs。