Gradle-使用Gradle构建和测试-2-GradleTasks(一)

原版电子书下载地址:Build and Test with Gradle.pdf

Chapter 2. Gradle Tasks

在Gradle构建文件里面,构建活动最基本的单元是任务(task)。任务被命名为Gradle完成一个构建时所执行的构建指令的集合。你已经在第一章中看到过了有关任务的例子,它们跟其他构建系统比看起来有点相似的抽象,但是Gradle提供了一个比你可能使用的构建系统更加丰富的模型。没有把构建活动赤裸裸的声明和依赖绑在一起,Gradle 任务是可用的顶级对象如果你想在构建中进行编程。

让我们看下不同的定义任务的方式、任务定义的两个关键方面以及我们可以用来执行我们自定义任务的task API。

2.1 Declaring a Task 声明一个任务

在绪论中,我们看到了在同一时间如何去创建一个任务以及分配它的行为。但是,有一种更加简单的方式去创建一个任务。你所需要的只是一个任务名称(例2-1).
例2-1. Declaring a task by name only
task hello
你可以看到通过运行gradle tasks后的结果(例2-1).
例2-2. Gradle’s report of the newly created task
Root Project
Help tasks


dependencies - Displays the dependencies of root project ‘task-lab’.
help - Displays a help message
projects - Displays the subprojects of root project ‘task-lab’.
properties - Displays the properties of root project ‘task-lab’.
dependencies - Displays the dependencies of root project ‘task-lab’.
help - Displays a help message
projects - Displays the subprojects of root project ‘task-lab’.
properties - Displays the properties of root project ‘task-lab’.


hello

2.2 Task Action 任务动作

然而,执行gradle hello 任务将不会产生任何结果,因为我们还没有分配动作给这个任务。之前,我们分配了一个动作给一个带有左移运算符的任务(例2-3).
例2-3. Giving a task a trivial action to perform

task hello <<{
    println 'hello world'
}

提示:在Groovy中,像 << 操作符(来自于Java的左移运算符)可以超载于根据被操作对象的类型而拥有不同的意思。在这种情况下,Gradle 重载 << 后面附加一个代码块来表示任务执行的动作列表。这就等价于我们稍后章节将会讲到的 doLast() 方法。

然而,现在我们有积累动作代码的灵活性通过指向我们已经创建的任务对象(例2-4).
例2-4. Appending a task’s actions one at a time

task hello
hello <<{
    print 'hello,'
}
hello <<{
    println 'world'
}

现在,我们可以重新获得熟悉的build输出(例2-5).
例2-5. The output of the build with actions appended a piece at a time

$ gradle hello
hello,world
$

这又是一个简单的build行为,但是它揭示了一个强大的洞察力:任务不是build活动的一次性声明,而是Gradle编程环境中的一级对象。如果我们可以积累构建动作在构建文件中,那么我们就有可能做得更多。让我们继续往下探索。

2.3 Task Configuration 任务配置

Gradle的新用户通常在尝试定义任务动作时会绊倒在配置语法上。继续我们之前的例子,我们可以引进一个配置块来扩展它(例2-6).
例2-6. A mix of task configuration and task action definition,文件命名为scratch.gradle

task initializeDatabase
initializeDatabase<<{println 'connect to database'}
initializeDatabase<<{println 'update database schema'}
initializeDatabase{println 'configuration database connection'}

运行这个build文件,我们获得的结果可以会有点违反直觉(例2-7).
例2-7. The output of the preceding build file

$ gradle -b scratch.gradle initializeDatabase
configuring database connection
:initializeDatabase
connect to database
update database schema
$

提示:Groovy 使用 “closure” 闭包形式来引用来个花括号之间的代码块。一个闭包函数就像一个对象,可以作为参数传递给一个方法或赋值给一个变量,然后稍后才被执行。你将会看到在Groovy中到处都是闭包,因为它们完全适合配置代码块和构建动作。

如果第3个闭包用了又一个build动作的片段,那么我们最后会看到期望它的信息打印在最后,而不是最开始。原来,添加到任务名而没有左移运算符的语句是不会创建额外的任务动作代码。反而,它成了一个配置块。当任务动作执行时,任务的配置块运行于Gradle的生命周期阶段期间,它运行于执行(execution)阶段之前。

提示:每一次Gradle执行一个build的时候,它要贯穿3个生命周期阶段:initialization(初始化),configuration(配置),以及execution(执行)。在执行阶段,构建任务按它们依赖要求的顺序执行。在配置阶段,任务对象被编译为一个内部的对象模型,通常称作有向无环图DAG(for directed acyclic graph)。在初始化阶段,Gradle决定了哪个项目(project)要参与构建。在多项目构建中靠后的阶段很重要。

配置闭包的添加类似于动作闭包,因此,我们可以像这样写之前的构建文件,我们将会看到相同的输出(例2-8).
例2-8. Appending configuration blocks

task initializeDatabase
initializeDatabase << { println 'connect to database' }
initializeDatabase << { println 'update database schema' }
initializeDatabase { print 'configuring ' }
initializeDatabase { println 'database connection' }

配置块是设置任务动作执行时所需要的变量和数据结构的地方。配置结构让你有可能将你的构建任务转变成一个丰富的填充着有关构建信息的对象模型,而不仅仅是一个在某些序列将执行的构建动作集。如果没有这个配置与动作之间的差别,你就不得不构建额外的复杂性到你的任务以来关系中,这将导致一个更加脆落的build以及一个更没有表达力的沟通build的基础数据结构的方式。

提示:每一次当你运行一个Gradle构建文件时所有的配置代码都要运行,不管执行时给的任务是什么。

2.4 Tasks are Objects 任务即对象

现在你可能已经知道Gradle在执行构建之前会创建一个内部的对象模型。事实上,这就是Gradle很明确地做的事。你所声明的每一个任务实际上是一个包含在整个项目中的任务对象。任务对象有属性和方法就像其他的对象一样。我们甚至能控制每一个任务对象的类型,访问方式,相应的特定类型功能。一些示例将会讲清楚这些。

默认地,每一个新任务接受DefaultTask类型。类似Java代码中的java.lang.Object,每一个Gradle任务继承于这个对象类型–即使它有自己的类型也要继承DefaultTask.默认任务实际上没做任何事情例如编译代码或拷贝文件,但是他们确实包含功能需求用于与Gradle项目模型相接合。

2.4.1 Methods of DefaultTask

(1)dependsOn(task)

添加一个任务作为调用任务的一个依赖。被依赖的任务总是比依赖它的任务更早执行。有一些列的方式来调用这个方法。如果任务world依赖任务hello,我们可以使用如下代码(例2-9).
例2-9. Different ways of calling the dependsOn method

// Declare that world depends on hello
// Preserves any previously defined dependencies as well
task loadTestData {
 dependsOn createSchema
}
// An alternate way to express the same dependency
task loadTestData {
 dependsOn << createSchema
}
// Do the same using single quotes (which are usually optional)
task loadTestData {
 dependsOn 'createSchema'
}
// Explicitly call the method on the task object
task loadTestData
loadTestData.dependsOn createSchema
// A shortcut for declaring dependencies
task loadTestData(dependsOn: createSchema)

一个任务可以依赖一个以上的任务。如果任务loadTestData依赖createSchema以及compileTestClasses,我们可以使用如下示例(例2-10).
例2-10. Different ways of calling the dependsOn method for multiple dependencies

// Declare dependencies one at a time
task loadTestData {
 dependsOn << compileTestClasses
 dependsOn << createSchema
}
// Pass dependencies as a variable-length list
task world {
 dependsOn compileTestClasses, createSchema
}
// Explicitly call the method on the task object
task world
world.dependsOn compileTestClasses, createSchema
// A shortcut for dependencies only
// Note the Groovy list syntax
task world(dependsOn: [ compileTestClasses, createSchema ])
(2) doFirst(closure)

添加一个可执行的代码块到任务动作的开头部分。在执行阶段,每一个关联任务的动作块都被执行。doFirst方法允许你添加一点点行为给正在执行动作的开头部分,即使那个动作被一个build文件或一个你没有控制的插件所定义。调用doFirst方法多次可以继续添加动作的新代码块到任务的执行序列的开头部分。

你可以在任务对象上直接调用doFirst方法,通过传递一个闭包给这个方法。这个闭包包含了要在任务的执行动作运行之前的代码。

提示:正如我们前面已经提到的,一个闭包是一个有一对花括号包含的Groovy代码块。你可以传递一个闭包就像其他对象一样。传递闭包给方法是一种普遍的Groovy习语。

例2-11. Calling the doFirst method on the task object

task setupDatabaseTests << {
 // This is the task's existing action
 println 'load test data'
}
setupDatabaseTests.doFirst {
 println 'create schema'
}

例2-12. The results of the preceding build file

$ gradle setupDatabaseTests
:setupDatabaseTests
create schema
load test data
$

你也可以从任务的配置块里调用doFirst。回忆一下,配置块是一段运行于任何任务动作之前的可执行代码,在build的配置阶段。在我们之前讨论的任务配置中,你也许已经想知道你是如何实际地使用配置块。下面的这些示例展示了你可以从配置里如何调用任务方法,这为修改任务行为提供了一个潜在的非常有表达力的格式(例2-13)。
例2-13. Calling the doFirst method inside the task’s configuration block

task setupDatabaseTests << {
 println 'load test data'
}
setupDatabaseTests {
 doFirst {
 println 'create schema'
 }
}

重复调用doFirst是附加行为。之前的每一个调用的动作代码是保留的,新的闭包被按顺序地附加到待执行的列表的开始位置。如果我们要创立一个数据库用于集成测试(想一次只做一小块),那么我们可以使用如下示例代码例2-14.
例2-14. Repeated calls to doFirst are cumulative

task setupDatabaseTests << {
 println 'load test data'
}
setupDatabaseTests.doFirst {
 println 'create database schema'
}
setupDatabaseTests.doFirst {
 println 'drop database schema'
}

例2-15 The output of the preceding example

$ gradle world
:setupDatabaseTests
drop database schema
create database schema
load test data
$

当然,这有点不自然在一个初始化序列中插入3个单独的闭包并调用doFirst(),如上示例。然而,任务初始的定义不会立刻有效来改变你决定要做的,例如一些任务被定义在另一个build文件让你不可能或者不切实际地去修改的特殊情况。这种对例外的不可访问的build逻辑的编程式地修改会非常强大。

到目前为止,我们的示例使用的都是非常简单的语法,这让Gradle机制显得更加明显,尽管以大量的重复为代价。在现实世界的build中,我们将更可能地按如下示例组织任务例2-16.
例2-16. Repeated calls to doFirst, refactored

// Initial task definition (maybe not easily editable)
task setupDatabaseTests << {
 println 'load test data'
}
// Our changes to the task (in a place we can edit them)
setupDatabaseTests {
 doFirst {
 println 'create database schema'
 }
 doFirst {
 println 'drop database schema'
 }
}

注意到我们把多个doFirst调用聚集在一个简单的配置块,这个发生在初始动作附加到world任务之后。

(3)doLast(closure)

doLast方法和doFirst非常类似,除了附加行为是附加到动作的结尾部分,而不是开始部分。如果你有想要运行在完成的将要执行的已存在任务之后的代码块,那么你可以按如下示例去做 例2-17:
例2-17. An example of the doLast method

task setupDatabaseTests << {
 println 'create database schema'
}
setupDatabaseTests.doLast {
 println 'load test data'
}

就像doFirst一样,重复调用doLast也是附加性的。每一个随后的调用按顺序附加它的闭包到待执行列表(例2-18).
例2-18. Repeated calls to doLast are additive

task setupDatabaseTests << {
 println 'create database schema'
}
setupDatabaseTests.doLast {
 println 'load test data'
}
setupDatabaseTests.doLast {
 println 'update version table'
}
(4)onlyIf(closure)

onlyIf方法允许你表达一个谓语predicate,这个谓语决定一个任务是否应该被执行。这个谓语的值即为闭包返回的值。使用这个方法,你可以使一个另外可能作为build依赖链的一个正常部分运行的任务的执行失效。

提示:在Groovy中,闭包的最后一条语句作为闭包的返回值,即使没有给定return语句。一个包含了一个单一的表达式Groovy方法是一个返回表达式的值的函数。

例2-19. A build file making use of the onlyIf method.

task createSchema << {
 println 'create database schema'
}
task loadTestData(dependsOn: createSchema) << {
 println 'load test data'
}
loadTestData.onlyIf {
 System.properties['load.data'] == 'true'
}

例2-20. Two invocations of the preceding build file. Note differing results.

$ build loadTestData
create database schema
:loadTestData SKIPPED
$ gradle -Dload.data=true loadTestData
:createSchema
create database schema
:loadTestData
load test data
$

通过使用onliIf方法,你可以使用你能用Groovy代码表达的任何的逻辑来切换单独的任务执行与否,而不只是我们在这使用的简单的系统属性测试。你可以读文件,调用web服务,检查安全认证信息或其他有关的信息。

2.4.2 Properties of DefaultTask

(1)didWork

一个boolean属性表示任务是否成功完成。并不是所有的任务一旦完成就会设置didWork属性,但是一些内置的任务像Compile,Copy和Delete任务将会设置该属性来反应它们的动作成功与否。一个处理过的任务的评估是特定的任务。例如,当前的对Java Compiler实现返回为true的didWork属性如果至少有一个文件编译成功。你可以在你自己的任务动作中设置didWork属性来反映你写的build代码的执行结果。
例2-21 . Send an email upon successful compilation

apply plugin: 'java'
task emailMe(dependsOn: compileJava) << {
 if(tasks.compileJava.didWork) {
 println 'SEND EMAIL ANNOUNCING SUCCESS'
 }
}

例2-22. The results of the didWork build

$ gradle -b didWork.gradle emailMe
SEND EMAIL ANNOUNCING SUCCESS
$
(2)enabled

一个boolean属性表示任务是否将会执行。你可以设置任意任务的enabled属性为false不让它运行。它的依赖任务如果是enabled将依然执行。
例2-23. Disabling a task

task templates << {
 println 'process email templates'
}
task sendEmails(dependsOn: templates) << {
 println 'send emails'
}
sendEmails.enabled = false

例2-24. The build with a task disabled. Note that the dependency still runs.

$ gradle -b enabled.gradle sendEmails
:templates
process email templates
:sendEmails SKIPPED
$

提示: -b命令选择指定特定的Gradle文件而非默认的构建文件build.gradle.

(3)path

一个string属性,包含了一个任务的完全限定路径。默认地,一个任务的路径是一个简单的以冒号引导的任务名。下面的build文件示范了这点。
例2-25. A single-level build file that echoes its only task’s path

task echoMyPath << {
 println "THIS TASK'S PATH IS ${path}"
}

例2-26. The results of the previous build file

$ gradle -b path.gradle echoMyPath
THIS TASK'S PATH IS :echoMyPath
$

这个引导冒号表明了任务位于build文件的顶层位置。然而,对于一个给定的build,并不是所有的任务必须位于build文件的顶层位置,因为Gradle支持依赖的子项目,或内嵌的build。如果一个任务位于一个称作子项目subProject的内嵌构建文件中,那么这个路径将会是:subProject:echoMyPath.更多嵌套build的详情请参考第6章。

(4)logger

一个对内部Gradle logger对象的引用。Gradle logger对象实现了org.slf4j.Logger接口,但是添加了一些额外的日志层级。由logger支持的日志层级如下所述。

  • DEBUG.For high-volume logging messages which are of interest to the build developer, but should be suppressed during normal build execution. When this log level
    is selected, Gradle automatically provides a richer log formatter, including the
    timestamp, log level, and logger name of each message. All other log levels emit
    only the undecorated log message.

  • INFO.For lower-volume informative build messages which may be of optional
    interest during build execution.

  • LIFECYCLE.Low-volume messages, usually from Gradle itself, about changes in the
    build lifecycle and the execution of the build tool. When Gradle is executed without
    the -q command line option, this is the default logging level. Calls to the println
    method emit log statements at this level.

  • WARN.Low-volume but important messages, alerting the executor of the build of
    potential problems. When the log level is set to WARN, QUIET-level messages are not
    emitted.

  • QUIET. Messages which should appear even if the quiet switch was specified on the
    Gradle command line. (Executing Gradle with the -q command line option causes
    this to be the default log level.) System.out.println is directed to the logger at this
    log level. When the log level is set to QUIET, WARN-level messages are not emitted.

  • ERROR. Rare but critically important log messages which should be emitted in all
    cases. Intended to communicate build failures. If the log level is set to ERROR, calls
    to System.out.println will not show up in the console.

例2-27.A task illustrating the effects of each logging level. This slightly trickier Groovy code
sets the log level to each of the valid options, attempting to emit a log message at each log level each
time.

task logLevel << {
 def levels = ['DEBUG',
 'INFO',
 'LIFECYCLE',
 'QUIET',
 'WARN',
 'ERROR']
 levels.each { level ->
 logging.level = level
 def logMessage = "SETTING LogLevel=${level}"
 logger.error logMessage
 logger.error '-' * logMessage.size()
 logger.debug 'DEBUG ENABLED'
 logger.info 'INFO ENABLED'
 logger.lifecycle 'LIFECYCLE ENABLED'
 logger.warn 'WARN ENABLED'
 logger.quiet 'QUIET ENABLED'
 logger.error 'ERROR ENABLED'
 println 'THIS IS println OUTPUT'
 logger.error ' '
 }
}

例2-28. The output generated by the preceding build file.

$ gradle -b logging.gradle logLevel
 16:02:34.883 [ERROR] [org.gradle.api.Task] SETTING LogLevel=DEBUG
 16:02:34.902 [ERROR] [org.gradle.api.Task] ----------------------
 16:02:34.903 [DEBUG] [org.gradle.api.Task] DEBUG ENABLED
 16:02:34.903 [INFO] [org.gradle.api.Task] INFO ENABLED
 16:02:34.904 [LIFECYCLE] [org.gradle.api.Task] LIFECYCLE ENABLED
 16:02:34.904 [WARN] [org.gradle.api.Task] WARN ENABLED
 16:02:34.905 [QUIET] [org.gradle.api.Task] QUIET ENABLED
 16:02:34.905 [ERROR] [org.gradle.api.Task] ERROR ENABLED
 16:02:34.906 [ERROR] [org.gradle.api.Task]
 SETTING LogLevel=INFO
 ---------------------
 INFO ENABLED
 LIFECYCLE ENABLED
 WARN ENABLED
 QUIET ENABLED
 ERROR ENABLED
 SETTING LogLevel=LIFECYCLE
 --------------------------
 LIFECYCLE ENABLED
 WARN ENABLED
 QUIET ENABLED
 ERROR ENABLED
 SETTING LogLevel=QUIET
 ----------------------
 QUIET ENABLED
 ERROR ENABLED
 SETTING LogLevel=WARN
 ---------------------
 WARN ENABLED
 ERROR ENABLED
 SETTING LogLevel=ERROR
 ----------------------
 ERROR ENABLED
 $
(5)logging

logging属性给了我们访问日志层级的权限。正如在如上logger属性讨论中,logging.level属性可读可写来改变build使用的日志层级。

(6)description

description属性就像它的意思一样:一小片人类可读的元数据用于说明任务的目的。有很多方式来设置description,如例2-29和例2-30所示。
例2-29. Setting the description and task behavior all in one

task helloWorld(description: 'Says hello to the world') << {
 println 'hello, world'
}

例2-30. The two ways of declaring task behavior and description separately

task helloWorld << {
 println 'hello, world'
}
helloWorld {
 description = 'Says hello to the world'
 }
// Another way to do it
helloWorld.description = 'Says hello to the world'
(7)temporaryDir

temporaryDir 属性返回一个File对象指向一个属于build文件的临时目录。这个目录生成可用于需要一个存储立即的任意工作结果的临时地方,或为了处理任务里面的临时文件。

2.4.3 Dynamic Properties

正如我们已经看到的,任务是由一系列固有属性组成的,这些属性对Gradle用户来说是不可或缺的。然而,我们也可以分配任何其他的我们想要的属性给任务。一个任务对象函数就像一个哈希map,可以包含无论其他任意我们想要的赋值给任务的名值对属性(只要名称不与内置属性名称冲突)。

离开我们熟悉的“hello,world”示例,我们假设有一个叫做createArtifact的任务,它依赖于一个叫copyFiles的任务。copyFiles任务的工作是收集来自于多个来源的文件然后拷贝它们到一个临时目录,这个目录是createArtifact任务之后将会组装为一个部署工件的地方。根据构建参数,文件列表可能会改变,但是这个工件必须包含一个manifest清单来排列它们,目的是为了满足部署应用的需求。这是一个绝佳的机会来使用动态属性(例2-31和例2-32).

例2-31. Build file showing a dynamic task property

task copyFiles {
 // Find files from wherever, copy them
 // (then hardcode a list of files for illustration)
 fileManifest = [ 'data.csv', 'config.json' ]
}
task createArtifact(dependsOn: copyFiles) << {
 println "FILES IN MANIFEST: ${copyFiles.fileManifest}"
}

例2-32. The output of the above build file

$ gradle -b dynamic.gradle createArtifact
 FILES IN MANIFEST: [data.csv, config.json]
$

原版电子书下载地址:Build and Test with Gradle.pdf

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值