前一篇中简要说明了Gradle构建脚本的书写,本篇将重点谈谈Gradle构建逻辑的核心对象:Project和Task。
这两个对象的作用,相信无需做过多的解释了,其各自名称已经非常明白地表明了自己的作用。在构建脚本执行时,Gradle会自动创建一个Project实例,并且:
- 任何你在构建脚本里调用的方法,如果构建脚本里没有定义,就委托给这个Project对象。
- 任何你在构建脚本里访问的属性,如果构建脚本里没有定义,就委托给这个Project对象。
上述两句摘自Gradle参考文档里的话可以解释前文代码示例的一些疑惑。对照Gradle的API文档,你可以发现task就是Project类的一个方法,结合上两句话,这就说明为什么在构建脚本中没有定义task但你却可以使用它的真正原因。
再看这个来自文档里的例子:
task check << {
allprojects {
println "project path $path"
println " project name = $name"
println " project dir = '${rootProject.relativePath(projectDir)}'"
println " build file = '${rootProject.relativePath(buildFile)}'"
println " build dir = '${rootProject.relativePath(buildDir)}'"
}
}
根据前面学到的知识,再结合API,这段代码的意义就很明白了:对于所有项目,列出当前项目的路径、名字、相对于根项目的各类路径。其中:
- task、allprojects都是Project对象的方法
- rootProject对应Project对象的getRootProject
- path、name、projectDir、buildFile、buildDir都是Project的属性
构建脚本里另一个最常使用的对象就是Task,关于它的创建,前一篇已经给出了几个例子,这里还有另一种方法:
tasks.add(name: 'hello') << {
println "hello"
}
上例中的tasks对应Project的getTasks方法,其返回类型为TaskContainer。
定义了Task,免不了要对其进行引用,主要的引用方法有:
- 将task名作为属性名访问,如hello.name,其中hello是前面定义的task名字。
- 通过tasks.taskName或tasks[taskName],如tasks.hello或tasks['hello']。
- 通过tasks.getByPath方法,如tasks.getByPath('projectA:hello').path。
这里所谓的path实际就是指:projectName:taskName,一般在多项目的时候使用。
除了自己定义task,我们还可以使用Gradle自带的task,方法:
task copy(type: Copy) {
from 'resources'
into 'target'
include( '**/*.txt', '**/*.xml', '**/*.properties')
}
上例定义了一个名为copy的Copy任务,这个例子也同时演示了利用闭包配置Task的方法。除了这种方法,由于我们可以在构建脚本中引用task对象,因此我们也可以象对待普通Java对象那样直接通过相应的getter/setter来进行配置,如task.name='Hello'。
Gradle的Task跟Ant的Target类似,也可以有依赖关系,其含义也完全一致。依赖关系的建立是通过dependsOn属性进行的,它可以接受的值包括:Task的名字;或一个闭包,其返回一组Task。
taskX.dependsOn {
tasks.findAll { task -> task.name.startsWith('lib') }
}
同样,你可以通过配置Task的description属性来定义任务的描述。
在Gradle里面,你甚至可以覆盖已经存在的Task,用自己的行为代替缺省行为:
task copy(type: Copy)
task copy(overwrite: true) << {
println('I am the new one.')
}
上例使用自定义copy任务覆盖了原Copy任务的行为,注意上面的overwrite属性。
有些任务的执行可能需要先满足一定的条件,这可以使用onlyIf闭包来完成:
hello.onlyIf { !project.hasProperty('skipHello') }
要是上述方法无法描述你的条件,还可以在通过抛出StopExecutionException来阻止Task的执行,但它不会阻止依赖这个Task的那些Task的执行:
task compile << {
println 'We are doing the compile.'
}
compile.doFirst {
if (true) { throw new StopExecutionException() }
}
task myTask(dependsOn: 'compile') << {
println 'I am not affected'
}
运行gradle myTask,“I am not affected”仍能正常输出。第三种阻止Task执行的方法就是将Task的enabled属性置为false。怎么样,是否比Ant完成类似的任务要方便太多了?
作为本篇的结束,让我们来了解一下Task规则。为了说明它的作用,让我们看看Grails工程自己的unit-test.gradle(gradle目录下)的一句话:
tasks.addRule("Pattern: testSingle<Name> will test **/<Name>Tests.class") {String taskName ->
if (taskName.startsWith("testSingle") && !taskName.endsWith('Coverage')) {
createTestTaskWithoutSuffix(taskName, ['**/' + taskName.substring(10) + 'Tests.class'], [])
}
}
其作用是动态创建一个单元测试任务。如果你在命令行中输入:gradle testSingleMockUtil,它将创建一个testSingleMockUtil的任务,它将运行工程中所有目录及子目录中的MockUtilTests。
按照Gradle API文档中对于Rule的描述:
一条规则代表了在引用未知领域对象(注意:不要跟Grails的混为一谈)时某种执行的动作。规则可以使用领域对象名隐式增加领域对象。
上述Grails的例子正是对该描述的最佳说明:文件里面并没有一个叫testSingleMockUtil的对象,因此,规则开始发生作用,最后testSingleMockUtil任务被创建了。createTestTaskWithoutSuffix是unit-test.gradle里定义的函数,负责创建测试任务。
从API的描述里可以知道,规则并不仅仅只局限于在命令行中引用了不存在的任务,对于其他地方的引用同样适合。参考文档里给出了denpendsOn的例子:
tasks.addRule("Pattern: ping<ID>") { String taskName ->
if (taskName.startsWith("ping")) {
task(taskName) << {
println "Pinging: " + (taskName - 'ping')
}
}
}
task groupPing {
dependsOn pingServer1, pingServer2
}
当执行gradle groupPing时,相应的规则就会触发,进而创建相应的任务。
本系列的其它部分:

最新评论
9 周 6 小时之前
9 周 1 天之前
11 周 5 天之前
11 周 5 天之前
17 周 2 天之前
17 周 4 天之前
19 周 3 天之前
20 周 1 天之前
20 周 2 天之前
20 周 5 天之前