本章向您介绍编写Gradle构建脚本的基础. 要获得快速动手的介绍,请尝试创建新的Gradle构建指南.

Projects and tasks

Gradle中的所有内容都基于两个基本概念: 项目任务 .

每个Gradle构建都由一个或多个项目组成 . 项目代表什么取决于您在Gradle中所做的事情. 例如,一个项目可能代表一个JAR库或一个Web应用程序. 它可能表示从其他项目产生的JAR组装而成的发行版ZIP. 项目不一定代表要构建的事物. 它可能表示要完成的事情,例如将应用程序部署到暂存或生产环境. 暂时不要担心这似乎还不清楚. Gradle的按惯例构建支持为项目的定义添加了更具体的定义.

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

现在,我们将研究在一个项目中构建一些简单的任务. 后面的章节将介绍处理多个项目,以及有关处理项目和任务的更多信息.

Hello world

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

要尝试此操作,请创建以下名为build.gradle构建脚本.

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

要尝试此操作,请创建以下名为build.gradle.kts构建脚本.

例子1.您的第一个构建脚本
build.gradle
task hello {
    doLast {
        println 'Hello world!'
    }
}
build.gradle.kts
tasks.register("hello") {
    doLast {
        println("Hello world!")
    }
}

在命令行外壳中,移至包含目录,并使用gradle -q hello执行构建脚本:

💡
-q做什么?

本用户指南中的大多数示例都使用-q命令行选项运行. 这将取消Gradle的日志消息,因此仅显示任务的输出. 这样可以使本用户指南中的示例输出更加清晰. 如果不想,则不需要使用此选项. 有关影响Gradle输出的命令行选项的更多详细信息,请参见日志记录 .

例子2.建立脚本的执行
gradle -q hello输出
> gradle -q hello
Hello world!

这里发生了什么? 该构建脚本定义了一个名为hello任务,并向其中添加了一个动作. 当您运行gradle hello ,Gradle将执行hello任务,该任务又将执行您提供的操作. 该动作只是一个包含一些要执行代码的块.

如果您认为这看起来类似于Ant的目标,那将是正确的. Gradle任务等效于Ant目标,但是正如您将看到的那样,它们的功能要强大得多. 我们使用了与Ant不同的术语,因为我们认为" 任务 "一词比" 目标 "一词更具表现力. 不幸的是,这会引起与Ant的术语冲突,因为Ant会调用其命令,例如javaccopy任务. 因此,当我们谈论任务时,我们总是指Gradle任务,它等同于Ant的目标. 如果我们谈论Ant任务(Ant命令),则明确地说出Ant task .

Build scripts are code

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
tasks.register("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

or

例子4.在Gradle的任务中使用Groovy或Kotlin
build.gradle
task count {
    doLast {
        4.times { print "$it " }
    }
}
build.gradle.kts
tasks.register("count") {
    doLast {
        repeat(4) { print("$it ") }
    }
}
gradle -q count输出
> gradle -q count
0 1 2 3

Task dependencies

您可能已经猜到了,您可以声明依赖于其他任务的任务.

例子5.依赖于其他任务的任务声明
build.gradle
task hello {
    doLast {
        println 'Hello world!'
    }
}
task intro {
    dependsOn hello
    doLast {
        println "I'm Gradle"
    }
}
build.gradle.kts
tasks.register("hello") {
    doLast {
        println("Hello world!")
    }
}
tasks.register("intro") {
    dependsOn("hello")
    doLast {
        println("I'm Gradle")
    }
}
gradle -q intro输出
> gradle -q intro
Hello world!
I'm Gradle

要添加依赖项,不需要存在相应的任务.

例子6.懒惰的dependOn-其他任务不存在(还)
build.gradle
task taskX {
    dependsOn 'taskY'
    doLast {
        println 'taskX'
    }
}
task taskY {
    doLast {
        println 'taskY'
    }
}
build.gradle.kts
tasks.register("taskX") {
    dependsOn("taskY")
    doLast {
        println("taskX")
    }
}
tasks.register("taskY") {
    doLast {
        println("taskY")
    }
}
gradle -q taskX输出
> gradle -q taskX
taskY
taskX

可以在定义taskY之前声明taskXtaskY的依赖关系. 对于多项目构建,这种自由非常重要. 将依赖项添加到任务中将详细讨论任务依赖 .

请注意,在引用尚未定义的任务时,不能使用快捷方式表示法 .

Dynamic tasks

Groovy或Kotlin的功能可用于定义任务以外的其他功能. 例如,您也可以使用它来动态创建任务.

例子7.动态创建任务
build.gradle
4.times { counter ->
    task "task$counter" {
        doLast {
            println "I'm task number $counter"
        }
    }
}
build.gradle.kts
repeat(4) { counter ->
    tasks.register("task$counter") {
        doLast {
            println("I'm task number $counter")
        }
    }
}
gradle -q task1输出
> gradle -q task1
I'm task number 1

Manipulating existing tasks

创建任务后,就可以通过API对其进行访问. 例如,您可以使用它在运行时为任务动态添加依赖项. 蚂蚁不允许这样的事情.

例子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 ->
    tasks.register("task$counter") {
        doLast {
            println("I'm task number $counter")
        }
    }
}
tasks.named("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.configure {
    doLast {
        println 'Hello Mars'
    }
}
hello.configure {
    doLast {
        println 'Hello Jupiter'
    }
}
build.gradle.kts
val hello by tasks.registering {
    doLast {
        println("Hello Earth")
    }
}
hello {
    doFirst {
        println("Hello Venus")
    }
}
hello {
    doLast {
        println("Hello Mars")
    }
}
hello {
    doLast {
        println("Hello Jupiter")
    }
}
gradle -q hello输出
> gradle -q hello
Hello Venus
Hello Earth
Hello Mars
Hello Jupiter

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

Groovy DSL shortcut notations

访问现有任务有一种方便的表示法. 每个任务都可以作为构建脚本的属性来使用:

例子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任务)时.

Extra task properties

您可以将自己的属性添加到任务. 要添加名为myProperty的属性,请将ext.myProperty设置为初始值. 从那时起,可以像预定义的任务属性一样读取和设置属性.

例子11.向任务添加额外的属性
build.gradle
task myTask {
    ext.myProperty = "myValue"
}

task printTaskProperties {
    doLast {
        println myTask.myProperty
    }
}
build.gradle.kts
tasks.register("myTask") {
    extra["myProperty"] = "myValue"
}

tasks.register("printTaskProperties") {
    doLast {
        println(tasks["myTask"].extra["myProperty"])
    }
}
gradle -q printTaskProperties输出
> gradle -q printTaskProperties
myValue

额外的属性不仅限于任务. 您可以在Extra属性中阅读有关它们的更多信息.

Using Ant Tasks

蚂蚁任务是Gradle中的一等公民. 仅仅依靠Groovy,Gradle就可以为Ant任务提供出色的集成. Groovy随附了出色的AntBuilder . 与从build.xml文件使用Ant任务相比,使用Gradle中的Ant任务既方便又强大. 它也可以从Kotlin使用. 从下面的示例中,您可以学习如何执行Ant任务以及如何访问Ant属性:

例子12.使用AntBuilder执行ant.loadfile目标
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
tasks.register("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中找到更多信息.

Using methods

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
tasks.register("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}"]}")
        }
    }
}

tasks.register("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项目 .

Default tasks

如果未指定其他任务,则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!")
    }
}

tasks.register("run") {
    doLast {
        println("Default Running!")
    }
}

tasks.register("other") {
    doLast {
        println("I'm not a default task!")
    }
}
gradle -q输出
> gradle -q
Default Cleaning!
Default Running!

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

Configure by DAG

正如我们稍后将详细描述的(请参阅Build Lifecycle ),Gradle具有配置阶段和执行阶段. 在配置阶段之后,Gradle知道应该执行的所有任务. Gradle为您提供了一个利用此信息的机会. 一个用例是检查发布任务是否在要执行的任务中. 以此为基础,您可以为某些变量分配不同的值.

在以下示例中, distributionrelease任务的执行导致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
tasks.register("distribution") {
    doLast {
        println("We build the zip with version=$version")
    }
}

tasks.register("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
Output of gradle -q release
> gradle -q release
We build the zip with version=1.0
We release now

重要的是, 执行释放任务之前whenReady会影响释放任务. 即使释放任务不是主要任务(即,传递给gradle命令的任务)也可以使用.

此示例有效,因为仅在执行时读取version值. 在实际构建中使用类似的构造时,必须确保在配置过程中不会急切读取该值. 否则,您的构建可能会对配置和执行之间的属性使用不同的值.

External dependencies for the build script

如果构建脚本需要使用外部库,则可以将它们添加到构建脚本本身中的脚本的类路径中. 您可以使用buildscript()方法执行此操作,并传入一个声明构建脚本类路径的块.

例子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配置中来声明构建脚本类classpath . 这与您声明Java编译类路径的方式相同. 您可以使用除项目依赖项以外的任何依赖项类型 .

Having declared the build script classpath, you can use the classes in your build script as you would any other classes on the classpath. The following example adds to the previous example, and uses classes from the build script classpath.

例子17.具有外部依赖关系的构建脚本
build.gradle
import org.apache.commons.codec.binary.Base64

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

task encode {
    doLast {
        def byte[] encodedString = new Base64().encode('hello world\n'.getBytes())
        println new String(encodedString)
    }
}
build.gradle.kts
import org.apache.commons.codec.binary.Base64

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))
    }
}
gradle -q encode输出
> gradle -q encode
aGVsbG8gd29ybGQK

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

构建脚本依赖项可能是Gradle插件. 请参阅使用Gradle插件以获取有关Gradle插件的更多信息.

每个项目都自动拥有buildEnvironment类型的任务BuildEnvironmentReportTask可以调用报告生成脚本依赖的分辨率.

Further Reading

本章仅探讨了可能的内容. 以下是一些其他有趣的主题:


1 . 有命令行开关可以更改此行为. 请参阅命令行界面
2 . 有命令行开关可以更改此行为. 请参阅命令行界面