12 使用 Gradle图形界面
为了辅助传统的命令行交互,Gradle还提供了一个图形界面
12-1 启动图形界面
gradle --gui
注意:这个命令执行后会使得命令行一直处于封锁状态,直到我们关闭图形界面. 可以另外加上“&”让它在后台执行:
gradle --gui&
如果从自己的Gradle项目目录中启动这个图形界面,会看到任务树.
建议从当前的Gradle项目目录启动图形界面,因为这种方式可以将有关于界面的一些设置存储到目录里面.不过也可以在启动它后切换工作目录,方式:通过界面中“Setup”选项卡可以设置.
界面在顶部有4个选项卡和底部一个输出窗口.
12.1 任务树
任务树使用分层方式显示了所有的项目和它们的任务,双击一个任务就可以执行它.
另外还可以使用过滤器过滤掉不常用的任务. 可以点击 Filter 按钮来设置过滤条件. 设定哪些任务和项目可以显示. 隐藏的任务会使用红色来标记.
注意:最新被创建的任务会默认被显示出来(相反是被隐藏).
在任务树界面可以做以下几种事情:
- 执行任务时忽略依赖性,而且并不需要重新编译独立的项目.
- 添加自己喜欢的任务,将其收藏(具体请看“Favorities”选项卡).
- 隐藏自己不想看到的任务,这个操作会将他们添加到过滤器中.
- 编辑 build.gradke 文件, 注意:这个需要 jdk版本为1.6以上,而且操作系统需要关联 .gradle 文件.
12.2 收藏夹
“Favorites”选项卡可以收藏常用的命令. 即使是复杂的命令集,只要它符合Gradle规范,都可以添加收藏,可以为它起个通俗易懂的别名. 一个一眼看上去就让人明白的自定义的命令可以称它为 fast build.
还可以对收藏的任务进行排序,或者导出它们到磁盘,然后将导出的命令分享; 如果想编辑它们–“Always Show Live Output”选项,可以强制命令在执行时显示在输出窗口.
12.3 命令行
在“Command Line”选项卡,只需将命令填入到 gradle输入框. 就可以直接执行单个的Gradle命令.
12.4 设置
在设置界面可以配置一些常用的设置.
- “Current Directory” 图形界面会默认设置Gradle项目的根目录(build.gradle 文件所在的目录)为当前目录.
- “Stack Trace Output“ 这个选项可以指定当错误发生时,有多少信息可以写入到轨迹栈中,注意:在设定轨迹栈级别后,如果”Command Line”(命令行)选项卡中,或者在”Favorites”(收藏夹)选项卡中的命令发生错误, 这个设置就不会起作用了.
- ”Only Show Output When Errors Occur” 设定当编译出问题时输出窗口才显示相关信息.
- “Use Custom Gradle Executor” 高级功能,可以指定一个路径启动Gradle命令代替默认的设置,e.g. 项目需要在别的批处理文件或者shell脚本进行额外的配置(例如指定一个初始化脚本),这种情况就可以使用它.
13 编写构建脚本
13.1 Gradle 构建语言
Gradle 是以 Groovy 语言为基础, 基于DSL (领域特定语言) 语法的自动化构建工具, 但是它增加了一些额外的特性, 这使得Gradle更加的容易去阐释构建.
一个构建脚本能够包含任何Groovy语言的元素 ( Any language element except for statement labels ), 每个构建脚本都使用UTF-8编码.
13.2 项目 API
第七章 Java构建入门那部分使用了 apply() 方法,这个方法是从哪里来的呢?
之前说过Gradle在构建脚本中定义了一个项目. 对于构建脚本中每个项目,Gradle 都创建了一个 Project 类型的对象用来关联此项目. 当构建脚本执行时,它会去配置所关联的工程对象.
- 构建脚本中每个被调用的方法(这些方法并未在构建脚本中定义)都被委托给当前工程对象(使用工程对象引用方法)。
- 构建脚本中每个被操作的属性(这些属性并未在构建脚本中定义)都被委托给当前工程对象(使用工程对象引用属性).
13-1 操作工程对象的属性
build.gradle
println name
println project.name
输出结果:
> gradle -q check
projectApi
projectApi
两个 println 语句都输出了相同的属性,第一个输出使用的是自动委托 ( auto-delegation ), 因为当前属性并没有在构建脚本中定义.
另一个语句使用了项目一个属性,这个属性在任何构建脚本中都可用,它的返回值是被关联的工程对象. 只有当您定义了一个属性或者一个方法, 它的名字和工程对象的某个成员的名字相同时, 才应该使用项目属性.
13.2.1 标准项目属性
Project 对象提供了一些标准的属性,可以在构建脚本中很方便的使用.
下面列出了常用的属性:
Name | Type | Default Value |
---|---|---|
project | Project | Project 实例对象 |
name | String | 项目目录的名称 |
path | String | 项目的绝对路径 |
description | String | 项目描述 |
projectDir | File | 包含构建脚本的目录 |
build | File | projectDir/build |
group | Object | 未具体说明 |
version | Object | 未具体说明 |
ant | AntBuilder | Ant实例对象 |
建议:
不要忘记构建脚本只是个很简单的 Groovy 代码 ,不过它会再调用 Gradle API,Project 接口通过调用 Gradle API 让我们可以操作任何事情,因此如果想知道哪个标签(‘tags’) 可以在构建脚本种使用,可以翻阅 Project 接口的说明文档.
13.3 脚本 API
当 Gradle 执行一个脚本时,它会将这个脚本编译为实现了 Script 的类. 也就是说所有的属性和方法都是在 Script 接口中声明的,由于脚本实现了 Script 接口,所以可以在自己的脚本中使用它们
13.4 声明变量
在 Gradle 构建脚本中有两种类型的变量可以声明:局部变量 ( local ) 和 扩展属性 ( extra ) .
13.4.1 局部变量
局部变量使用关键字 def 来声明,只在声明它的地方可见 .
局部变量是 Groovy 语言的一个基本特性.
13-2 使用局部变量
def dest = "dest"task copy(type: Copy) {
form "source"into dest
}
13.4.2 扩展属性
在 Gradle 领域模型中所有被增强的对象能够拥有自己定义的属性. 这包括,但不仅限于 projects , tasks , 还有 source sets .
Project 对象可以添加,读取,更改扩展的属性. 另外,使用 ext 扩展块可以一次添加多个属性
13-3 使用扩展属性
build.gradle
apply plugin: "java"ext {springVersion = "3.1.0.RELEASE"emailNotification = "build@master.org"
}sourceSets.all { ext.purpose = null }sourceSets {main {purpose = "production"}test {purpose = "test"}plugin {purpose = "production"}}task printProperties << {println springVersionprintln emailNotificationsourceSets.matching { it.purpose == "production" }.each { println it.name }}
输出结果
> gradle -q printProperties
3.1.0.RELEASE
build@master.org
main
plugin
在上例,一个 ext 扩展块向 Project 对象添加了两个扩展属性. 名为 purpose 的属性被添加到每个 source set,然后设置 ext.purpose 等于 null ( null值是被允许的 ). 当这些扩展属性被添加后,它们就像预定义的属性一样可以被读取,更改值.
例子中通过一个特殊的语句添加扩展属性,当试图设置一个预定义属性或者扩展属性,但是属性名拼写错误或者并不存在时,操作就会失败.
Project 对象可以在任何地方使用其扩展属性 ,它们比局部变量有更大的作用域. 一个项目的扩展属性对其子项目也可见.
ExtraPropertiesExtension
13.5 Groovy 基础
Groovy 提供了大量的特性用来创建 DSL. Gradle 构建语言知道 Groovy 语言的工作原理,并利用这些特性帮助编写构建脚本,特别是在编写 plugin 或者 task 的时候,会觉得很方便.
13.5.1 Groovy JDK
Groovy 在 Java 基础上添加了很多有用的方法.
e.g. Iterable 有一个 each 方法, 通过使用 each 方法, 可以迭代出 Iterable 中的每一个元素:
13-4 Groovy JDK 方法
build.gradle
configuration.runtime.each { File f -> println f }
http://groovy.codehaus.org/groovy-jdk/
13.5.2 属性存取器
Groovy 自动将一个属性的引用转换为相应的 getter 或 setter 方法.
13-5 属性存取器
// 使用 getter 方法
println project.buildDir
println getProject().getBuildDir()// 使用 setter 方法
project.buildDir = 'target'
getProject().setBuildDir('target')
13.5.3 可有可无的圆括号
在调用方法时,圆括号可有可无,是个可选的
13-6 不使用圆括号调用方法
build.gradle
test.systemProperty 'some.prop', 'value'
test.systemProperty('some.prop', 'value')
13.5.4 List 和 Map 集合
Groovy 为预定义的 List 和 Map 集合提供了一些操作捷径,这两个字面值都比较简单易懂,但是 Map 会有一些不同.
e.g. 当使用 “apply” 方法应用 plug 时,apply 会自动加上 Map 的一个参数,当写 apply plugin: 'java'
时,实际上使用的是 name 参数(name-value),只不过在 Groovy 中 使用 Map 没有 < >
, 当方法被调用的时候,name 参数就会被转换成 Map 键值对,只不过在 Groovy 中看起来不像一个 Map.
13-7 List 和 Map 集合
build.gradle
// List 集合
test.includes = ['org/gradle/api/**', 'org/gradle/internal/**']List<String> list = new ArraryList<String>()
list.add('org/gradle/api/**')
list.add('org/gradle/internal/**')
test.includes = list// Map 集合
Map<String,String> map = [key1:'value1', key2:'valu2']// Groovy 会强制将 Map的键值对转换为只有value的映射
apply plugin: 'java'
13.5.5 闭包作为方法的最后一个参数
Gradle DSL 在很多地方使用闭包.
当一个方法的最后一个参数是一个闭包时,可以在方法调用后放置一个闭包.
13-8 闭包作为方法的参数
build.gradle
repositories {println "in a closure"
}
repositories() { println "in a closure" }
repositories({
println "in a closure" })
13.5.6 闭包委托对象
每个闭包都有一个委托对象,当闭包既不是局部变量也不是作为方法参数时,Groovy 使用委托对象查找变量和方法引用.
当委托对象被用来管理时,Gradle 使用它来管理闭包.
13-9 闭包引用
build.gradle
dependencies {assert delegate == project.dependenciestestCompile('junit:junit:4.11')delegate.testCompile('junit:junit:4.11')
}
14 深入理解 Tasks
在 第 6 章, 构建脚本基础 已经学习了如何创建简单的任务. 然后学习了如何给这些任务加入额外的行为, 以及如何在任务之间建立依赖关系. 这些仅仅是用来构建简单的任务.
Gradle 可以创建更为强大复杂的任务. 这些任务可以有它们自己的属性和方法. 这一点正是和 Ant targets 不一样的地方. 这些强大的任务既可以由你自己创建也可以使用 Gradle 内建好的.
14.1 定义 tasks
第 6 章学习了定义任务的形式 (keyword 形式). 当然也会有一些定义形式的变化来适应某些特殊的情况.
比如下面的例子中任务名被括号括起来了. 这是因为之前定义简单任务的形式 (keyword 形式) 在表达式里是不起作用的.
15-1 定义 tasks
build.gradle
task(hello) << {println "hello"
}task(copy, type: Copy) {from(file('srcDir'))into(buildDir)
}
15-2 使用 strings 来定义任务的名字
build.gradle
task('hello') <<
{println "hello"
}task('copy', type: Copy) {from(file('srcDir'))into(buildDir)
}
15-3 另外一种语法形式
build.gradle
tasks.create(name: 'hello') << {println "hello"
}tasks.create(name: 'copy', type: Copy) {from(file('srcDir'))into(buildDir)
}
这里实际上是把任务加入到 tasks collection 中.
TaskContainer
14.2 定位 tasks
经常需要在构建文件里找到你定义的 tasks, e.g. 为了配置它们或者使用它们作为依赖. 有许多种方式都可以来实现定位. 首先, 每一个任务都必须是一个 project 的有效属性, 并使用任务名来作为属性名:
15-4 通过属性获取 tasks
build.gradle
task helloprintln hello.name
println project.hello.name
Tasks 也可以通过 tasks collection 来得到.
15-5 通过 tasks collection 获取 tasks
build.gradle
task helloprintln tasks.hello.name
println tasks['hello'].name
也可以使用 tasks.getByPath() 方法通过任务的路径来使用任何 project 里的任务. 可以通过使用任务的名字, 任务的相对路径或者绝对路径作为 getByPath() 方法的输入.
15-6 通过路径获取 tasks
build.gradle
project(':projectA') {task hello
}task helloprintln tasks.getByPath('hello').path
println tasks.getByPath(':hello').path
println tasks.getByPath('projectA:hello').path
println tasks.getByPath(':projectA:hello').path
输出
> gradle -q hello
:hello
:hello
:projectA:hello
:projectA:hello
TaskContainer
14.3 配置 tasks
e.g. 看一看 Gradle 自带的 Copy task. 为了创建一个 Copy task, 你需要在构建脚本里先声明它:
15-7 创建一个 copy task
build.gradle
task myCopy(type: Copy)
创建了一个没有默认行为的 copy task. 这个 task 可以通过它的 API 来配置(参考 https://docs.gradle.org/current/dsl/org.gradle.api.tasks.Copy.html
).
补充说明一下, 这个 task 的名字是 “myCopy”, 但是它是 “Copy” 类(type). 可以有许多同样 type 不同名字的 tasks. 这个在实现特定类型的所有任务的 cross-cutting concerns 时特别有用.
15-8 配置一个任务 - 不同的方法
build.gradle
Copy myCopy = task(myCopy, type: Copy)
myCopy.from 'resources'
myCopy.into 'target'
myCopy.include('**/*.txt', '**/*.xml', '**/*.properties')
通过 Java 配置对象是一样的形式. 但是每次都必须在语句里重复上下文 (myCopy). 这种方式可能读起来并不是那么的漂亮.
下面一种方式就解决了这个问题. 是公认的最具可读性的方式.
15-9 配置一个任务 - 通过闭包 closure
build.gradle
task myCopy(type: Copy)myCopy {from 'resources'into 'target'include('**/*.txt', '**/*.xml', '**/*.properties')
}
例子里的第三行是 tasks.getByName() 方法的一个简洁的写法.
特别要注意的是, 如果你通过闭包的形式来实现 getByName() 方法, 这个闭包会在 task 配置的时候执行而不是在 task 运行的时候执行.
可以直接在定义 task 时使用闭包.
15-10 通过定义一个任务
build.gradle
task copy(type: Copy) {from 'resources'into 'target'include('**/*.txt', '**/*.xml', '**/*.properties')
}
请不要忘了构建的各个阶段.
一个任务有配置和动作. 当使用 << 时, 只是简单的使用捷径定义了动作. 定义在配置区域的代码只会在构建的配置阶段执行, 而且不管是执行哪个任务.
参考第 55 章, The Build Lifecycle for more details about the build lifecycle.
14.4 给 task 加入依赖
有许多种加入依赖的方式. 在 6.5 小节, “任务依赖”里, 已经学习了如何使用任务的名称定义依赖.
任务名称可以指向同一个项目里的任务, 或者其他项目里的任务. 为了指向其他项目, 必须在任务的名称前加入项目的路径.
e.g. 给 projectA:taskX 加入依赖 projectB:taskY :
15-11 从另外一个项目给任务加入依赖
build.gradle
project('projectA') {task taskX(dependsOn: ':projectB:taskY') << {println 'taskX'}
}project('projectB') {task taskY << {println 'taskY'}
}
输出
> gradle -q taskX
taskY
taskX
除了使用任务名称, 也可以定义一个依赖对象y:
15-12 通过任务对象加入依赖
build.gradle
task taskX << {println 'taskX'
}task taskY << {println 'taskY'
}taskX.dependsOn taskY
输出
> gradle -q taskX
taskY
taskX
更加先进的用法, 可以通过闭包定义一个任务依赖.
闭包只能返回一个单独的 Task 或者 Task 对象的 collection, 这些返回的任务就将被当做依赖.
e.g. 给 taskX 加入了一个复杂的依赖, 所有以 lib 开头的任务都将在 taskX 之前执行:
15-13 通过闭包加入依赖
build.gradle
task taskX << {println 'taskX'
}taskX.dependsOn {tasks.findAll { task -> task.name.startsWith('lib') }
}task lib1 << {println 'lib1'
}task lib2 << {println 'lib2'
}task notALib << {println 'notALib'
}
输出
> gradle -q taskX
lib1
lib2
taskX
Task API
14.5 给 tasks 排序
任务的排序功能正在测试和优化. 请注意, 这项功能在 Gradle 之后的版本里可能会改变.
在某些情况下, 我们希望能控制任务的的执行顺序, 这种控制并不是去显式地加入依赖关系. 最主要的区别是我们设定的排序规则不会影响那些要被执行的任务, 只是影响执行的顺序本身.
看看以下几种有用的场景:
- 执行连续的任务: e.g. ‘build’ 从来不会在 ‘clean’ 之前执行.
- 在 build 的一开始先运行构建确认 (build validations): e.g. 在正式的发布构建前先确认证书是正确的.
- 在运行长时间的检测任务前先运行快速的检测任务来获得更快的反馈: e.g. 单元测试总是应该在集成测试之前被执行.
- 一个聚集 (aggregates) 某种特定类型的所有任务结果的任务: e.g. 测试报告任务 (test report task) 包含了所有测试任务的运行结果.
目前, 有 2 种可用的排序规则: “must run after” 和 “should run after”
当你使用 “must run after” 时即意味着 taskB 必须总是在 taskA 之后运行, 无论 taskA 和 taskB 是否将要运行:
taskB.mustRunAfter(taskA)
“should run after” 规则其实和 “must run after” 很像, 只是没有那么的严格, 在 2 种情况下它会被忽略:
1. 使用规则来阐述一个执行的循环.
2. 当并行执行并且一个任务的所有依赖除了 “should run after” 任务其余都满足了, 那么这个任务无论它的 “should run after” 依赖是否执行, 它都可以执行.
当要求不是那么严格时, “should run after” 是非常有用的.
即使有目前的这些规则, 我们仍可以执行 taskA 而不管 taskB, 反之亦然.
15-14 加入 ‘must run after’
build.gradle
task taskX << {println 'taskX'
}
task taskY << {println 'taskY'
}
taskY.mustRunAfter taskX
输出
> gradle -q taskY taskX
taskX
taskY
15-15 加入 ‘should run after’
build.gradle
task taskX << {println 'taskX'
}
task taskY << {println 'taskY'
}
taskY.shouldRunAfter taskX
输出
> gradle -q taskY taskX
taskX
taskY
上面的例子里, 我们仍可以直接执行 taskY 而不去 taskX :
15-16 任务排序不影响任务执行
输出
> gradle -q taskY
taskY
为了在 2 个任务间定义 “must run after” 或者 “should run after” 排序, 我们需要使用 Task.mustRunAfter() 和 Task.shouldRunAfter() 方法. 这些方法接收一个任务的实例, 任务的名字或者任何 Task.dependsOn()可以接收的输入.
注意 “B.mustRunAfter(A)” 或者 “B.shouldRunAfter(A)” 并不影响任何任务间的执行依赖:
- tasks A 和 B 可以被独立的执行. 排序规则只有当 2 个任务同时执行时才会被应用.
- 在运行时加上 –continue, 当 A 失败时 B 仍然会执行.
“should run after” 规则在一个执行循环中将被忽略:
15-17 ‘should run after’ 任务的忽略
build.gradle
task taskX << {println 'taskX'
}
task taskY << {println 'taskY'
}
task taskZ << {println 'taskZ'
}
taskX.dependsOn taskY
taskY.dependsOn taskZ
taskZ.shouldRunAfter taskX
输出
> gradle -q taskX
taskZ
taskY
taskX
—TBC—
—YCR—