Skip to content

Latest commit

 

History

History
677 lines (571 loc) · 18.7 KB

tutorial-using-tasks.md

File metadata and controls

677 lines (571 loc) · 18.7 KB

脚本基本知识

本章向您介绍编写Gradle构建脚本的基本知识。要获得快速的实践介绍,请尝试 创建新的Gradle构建指南

项目与任务

Gradle的一切都基于两个基本概念:项目和任务。

每个 Gradle 构建都由一个或多个子项目组成。项目代表什么取决于你想让Gradle做什么。例如,项目可能代表JAR库或web应用程序。它可能代表由其他项目生成的jar组装而成的发行版ZIP。项目不一定代表要构建的东西。它可能表示要做的事情,例如将应用程序部署到登台或生产环境。如果这看起来有点模糊,不要担心。Gradle的按惯例构建支持为项目添加了一个更具体的定义。

每个项目由一个或多个任务组成。任务表示构建执行的某个原子工作片段。这可能是编译一些类、创建一个JAR、生成 Javadoc 或将一些存档发布到存储库。

现在,我们将着眼于在一个项目的构建中定义一些简单的任务。后面的章节将介绍如何处理多个项目,以及如何处理项目和任务。

Hello world (世界你好)

您可以使用 gradle 命令运行Gradle构建,gradle 命令在当前目录中查找一个名为 build.gradle 的文件,我们将 build.gradle 称为构建脚本,尽管严格地说它是一个构建配置脚本,我们稍后将看到构建脚本中定义了一个项目及其任务。

为此,可以根据下面内容创建名为 build.gradle 的构建脚本。

示例1. 您的第一个构建脚本

build.gradle

task hello {
    doLast {
        println 'Hello world!'
    }
}

build.gradle.kts

task("hello") {
    doLast {
        println("Hello world!")
    }
}

在命令行shell中(或 cmd),进入到 build.gradle 的目录并使用 gradle -q hello 执行构建脚本:

-q 是做什么的?
本用户指南中的大多数示例都使用 -q 命令行选项运行。这会关闭 Gradle 的日志消息,以便只显示任务的输出。这使得本用户指南中的示例输出更加清晰。如果你需要看详细的日志信息,去掉本参数即可。有关影响Gradle 输出的命令行选项的更多细节,请参见 日志

示例2. 构建脚本的执行

输出结果来自命令:gradle -q hello

> gradle -q hello
Hello world!

这是怎么回事?这个构建脚本定义了一个名为 hello 的任务,并向其添加了一个操作。当您运行 gradle hello 时,gradle 执行 hello 任务,该任务反过来执行您提供的操作。该操作只是一个包含要执行的代码的块。

如果您认为这看起来与 Ant 的 target 相似,那么你是正确的。Gradle任务就相当于 Ant 的 target,但是却要强大得多。我们使用了与 Ant 不同的术语,因为我们认为单词 task 比单词 target 更具表现力。不幸的是,这引入了与 Ant 冲突的术语,因为在 Ant 中会把它的命令 (如javac或copy)叫做任务。所以当本文说到任务时,总是指的都是是 Gradle 任务,它相当于 Ant target 目标。如果我们提到 Ant 任务 (Ant命令),将会明确地说 Ant 任务

构建脚本也是可执行代码

Gradle 的构建脚本提供了 Groovy 和 Kotlin 的全部功能支持。作为开胃菜,看看这个:

示例3. 在 Gradle 的任务中使用 Groovy 或 Kotlin

build.gradle

task upper {
    doLast {
        String someString = 'mY_nAmE'
        println "Original: $someString"
        println "Upper case: ${someString.toUpperCase()}"
    }
}

build.gradle.kts

task("upper") {
    doLast {
        val someString = "mY_nAmE"
        println("Original: $someString")
        println("Upper case: ${someString.toUpperCase()}")
    }
}

输出结果来自命令:gradle -q upper

> gradle -q upper
Original: mY_nAmE
Upper case: MY_NAME

或者

build.gradle

task count {
    doLast {
        4.times { print "$it " }
    }
}

build.gradle.kts

task("count") {
    doLast {
        repeat(4) { print("$it ") }
    }
}

输出结果来自命令:gradle -q count

> gradle -q count
0 1 2 3

任务依赖

正如您可能已经猜到的,您可以声明依赖于其他任务的任务。

build.gradle

task hello {
    doLast {
        println 'Hello world!'
    }
}
task intro {
    dependsOn hello
    doLast {
        println "I'm Gradle"
    }
}

build.gradle.kts

task("hello") {
    doLast {
        println("Hello world!")
    }
}
task("intro") {
    dependsOn("hello")
    doLast {
        println("I'm Gradle")
    }
}

输出结果来自命令:gradle -q intro

> gradle -q intro
Hello world!
I'm Gradle

要添加依赖任务的时候,不要求当前存在的任务,也可以是后面添加的任务。

示例6. 惰性依赖 —— 另一个任务不存在

build.gradle

task taskX {
    dependsOn 'taskY'
    doLast {
        println 'taskX'
    }
}
task taskY {
    doLast {
        println 'taskY'
    }
}

build.gradle.kts

task("taskX") {
    dependsOn("taskY")
    doLast {
        println("taskX")
    }
}
task("taskY") {
    doLast {
        println("taskY")
    }
}

输出结果来自命令:gradle -q taskX

> gradle -q taskX
taskY
taskX

taskX 依赖 taskY 的时候,taskY 还没有声明定义。这个在多项目构建中非常重要。关于任务依赖更多详细信息请参考 给任务添加依赖

不过需要注意,在引用尚未定义的任务时,不能使用 快捷表示法

动态任务

Groovy 或 Kotlin 的强大功能不仅可以用于定义任务的功能。例如,您还可以使用它来动态创建任务。

示例7. 任务的动态创建

build.gradle

4.times { counter ->
    task "task$counter" {
        doLast {
            println "I'm task number $counter"
        }
    }
}

build.gradle.kts

repeat(4) { counter ->
    task("task$counter") {
        doLast {
            println("I'm task number $counter")
        }
    }
}

输出结果来自命令:gradle -q task1

> gradle -q task1
I'm task number 1

操作任务

一旦创建了任务,就可以通过API访问它们。例如,您可以使用它在运行时动态地向任务添加依赖项。而在 Ant 中不支持这样的操作。

示例8. 通过 API 访问任务 —— 添加依赖项

build.gradle

4.times { counter ->
    task "task$counter" {
        doLast {
            println "I'm task number $counter"
        }
    }
}
task0.dependsOn task2, task3

build.gradle.kts

repeat(4) { counter ->
    task("task$counter") {
        doLast {
            println("I'm task number $counter")
        }
    }
}
tasks["task0"].dependsOn("task2", "task3")

输出结果来自命令:gradle -q task0

> gradle -q task0
I'm task number 2
I'm task number 3
I'm task number 0

你还可以向现有任务添加行为。

示例9. 通过 API 添加行为访问任务

build.gradle

task hello {
    doLast {
        println 'Hello Earth'
    }
}
hello.doFirst {
    println 'Hello Venus'
}
hello.doLast {
    println 'Hello Mars'
}
hello {
    doLast {
        println 'Hello Jupiter'
    }
}

build.gradle.kts

val hello = task("hello") {
    doLast {
        println("Hello Earth")
    }
}
hello.doFirst {
    println("Hello Venus")
}
hello.doLast {
    println("Hello Mars")
}
hello.apply {
    doLast {
        println("Hello Jupiter")
    }
}

输出结果来自命令:gradle -q hello

> gradle -q hello
Hello Venus
Hello Earth
Hello Mars
Hello Jupiter

doFirstdoLast 可以调用多次。它们将一个操作添加到任务操作列表的开头或结尾。当任务执行时,将按顺序执行操作列表中的操作。

快捷符号

访问现有任务有一种快捷的表示法:Groovy DSL。每个任务都可以作为构建脚本的属性使用:

示例10. 将任务作为构建脚本的属性访问

build.gradle

task hello {
    doLast {
        println 'Hello world!'
    }
}
hello.doLast {
    println "Greetings from the $hello.name task."
}

输出结果来自命令:gradle -q hello

> gradle -q hello
Hello world!
Greetings from the hello task.

这大大提高了代码的可读性,特别是在使用插件提供的任务时,比如 compile 任务。

任务额外属性

您可以将自己的属性添加到任务中。要添加名为 myProperty 的属性需要给 ext.myProperty 初始值。然后就可以像读取和设置预定义的任务属性一样读取和设置属性。

示例11. 向任务添加额外属性

build.gradle

task myTask {
    ext.myProperty = "myValue"
}

task printTaskProperties {
    doLast {
        println myTask.myProperty
    }
}

build.gradle.kts

task("myTask") {
    extra["myProperty"] = "myValue"
}

task("printTaskProperties") {
    doLast {
        println(tasks["myTask"].extra["myProperty"])
    }
}

输出结果来自命令:gradle -q printTaskProperties

> gradle -q printTaskProperties
myValue

额外的属性并不仅限于任务使用。您可以在 额外属性 中阅读更多关于它们的信息。

使用Ant任务

Gradle 中内嵌了 Ant 完整的子系统 (Ant tasks are first-class citizens in Gradle)。Gradle 可以在 Groovy 的基础上通过简单地依赖配置方便的集成 Ant 任务。Groovy 附带了出色的 AntBuilder 。使用Gradle 中的 Ant 任务与使用 build.xml 文件中的 Ant 任务一样方便和强大。Kotlin也可以使用。从下面的例子中,您可以学习如何执行 Ant 任务以及如何访问 Ant 属性:

示例12. 使用 AntBuilder 执行 ant.loadfile target

build.gradle

task loadfile {
    doLast {
        def files = file('./antLoadfileResources').listFiles().sort()
        files.each { File file ->
            if (file.isFile()) {
                ant.loadfile(srcFile: file, property: file.name)
                println " *** $file.name ***"
                println "${ant.properties[file.name]}"
            }
        }
    }
}

build.gradle.kts

task("loadfile") {
    doLast {
        val files = file("./antLoadfileResources").listFiles().sorted()
        files.forEach { file ->
            if (file.isFile) {
                ant.withGroovyBuilder {
                    "loadfile"("srcFile" to file, "property" to file.name)
                }
                println(" *** ${file.name} ***")
                println("${ant.properties[file.name]}")
            }
        }
    }
}

输出结果来自命令:gradle -q loadfile

> gradle -q loadfile
 *** agile.manifesto.txt ***
Individuals and interactions over processes and tools
Working software over comprehensive documentation
Customer collaboration  over contract negotiation
Responding to change over following a plan
 *** gradle.manifesto.txt ***
Make the impossible possible, make the possible easy and make the easy elegant.
(inspired by Moshe Feldenkrais)

在构建脚本中,您可以使用 Ant 做更多的事情。你可以在 Ant文档 中找到更多信息。

方法提取

Gradle 在如何组织构建逻辑方面具有伸缩性。对于上述示例,组织构建逻辑的最需要的优化是提取方法。

示例13. 方法提取组织构建逻辑

build.gradle

task checksum {
    doLast {
        fileList('./antLoadfileResources').each { File file ->
            ant.checksum(file: file, property: "cs_$file.name")
            println "$file.name Checksum: ${ant.properties["cs_$file.name"]}"
        }
    }
}

task loadfile {
    doLast {
        fileList('./antLoadfileResources').each { File file ->
            ant.loadfile(srcFile: file, property: file.name)
            println "I'm fond of $file.name"
        }
    }
}

File[] fileList(String dir) {
    file(dir).listFiles({file -> file.isFile() } as FileFilter).sort()
}

build.gradle.kts

task("checksum") {
    doLast {
        fileList("./antLoadfileResources").forEach { file ->
            ant.withGroovyBuilder {
                "checksum"("file" to file, "property" to "cs_${file.name}")
            }
            println("$file.name Checksum: ${ant.properties["cs_${file.name}"]}")
        }
    }
}

task("loadfile") {
    doLast {
        fileList("./antLoadfileResources").forEach { file ->
            ant.withGroovyBuilder {
                "loadfile"("srcFile" to file, "property" to file.name)
            }
            println("I'm fond of ${file.name}")
        }
    }
}

fun fileList(dir: String): List<File> =
    file(dir).listFiles { file: File -> file.isFile }.sorted()

输出结果来自命令:gradle -q loadfile

> gradle -q loadfile
I'm fond of agile.manifesto.txt
I'm fond of gradle.manifesto.txt

稍后您将看到这些方法可以在多项目构建中的子项目之间共享。如果您的构建逻辑变得更加复杂,Gradle将为您提供其他非常方便的方式来组织它。我们用了整整一章来讨论这个问题。参见 组织Gradle项目

默认任务

Gradle允许您定义一个或多个默认任务,如果没有指定其他任务,将执行这些任务。

示例14. 定义默认任务

build.gradle

defaultTasks 'clean', 'run'

task clean {
    doLast {
        println 'Default Cleaning!'
    }
}

task run {
    doLast {
        println 'Default Running!'
    }
}

task other {
    doLast {
        println "I'm not a default task!"
    }
}

build.gradle.kts

defaultTasks("clean", "run")

task("clean") {
    doLast {
        println("Default Cleaning!")
    }
}

task("run") {
    doLast {
        println("Default Running!")
    }
}

task("other") {
    doLast {
        println("I'm not a default task!")
    }
}

输出结果来自命令:gradle -q

> gradle -q
Default Cleaning!
Default Running!

这相当于运行 gradle clean run 。在多项目构建中,每个子项目都可以有自己特定的默认任务。如果子项目没有指定默认任务,则使用父项目的默认任务(如果定义了)。

使用DAG配置

Gradle有一个配置阶段和一个执行阶段(详细查看 构建周期)。在配置阶段之后,Gradle 会知道应该执行哪些任务。Gradle 提供了一个钩子来利用这些信息。用例之一是检查发布任务是否在要执行的任务中。根据这一点,您可以为某些变量分配不同的值。

在下面的示例中,distribution release 任务的执行将导致 version 变量有不同的值。

15例. 构建的不同结果取决于所选择的任务

build.gradle

task distribution {
    doLast {
        println "We build the zip with version=$version"
    }
}

task release {
    dependsOn 'distribution'
    doLast {
        println 'We release now'
    }
}

gradle.taskGraph.whenReady { taskGraph ->
    if (taskGraph.hasTask(":release")) {
        version = '1.0'
    } else {
        version = '1.0-SNAPSHOT'
    }
}

build.gradle.kts

task("distribution") {
    doLast {
        println("We build the zip with version=$version")
    }
}

task("release") {
    dependsOn("distribution")
    doLast {
        println("We release now")
    }
}

gradle.taskGraph.whenReady {
    version =
        if (hasTask(":release")) "1.0"
        else "1.0-SNAPSHOT"
}

输出结果来自命令:gradle -q distribution

> gradle -q distribution
We build the zip with version=1.0-SNAPSHOT

输出结果来自命令:gradle -q release

> gradle -q release
We build the zip with version=1.0
We release now

重要的是 whenReady 会在执行 release 任务之前影响发布任务。即使 release 任务不是主要任务。

构建脚本的外部依赖项

如果构建脚本需要使用外部库,可以将它们添加到构建脚本中的脚本 classpath 中。您可以使用 buildscript() 传入一个声明构建脚本classpath 的代码块。

示例16. 为构建脚本声明外部依赖关系

build.gradle

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath group: 'commons-codec', name: 'commons-codec', version: '1.2'
    }
}

build.gradle.kts

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        "classpath"(group = "commons-codec", name = "commons-codec", version = "1.2")
    }
}

上面的代码块传递给 buildscript() 库和依赖的代码块来配置 ScriptHandler 实例。通过 classpath 配置添加依赖项。这与声明 Java 编译类路径的方式相同。除了项目依赖项之外,您可以使用任何依赖项类型。

添加了构建脚本依赖之后,就可以在构建脚本中直接使用依赖中的类来完成特定的工作了。下面的示例通过给构建脚本添加 Base64 的依赖来,完成对内容的 Base64 加密。

输出结果来自命令:gradle -q encode

> gradle -q encode
aGVsbG8gd29ybGQK

对于多项目构建,用根项目的 buildscript() 方法声明的依赖可用于其所有子项目中的构建脚本。

构建脚本的依赖项可以是 Gradle 插件。有关Gradle插件的更多信息,请咨询 使用Gradle插件

每一个项目都会自带一个类型为 BuildEnvironmentReportTask 名称为 buildEnvironment 的任务。可以通过执行它来查看构建脚本的依赖项情况。

接下来去哪里?

在这一章中,我们首先看了任务。但这并不是任务的结束。如果您想了解更多细节,请查看 更多关于任务 的信息。

或者,继续学习 Java构建教程依赖关系管理