之前只会用 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。