Gradle 6.0 的新功能
1. 概述
Gradle 6.0 版本带来了一些新功能,这将有助于使我们的构建更加高效和健壮。这些功能包括改进的依赖管理、模块元数据发布、任务配置避免以及对 JDK 13 的支持。
在本教程中,我们将介绍 Gradle 6.0 中可用的新功能。我们的示例构建文件将使用 Gradle 的 Kotlin DSL。
2. 依赖管理改进
近年来,随着每个版本的发布,Gradle 都对项目管理依赖项的方式进行了增量改进。这些依赖项改进在 Gradle 6.0 中达到顶峰。让我们回顾一下现在稳定的依赖管理改进。
2.1. API 和实现分离
java-library插件帮助我们创建一个可重用的 Java 库。该插件鼓励我们将作为库公共 API 一部分的依赖项与作为实现细节的依赖项分开。这种分离使构建更加稳定,因为用户不会意外引用不属于库公共 API 的类型。
Gradle 3.4 中引入了java-library插件及其api 和*implementation *。虽然这个插件对 Gradle 6.0 来说并不新鲜,但它提供的增强的依赖管理功能是 Gradle 6.0 中实现的全面依赖管理的一部分。
2.2. 丰富的版本
我们的项目依赖关系图通常具有相同依赖项的多个版本。发生这种情况时,Gradle 需要选择项目最终将使用哪个版本的依赖项。
Gradle 6.0 允许我们向依赖项添加丰富的版本 信息。丰富的版本信息有助于 Gradle 在解决依赖冲突时做出最佳选择。
例如,考虑一个依赖 Guava 的项目。进一步假设这个项目使用 Guava 版本 28.1-jre,即使我们知道它只使用自 10.0 版本以来就稳定的 Guava API。
我们可以使用require声明告诉 Gradle 这个项目可以使用自 10.0 以来的任何版本的 Guava,并且我们使用prefer声明告诉 Gradle 如果没有其他约束阻止它使用 28.1-jre,它应该使用 28.1-jre。因为声明添加了一个注释来解释这个丰富的版本信息:
implementation("com.google.guava:guava") {
version {
require("10.0")
prefer("28.1-jre")
because("Uses APIs introduced in 10.0. Tested with 28.1-jre")
}
}
这如何帮助我们的构建更稳定?假设该项目还依赖于必须使用 Guava 16.0 版的依赖项 foo 。foo项目的构建文件将该依赖项声明为:
dependencies {
implementation("com.google.guava:guava:16.0")
}
由于foo项目依赖于 Guava 16.0,而我们的项目同时依赖于 Guava 版本 28.1-jre 和foo,所以我们有冲突。Gradle 的默认行为是选择最新版本。然而,在这种情况下,选择最新版本是错误的选择,因为foo必须使用 16.0 版本。
在 Gradle 6.0 之前,用户必须自己解决冲突。因为 Gradle 6.0 允许我们告诉 Gradle 我们的项目可能使用低至 10.0 的 Guava 版本,所以 Gradle 会正确解决这个冲突并选择 16.0 版本。
除了require和prefer声明,我们还可以使用strict和reject声明。strict的声明描述了我们的项目必须使用的依赖版本范围。reject声明描述了与我们的项目不兼容的依赖版本。
如果我们的项目依赖于我们知道将在 Guava 29 中删除的 API,那么我们使用strict声明来阻止 Gradle 使用大于 28 的 Guava 版本。同样,如果我们知道 Guava 27.0 中存在导致我们项目的问题,我们使用reject来排除它:
implementation("com.google.guava:guava") {
version {
strictly("[10.0, 28[")
prefer("28.1-jre")
reject("27.0")
because("""
Uses APIs introduced in 10.0 but removed in 29. Tested with 28.1-jre.
Known issues with 27.0
""")
}
}
2.3. 平台
java-platform插件允许我们跨项目重用一组依赖约束。平台作者声明了一组紧密耦合的依赖关系,其版本由平台控制。
**依赖于平台的项目不需要为平台控制的任何依赖项指定版本。**Maven 用户会发现这类似于 Maven 父 POM 的*dependencyManagement *功能。
平台在多项目构建中特别有用。多项目构建中的每个项目都可能使用相同的外部依赖项,我们不希望这些依赖项的版本不同步。
让我们创建一个新平台,以确保我们的多项目构建跨项目使用相同版本的 Apache HTTP 客户端。首先,我们创建一个使用java-platform插件的项目httpclient-platform :
plugins {
`java-platform`
}
接下来,我们为该平台中包含**的依赖项声明约束。**在此示例中,我们将选择要在项目中使用的 Apache HTTP 组件的版本:
dependencies {
constraints {
api("org.apache.httpcomponents:fluent-hc:4.5.10")
api("org.apache.httpcomponents:httpclient:4.5.10")
}
}
最后,让我们添加一个使用 Apache HTTP Client Fluent API 的person-rest-client项目。在这里,我们使用platform方法添加对httpclient-platform项目的依赖。我们还将添加对org.apache.httpcomponents:fluent-hc 的依赖。此依赖项不包含版本,因为httpclient-platform确定要使用的版本:
plugins {
`java-library`
}
dependencies {
api(platform(project(":httpclient-platform")))
implementation("org.apache.httpcomponents:fluent-hc")
}
java-platform插件有助于避免由于构建中未对齐的依赖关系而在运行时出现不受欢迎的意外。
2.4. 测试工具
在 Gradle 6.0 之前,想要跨项目共享测试夹具的构建作者将这些工具提取到另一个库项目。现在,构建作者可以使用java-test-fixtures插件从他们的项目中发布测试工具。
让我们构建一个库来定义一个抽象并发布测试夹具来验证该抽象所期望的合约。
在这个例子中,我们的抽象是一个 Fibonacci 序列生成器,而测试夹具是一个JUnit 5 测试混合 。Fibonacci 序列生成器的实现者可以使用测试混合来验证他们是否正确地实现了序列生成器。
首先,让我们为我们的抽象和测试装置创建一个新项目fibonacci-spi 。该项目需要java-library和java-test-fixtures插件:
plugins {
`java-library`
`java-test-fixtures`
}
接下来,让我们将 JUnit 5 依赖项添加到我们的测试装置中。正如java-library插件定义了api和implementation配置一样,java-test-fixtures插件定义了testFixturesApi和testFixturesImplementation配置:
dependencies {
testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.1")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-engine:5.8.1")
}
有了我们的依赖项,让我们将一个 JUnit 5 测试混合添加到由java-test-fixtures插件创建的src/testFixtures/java源集。这个测试混合验证了我们的FibonacciSequenceGenerator抽象的契约:
public interface FibonacciSequenceGeneratorFixture {
FibonacciSequenceGenerator provide();
@Test
default void whenSequenceIndexIsNegative_thenThrows() {
FibonacciSequenceGenerator generator = provide();
assertThrows(IllegalArgumentException.class, () -> generator.generate(-1));
}
@Test
default void whenGivenIndex_thenGeneratesFibonacciNumber() {
FibonacciSequenceGenerator generator = provide();
int[] sequence = { 0, 1, 1, 2, 3, 5, 8 };
for (int i = 0; i < sequence.length; i++) {
assertEquals(sequence[i], generator.generate(i));
}
}
}
这就是我们与其他项目共享此测试夹具所需要做的一切。
现在,让我们创建一个新项目fibonacci-recursive,它将重用这个测试夹具。该项目将使用我们的依赖项块中的testFixtures方法声明对我们的fibonacci-spi项目中的测试夹具的依赖:
dependencies {
api(project(":fibonacci-spi"))
testImplementation(testFixtures(project(":fibonacci-spi")))
testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.1")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1")
}
最后,我们现在可以使用fibonacci-spi项目中定义的 test mix-in 来为我们的递归 fibonacci 序列生成器创建一个新的测试:
class RecursiveFibonacciUnitTest implements FibonacciSequenceGeneratorFixture {
@Override
public FibonacciSequenceGenerator provide() {
return new RecursiveFibonacci();
}
}
Gradle 6.0 java-test-fixtures插件为构建作者提供了更大的灵活性,可以跨项目共享他们的测试夹具。
3. Gradle 模块元数据发布
传统上,Gradle 项目将构建工件发布到 Ivy 或 Maven 存储库。这包括分别生成 ivy.xml 或 pom.xml 元数据文件。
ivy.xml 和 pom.xml 模型无法存储我们在本文中讨论的丰富的依赖关系信息。这意味着当我们将库发布到 Maven 或 Ivy 存储库时,下游项目不会从这些丰富的依赖信息中受益。
Gradle 6.0 通过引入Gradle 模块元数据规范 解决了这一差距。Gradle 模块元数据规范是一种 JSON 格式,支持存储 Gradle 6.0 中引入的所有增强的模块依赖元数据。
除了传统的 ivy.xml 和 pom.xml 元数据文件之外,项目还可以构建并将此元数据文件发布到 Ivy 和 Maven 存储库。这种向后兼容性允许 Gradle 6.0 项目在不破坏旧工具的情况下利用此模块元数据(如果存在) 。
要发布 Gradle 模块元数据文件,项目必须使用新的Maven 发布插件 或Ivy 发布插件 。从 Gradle 6.0 开始,这些插件默认发布 Gradle 模块元数据文件。这些插件取代了传统的发布系统 。
3.1. 将 Gradle 模块元数据发布到 Maven
让我们配置一个构建以将 Gradle 模块元数据发布到 Maven。首先,我们在构建文件中包含maven-publish :
plugins {
`java-library`
`maven-publish`
}
接下来,我们配置一个发布。一个出版物可以包含任意数量的工件。让我们添加与java配置关联的工件:
publishing {
publications {
register("mavenJava", MavenPublication::class) {
from(components["java"])
}
}
}
maven-publish插件添加了publishToMavenLocal任务。让我们使用这个任务来测试我们的 Gradle 模块元数据发布:
./gradlew publishToMavenLocal
接下来,让我们在本地 Maven 存储库中列出该工件的目录:
ls ~/.m2/repository/com/blogdemo/gradle-6/1.0.0/
gradle-6-1.0.0.jar gradle-6-1.0.0.module gradle-6-1.0.0.pom
正如我们在控制台输出中看到的,Gradle 除了生成 Maven POM 之外,还生成了 Module Metadata 文件。
4. 配置避免API
从 5.1 版开始,Gradle 鼓励插件开发人员使用新的、正在孵化的 Configuration Avoidance API。这些 API 有助于构建尽可能避免相对较慢的任务配置步骤。Gradle 将此性能改进称为Task Configuration Avoidance 。Gradle 6.0 将这个孵化 API 提升为稳定版。
虽然配置避免功能主要影响插件作者,但在其构建中创建任何自定义配置、任务或属性的构建作者也会受到影响。插件作者和构建作者现在都可以使用新的惰性配置 API 来包装具有Provider类型的对象,这样 Gradle 将避免“实现”这些对象,直到它们被需要。
让我们使用惰性 API 添加自定义任务。首先,我们使用TaskContainer.registering扩展方法注册任务。由于registering 返回一个TaskProvider,因此Task实例的创建被推迟到 Gradle 或构建作者调用TaskProvider.get()。最后,我们提供了一个闭包,它将在 Gradle 创建后配置我们的Task:
val copyExtraLibs by tasks.registering(Copy::class) {
from(extralibs)
into(extraLibsDir)
}
Gradle 的任务配置避免迁移指南 帮助插件作者和构建作者迁移到新的 API。构建作者最常见的迁移包括:
- tasks.register而不是tasks.create
- tasks.named而不是tasks.getByName
- configurations.register而不是configurations.create
- project.layout.buildDirectory.dir(“foo”)而不是File(project.buildDir, “foo”)
5. JDK 13 支持
Gradle 6.0 引入了对使用 JDK 13 构建项目的支持。我们可以通过熟悉的sourceCompatibility和targetCompatibility设置配置我们的 Java 构建以使用 Java 13:
sourceCompatibility = JavaVersion.VERSION_13
targetCompatibility = JavaVersion.VERSION_13
JDK 13中一些最令人兴奋的语言功能,例如原始字符串文字,仍处于预览状态。让我们在 Java 构建中配置任务以启用这些预览功能:
tasks.compileJava {
options.compilerArgs.add("--enable-preview")
}
tasks.test {
jvmArgs.add("--enable-preview")
}
tasks.javadoc {
val javadocOptions = options as CoreJavadocOptions
javadocOptions.addStringOption("source", "13")
javadocOptions.addBooleanOption("-enable-preview", true)
}