我们之前说过,Gradle的核心是一种用于基于依赖的编程的语言. 用Gradle术语,这意味着您可以定义任务以及任务之间的依赖关系. Gradle保证这些任务按照其依赖关系的顺序执行,并且每个任务仅执行一次. 这些任务形成有向无环图 . 有一些构建工具在执行任务时会建立这种依赖关系图. 执行任何任务之前, Gradle会构建完整的依赖关系图. 这是Gradle的核心所在,它使许多事情成为可能,而这在其他情况下是不可能的.

您的构建脚本将配置此依赖关系图. 因此,严格来说,它们是构建配置脚本 .

Build phases

Gradle构建具有三个不同的阶段.

Initialization

Gradle支持单项目和多项目构建. 在初始化阶段,Gradle确定将要参与构建的项目 ,并为每个项目创建一个Project实例.

Configuration

在此阶段,将配置项目对象. 执行作为构建一部分的所有项目的构建脚本.

Execution

Gradle确定要在配置阶段创建和配置的任务子集. 子集由传递给gradle命令的任务名称参数和当前目录确定. 然后Gradle执行每个选定的任务.

Settings file

除了构建脚本文件外,Gradle还定义了一个设置文件. 设置文件由Gradle通过命名约定确定. 该文件的默认名称是settings.gradle . 在本章的后面,我们将解释Gradle如何查找设置文件.

设置文件在初始化阶段执行. 多项目构建必须在多项目层次结构的根项目中具有settings.gradle文件. 这是必需的,因为设置文件定义了哪些项目正在参与多项目构建(请参见创作多项目构建 ). 对于单项目构建,设置文件是可选的. 除了定义包含的项目之外,您可能还需要将库添加到构建脚本类路径中(请参阅组织Gradle项目 ). 让我们首先用单个项目构建进行一些自省:

例子1.单项目构建
settings.gradle
println 'This is executed during the initialization phase.'
build.gradle
println 'This is executed during the configuration phase.'

task configured {
    println 'This is also executed during the configuration phase.'
}

task test {
    doLast {
        println 'This is executed during the execution phase.'
    }
}

task testBoth {
	doFirst {
	  println 'This is executed first during the execution phase.'
	}
	doLast {
	  println 'This is executed last during the execution phase.'
	}
	println 'This is executed during the configuration phase as well.'
}
settings.gradle.kts
println("This is executed during the initialization phase.")
build.gradle.kts
println("This is executed during the configuration phase.")

tasks.register("configured") {
    println("This is also executed during the configuration phase.")
}

tasks.register("test") {
    doLast {
        println("This is executed during the execution phase.")
    }
}

tasks.register("testBoth") {
    doFirst {
        println("This is executed first during the execution phase.")
    }
    doLast {
        println("This is executed last during the execution phase.")
    }
    println("This is executed during the configuration phase as well.")
}

Output of gradle test testBoth

> gradle test testBoth
This is executed during the initialization phase.

> Configure project :
This is executed during the configuration phase.
This is also executed during the configuration phase.
This is executed during the configuration phase as well.

> Task :test
This is executed during the execution phase.

> Task :testBoth
This is executed first during the execution phase.
This is executed last during the execution phase.

BUILD SUCCESSFUL in 0s
2 actionable tasks: 2 executed
> gradle test testBoth
This is executed during the initialization phase.

> Configure project :
This is executed during the configuration phase.
This is executed during the configuration phase as well.

> Task :test
This is executed during the execution phase.

> Task :testBoth
This is executed first during the execution phase.
This is executed last during the execution phase.

BUILD SUCCESSFUL in 0s
2 actionable tasks: 2 executed

对于构建脚本,将属性访问和方法调用委托给项目对象. 同样,设置文件中的属性访问和方法调用也委托给设置对象. 查看API文档中的Settings类以获取更多信息.

Multi-project builds

多项目构建是在一次执行Gradle期间构建多个项目的构建. 您必须在设置文件中声明参与多项目构建的项目. 在致力于该主题的一章中,关于多项目构建的内容还有很多要说的(请参见创作多项目构建 ).

Project locations

多项目构建始终由具有单个根的树表示. 树中的每个元素代表一个项目. 一个项目具有一个路径,该路径表示该项目在多项目构建树中的位置. 在大多数情况下,项目路径与项目在文件系统中的物理位置一致. 但是,此行为是可配置的. 将在settings.gradle文件中创建项目树. 默认情况下,假定设置文件的位置也是根项目的位置. 但是您可以在设置文件中重新定义根项目的位置.

Building the tree

在设置文件中,您可以使用一组方法来构建项目树. 分层和平面物理布局得到特殊支持.

Hierarchical layouts

例子2.分层布局
settings.gradle
include 'project1', 'project2:child', 'project3:child1'
settings.gradle.kts
include("project1", "project2:child", "project3:child1")

include方法将项目路径作为参数. 假定项目路径等于相对物理文件系统路径. 例如,默认情况下,路径" services:api"映射到文件夹" services / api"(相对于项目根目录). 您只需要指定树的叶子即可. 这意味着包含路径" services:hotels:api"将导致创建3个项目:" services"," services:hotels"和" services:hotels:api". 有关如何使用项目路径的更多示例,可以在Settings.include(java.lang.String [])的DSL文档中找到.

Flat layouts

例子3.平面布局
settings.gradle
includeFlat 'project3', 'project4'
settings.gradle.kts
includeFlat("project3", "project4")

includeFlat方法将目录名称作为参数. 这些目录需要作为根项目目录的同级存在. 这些目录的位置被视为多项目树中根项目的子项目.

Modifying elements of the project tree

在设置文件中创建的多项目树由所谓的项目描述符组成 . 您可以随时在设置文件中修改这些描述符. 要访问描述符,您可以执行以下操作:

例子4.查找项目树的元素
settings.gradle
println rootProject.name
println project(':projectA').name
settings.gradle.kts
println(rootProject.name)
println(project(":projectA").name)

使用此描述符,您可以更改名称,项目目录和项目的构建文件.

例子5.修改项目树的元素
settings.gradle
rootProject.name = 'main'
project(':projectA').projectDir = new File(settingsDir, '../my-project-a')
project(':projectA').buildFileName = 'projectA.gradle'
settings.gradle.kts
rootProject.name = "main"
project(":projectA").projectDir = File(settingsDir, "../my-project-a")
project(":projectA").buildFileName = "projectA.gradle"

Look at the ProjectDescriptor class in the API documentation for more information.

Initialization

Gradle如何知道要执行单个项目还是进行多个项目? 如果您从带有设置文件的目录中触发多项目构建,那么事情很容易. 但是Gradle还允许您从参与构建的任何子项目中执行构建. [ 1 ]如果您在没有settings.gradle文件的项目中执行Gradle,则Gradle会通过以下方式查找settings.gradle文件:

  • 它在名为master的目录中查找,该目录与当前目录具有相同的嵌套级别.

  • 如果尚未找到,它将搜索父目录.

  • 如果尚未找到,则该构建将作为单个项目构建执行.

  • 如果找到settings.gradle文件,则Gradle会检查当前项目是否属于找到的settings.gradle文件中定义的多项目层次结构的一部分. 如果不是,则将构建作为单个项目构建执行. 否则,将执行多项目构建.

这种行为的目的是什么? Gradle需要确定您所在的项目是否是多项目构建的子项目. 当然,如果它是一个子项目,则仅构建子项目及其从属项目,但是Gradle需要为整个多项目构建创建构建配置(请参见创作多项目构建 ). 如果当前项目包含settings.gradle文件,则该构建始终按以下方式执行:

  • a single project build, if the settings.gradle file does not define a multi-project hierarchy

  • 如果settings.gradle文件确实定义了多项目层次结构,则为多项目构建.

自动搜索settings.gradle文件仅适用于具有物理层次结构或平面布局的多项目构建. 对于平面布局,您必须另外遵守上述命名约定(" master "). Gradle支持多项目构建的任意物理布局,但是对于这种任意布局,您需要从设置文件所在的目录执行构建. 有关如何从根目录运行部分构建的信息,请参阅按其绝对路径运行任务 .

Gradle为参与构建的每个项目创建一个Project对象. 对于多项目构建,这些是Settings对象(加上根项目)中指定的项目. 默认情况下,每个项目对象的名称都等于其顶级目录的名称,除根项目外的每个项目都有一个父项目. 任何项目都可以有子项目.

Configuration and execution of a single project build

对于单个项目构建, 初始化阶段之后的工作流程非常简单. 将针对初始化阶段创建的项目对象执行构建脚本. 然后Gradle查找名称与作为命令行参数传递的名称相同的任务. 如果存在这些任务名称,则会按照传递它们的顺序将它们作为单独的版本执行. 在创作多项目构建中讨论了多项目构建的配置和执行.

Responding to the lifecycle in the build script

随着构建在生命周期中的进展,您的构建脚本可以接收通知. 这些通知通常采用两种形式:您可以实现特定的侦听器接口,也可以提供一个闭包以在触发通知时执行. 下面的示例使用闭包. 有关如何使用侦听器接口的详细信息,请参阅API文档.

Project evaluation

您可以在评估项目之前和之后立即收到通知. 一旦构建脚本中的所有定义均已应用,这可用于执行其他配置,或用于某些自定义日志记录或性能分析.

下面的示例将test任务添加到hasTests属性值为true的每个项目中.

例子6.将测试任务添加到每个具有特定属性集的项目
build.gradle
allprojects {
    afterEvaluate { project ->
        if (project.hasTests) {
            println "Adding test task to $project"
            project.task('test') {
                doLast {
                    println "Running tests for $project"
                }
            }
        }
    }
}
projectA.gradle
hasTests = true
build.gradle.kts
allprojects {
    afterEvaluate {
        if (extra["hasTests"] as Boolean) {
            println("Adding test task to $project")
            tasks.register("test") {
                doLast {
                    println("Running tests for $project")
                }
            }
        }
    }
}
projectA.gradle.kts
extra["hasTests"] = true
gradle -q test输出
> gradle -q test
Adding test task to project ':projectA'
Running tests for project ':projectA'

本示例使用Project.afterEvaluate()方法添加一个闭包,该闭包在评估项目后执行.

评估任何项目时,也可能会收到通知. 本示例对项目评估执行一些自定义日志记录. 请注意,无论项目评估成功还是失败,都将收到afterProject通知.

例子7.通知
build.gradle
gradle.afterProject { project ->
    if (project.state.failure) {
        println "Evaluation of $project FAILED"
    } else {
        println "Evaluation of $project succeeded"
    }
}
build.gradle.kts
gradle.afterProject {
    if (state.failure != null) {
        println("Evaluation of $project FAILED")
    } else {
        println("Evaluation of $project succeeded")
    }
}

Output of gradle -q test

> gradle -q test
Evaluation of root project 'buildProjectEvaluateEvents' succeeded
Evaluation of project ':projectA' succeeded
Evaluation of project ':projectB' FAILED

FAILURE: Build failed with an exception.

* Where:
Build file '/home/user/gradle/samples/groovy/projectB.gradle' line: 1

* What went wrong:
A problem occurred evaluating project ':projectB'.
> broken

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 0s
> gradle -q test
Evaluation of root project 'buildProjectEvaluateEvents' succeeded
Evaluation of project ':projectA' succeeded
Evaluation of project ':projectB' FAILED

FAILURE: Build failed with an exception.

* Where:
Build file '/home/user/gradle/samples/kotlin/projectB.gradle.kts' line: 1

* What went wrong:
broken

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 0s

您还可以向Gradle添加ProjectEvaluationListener来接收这些事件.

Task creation

将任务添加到项目后,您会立即收到通知. 在构建文件中使任务可用之前,可以使用它来设置一些默认值或添加行为.

以下示例在创建每个任务时设置其srcDir属性.

例子8.为所有任务设置某些属性
build.gradle
tasks.whenTaskAdded { task ->
    task.ext.srcDir = 'src/main/java'
}

task a

println "source dir is $a.srcDir"
build.gradle.kts
tasks.whenTaskAdded {
    extra["srcDir"] = "src/main/java"
}

val a by tasks.registering

println("source dir is ${a.get().extra["srcDir"]}")
gradle -qa输出
> gradle -q a
source dir is src/main/java

您还可以将Action添加到TaskContainer中以接收这些事件.

Task execution graph ready

填充任务执行图后,您会立即收到通知(请参阅" 通过DAG配置" ).

您还可以将TaskExecutionGraphListener添加到TaskExecutionGraph来接收这些事件.

Task execution

您可以在执行任何任务之前和之后立即收到通知.

下面的示例记录每个任务执行的开始和结束. 请注意,无论任务是成功完成还是因异常而失败,都会收到afterTask通知.

例子9.记录每个任务执行的开始和结束
build.gradle
task ok

task broken(dependsOn: ok) {
    doLast {
        throw new RuntimeException('broken')
    }
}

gradle.taskGraph.beforeTask { Task task ->
    println "executing $task ..."
}

gradle.taskGraph.afterTask { Task task, TaskState state ->
    if (state.failure) {
        println "FAILED"
    }
    else {
        println "done"
    }
}
build.gradle.kts
tasks.register("ok")

tasks.register("broken") {
    dependsOn("ok")
    doLast {
        throw RuntimeException("broken")
    }
}

gradle.taskGraph.beforeTask {
    println("executing $this ...")
}

gradle.taskGraph.afterTask {
    if (state.failure != null) {
        println("FAILED")
    } else {
        println("done")
    }
}

Output of gradle -q broken

> gradle -q broken
executing task ':ok' ...
done
executing task ':broken' ...
FAILED

FAILURE: Build failed with an exception.

* Where:
Build file '/home/user/gradle/samples/groovy/build.gradle' line: 5

* What went wrong:
Execution failed for task ':broken'.
> broken

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 0s
> gradle -q broken
executing task ':ok' ...
done
executing task ':broken' ...
FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':broken'.
> broken

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 0s

您还可以对TaskExecutionGraph使用TaskExecutionListener来接收这些事件.


1 . Gradle支持部分多项目构建(请参阅创作多项目构建 ).