Gradle 和 Maven 对如何构建项目有着根本不同的看法。Gradle 提供了一个灵活可扩展的构建模型,将实际工作委托给任务图的执行。Maven 使用固定线性阶段模型,您可以将目标(执行工作的对象)附加到这些阶段。这可能会使两者之间的迁移看起来令人望而生畏,但迁移可能出奇地容易,因为 Gradle 遵循与 Maven 许多相同的约定——例如
标准项目结构
——并且其依赖管理以类似方式工作。
在这里,我们列出了一系列可供您遵循的步骤,以帮助促进任何 Maven 构建向 Gradle 的迁移
开发一种机制来验证两个构建是否生成相同的工件。
这是确保您的部署和测试不会中断至关重要的一步。即使是很小的更改,例如 JAR 中清单文件的内容,也可能导致问题。如果您的 Gradle 构建产生与 Maven 构建相同的输出,这将使您对切换充满信心,并更容易实施将带来最大收益的更改。
这并不意味着您需要在每个阶段验证每个工件,尽管这样做可以帮助您快速识别问题的根源。您应该关注关键输出,例如最终报告和已发布或部署的工件。
您需要考虑 Gradle 生成的构建输出与 Maven 相比的一些固有差异。生成的 POM 将仅包含用于消费所需的信息,并且它们将为该场景正确使用
<compile>
和
<runtime>
作用域。您可能还会看到归档中文件和类路径上文件的顺序存在差异。大多数差异将是微小的,但值得识别它们并验证它们是否可接受。
运行自动转换
.
这将创建您需要的所有 Gradle 构建文件,即使是对于
多模块构建
。对于更简单的 Maven 项目,Gradle 构建将可以直接运行!
为 Gradle 构建创建构建扫描
.
构建扫描将使您更容易可视化构建中发生的情况。对于 Gradle 构建,您将能够看到项目结构、依赖项(常规依赖项和项目间依赖项)、正在使用的插件以及构建的控制台输出。
您的构建此时可能会失败,但这没关系;扫描仍会运行。将 Gradle 构建的构建扫描与 Maven 构建的构建扫描进行比较,并继续此列表以解决失败问题。
我们建议您在迁移期间定期生成构建扫描,以帮助您识别和解决问题。如果您愿意,还可以使用构建扫描来识别
提高构建性能
的机会。
验证您的依赖项并修复所有问题
.
配置集成和功能测试
.
许多测试只需通过配置额外的源集即可轻松迁移。如果您正在使用第三方库,例如
FitNesse
,请查看 Gradle 插件门户上是否有合适的社区插件可用。
将 Maven 插件替换为 Gradle 等效项。
对于
流行插件
,Gradle 通常有可供您使用的等效插件。您可能还会发现可以
用内置的 Gradle 功能替换插件
。万不得已,您可能需要
通过您自己的自定义插件和任务类型
重新实现 Maven 插件。
本章的其余部分将更详细地探讨从 Maven 迁移构建到 Gradle 的具体方面。
Maven 构建基于
构建生命周期
的概念,该生命周期由一组固定阶段组成。这对于迁移到 Gradle 的用户来说可能是一个挑战,因为构建生命周期是
一个新概念
。尽管理解 Gradle 构建如何适应
初始化
、
配置
和
执行
阶段的结构非常重要,但 Gradle 提供了一个可以模仿 Maven 阶段的辅助功能:
生命周期任务
。
此功能允许您通过创建不执行操作但只依赖于您感兴趣的任务的任务来定义自己的“生命周期”。为了使 Maven 用户更容易过渡到 Gradle,
基本插件
(由所有 JVM 语言插件(如
Java 库插件
)应用)提供了一组与主要 Maven 阶段对应的生命周期任务。
以下是一些主要 Maven 阶段及其对应的 Gradle 任务的列表
clean
使用基本插件提供的
clean
任务。
compile
使用
Java 插件
和其他 JVM 语言插件提供的
classes
任务。它编译所有语言的所有源文件的所有类,并通过
processResources
任务执行
资源过滤
。
使用 Java 插件提供的
test
任务。它运行单元测试,更具体地说,是构成
test
源集
的测试。
package
使用基本插件提供的
assemble
任务。这会构建项目适用的包;例如,Java 库的 JAR 或传统 Java Web 应用程序的 WAR。
verify
使用基本插件提供的
check
任务。这会运行所有附加到它的验证任务,通常包括单元测试、任何静态分析任务(例如
Checkstyle
)和其他任务。如果要包含集成测试,则必须
手动配置这些
。
install
使用
Maven Publish 插件
提供的
publishToMavenLocal
任务。
请注意,Gradle 构建不需要您“安装”工件,因为您可以访问更合适的功能,例如
项目间依赖项
和
复合构建
。您应该只使用
publishToMavenLocal
来与 Maven 构建交互。
Gradle 还允许您根据本地 Maven 缓存解析依赖项,如
声明仓库
部分所述。
deploy
使用
Maven Publish 插件
提供的
publish
任务——如果您的构建正在使用旧的 Maven 插件(ID:
maven
),请务必从旧的 Maven 插件切换。这会将您的包发布到所有配置的发布仓库。还有一些任务允许您将包发布到单个仓库,即使定义了多个仓库也是如此。
请注意,Maven Publish 插件*默认情况下*不发布**源代码和 Javadoc JAR**,但这可以很容易地激活,如
Java 项目构建指南
中所述。
Gradle 使用与 Maven 相同的依赖标识符组件:group ID、artifact ID 和 version。它还支持分类器。您只需将依赖项的标识符信息替换为 Gradle 的语法即可,这在
声明依赖项
章节中有所描述。
例如,考虑以下 Maven 风格的 Log4J 依赖项
<dependencies>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
</dependencies>
此依赖项在 Gradle 构建脚本中将如下所示
字符串标识符采用 Maven 的
groupId
、
artifactId
和
version
值,尽管 Gradle 将它们称为
group
、
module
和
version
。
上面的示例提出了一个显而易见的问题:
implementation
配置是什么?它是
Java 插件
提供的标准依赖配置之一,通常用作 Maven 默认
compile
作用域的替代品。
Maven 作用域和 Gradle 标准配置之间的一些差异归结为 Gradle 区分构建模块所需的依赖项和构建依赖于该模块的模块所需的依赖项。Maven 不区分这些,因此已发布的 POM 通常包含库使用者实际不需要的依赖项。
以下是主要的 Maven 依赖项作用域以及您应该如何处理它们的迁移
compile
Gradle 有两个配置可以替代
compile
作用域:
implementation
和
api
。前者可用于任何应用 Java 插件的项目,而
api
仅适用于明确应用
Java 库插件
的项目。
在大多数情况下,您应该直接使用
implementation
配置,特别是如果您正在构建应用程序或 Web 应用程序。但是,如果您正在构建库,则可以在
构建 Java 库
部分了解哪些依赖项应使用
api
声明。有关
api
和
implementation
之间差异的更多信息,请参阅上面链接的 Java 库插件章节。
runtime
使用
runtimeOnly
配置。
Gradle 区分编译项目测试所需的依赖项和仅运行测试所需的依赖项。
测试编译所需的依赖项应针对
testImplementation
配置声明。仅运行测试所需的依赖项应使用
testRuntimeOnly
。
provided
使用
compileOnly
配置。
请注意,
War 插件
添加了
providedCompile
和
providedRuntime
依赖项配置。这些配置的行为与
compileOnly
略有不同,它们只是确保这些依赖项未打包在 WAR 文件中。但是,这些依赖项包含在运行时和测试运行时类路径中,因此如果您需要这种行为,请使用这些配置。
import
import
范围主要用于
<dependencyManagement>
块中,并且仅适用于仅 POM 的发布。阅读有关
使用物料清单
的部分,了解如何复制此行为。
您还可以指定对仅 POM 发布的常规依赖项。在这种情况下,在该 POM 中声明的依赖项被视为构建的正常传递性依赖项。
例如,假设您想为测试使用
groovy-all
POM。它是一个仅 POM 的发布,其自身的依赖项列在
<dependencies>
块中。Gradle 构建中的相应配置如下所示
Gradle 允许您从任何 Maven 兼容或 Ivy 兼容的仓库检索声明的依赖项。与 Maven 不同,它没有默认仓库,因此您必须声明至少一个。为了获得与您的 Maven 构建相同的行为,只需在 Gradle 构建中配置
Maven Central
,如下所示
最后,Gradle 允许您根据
本地 Maven 缓存/仓库
解析依赖项。这有助于 Gradle 构建与 Maven 构建协同工作,但如果您不需要这种协同工作,则不应使用此技术。如果您想通过文件系统共享发布的工件,请考虑配置一个带有
file://
URL 的
自定义 Maven 仓库
。
您可能还会对了解 Gradle 自己的
依赖项缓存
感兴趣,它比 Maven 的更可靠,并且可以由多个并发的 Gradle 进程安全地使用。
传递依赖项的存在意味着您很容易在依赖项图中出现相同依赖项的多个版本。默认情况下,Gradle 将选择图中依赖项的最新版本,但这并非总是正确的解决方案。这就是它提供几种机制来控制解析给定依赖项的哪个版本的原因。
对于每个项目,您可以使用
Maven 构建使用排除项将不需要的依赖项——或不需要的依赖项*版本*——排除在依赖项图之外。您也可以使用 Gradle 进行相同的操作,但这不一定是*正确*的做法。Gradle 提供了其他选项,可能更适合特定情况,因此您确实需要了解排除项存在的*原因*才能正确迁移它。
如果您想出于与版本无关的原因排除依赖项,请查看
排除传递依赖项
部分。它向您展示了如何将排除项附加到整个配置(通常是最合适的解决方案)或依赖项。您甚至可以轻松地将排除项应用于所有配置。
如果您对控制实际解析哪个版本的依赖项更感兴趣,请参阅上一节。
您可能会遇到两种关于可选依赖项的情况
对于第一种情况,Gradle 的行为与 Maven 相同,它只是忽略任何声明为可选的传递依赖项。它们不会被解析,并且如果相同的依赖项在依赖图中的其他地方出现为非可选,则它们对所选版本没有影响。
至于将依赖项发布为可选,Gradle 提供了一个更丰富的模型,称为
功能变体
,它允许您声明您的库提供的“可选功能”。
Maven 允许您通过在 POM 文件中定义打包类型为
pom
的
<dependencyManagement>
部分中的依赖项来共享依赖约束。然后,这种特殊类型的 POM(BOM)可以导入到其他 POM 中,以便您在所有项目中拥有一致的库版本。
Gradle 可以将此类 BOM 用于相同的目的,使用基于
platform()
和
enforcedPlatform()
方法的特殊依赖语法。您只需以正常方式声明依赖项,但将依赖项标识符包装在适当的方法中,如以下示例所示,该示例“导入”Spring Boot Dependencies BOM
dependencies {
implementation(platform("org.springframework.boot:spring-boot-dependencies:1.5.8.RELEASE")) (1)
implementation("com.google.code.gson:gson") (2)
implementation("dom4j:dom4j")
dependencies {
implementation platform('org.springframework.boot:spring-boot-dependencies:1.5.8.RELEASE') (1)
implementation 'com.google.code.gson:gson' (2)
implementation 'dom4j:dom4j'
------------------------------------------------------------
Root project 'simple-multi-module'
------------------------------------------------------------
Root project 'simple-multi-module'
+--- Project ':simple-weather'
\--- Project ':simple-webapp'
To see a list of the tasks of a project, run gradle <project-path>:tasks
For example, try running gradle :simple-weather:tasks
这让我们想到了 Maven 配置文件。它们是一种根据环境、目标平台或任何其他类似因素启用和禁用不同配置的方法。从逻辑上讲,它们不过是有限的 if 语句。由于 Gradle 具有更强大的声明条件的方法,因此它不需要正式支持配置文件(除了依赖项的 POM 中的配置文件)。您可以通过将条件与辅助构建脚本相结合轻松获得相同的行为,正如您将看到的。
假设您有不同的部署设置,具体取决于环境:本地开发(默认)、测试环境和生产。要添加类似配置文件的行为,您首先在项目根目录中为每个环境创建构建脚本:profile-default.gradle、profile-test.gradle 和 profile-prod.gradle。然后,您可以根据您自己选择的项目属性有条件地应用其中一个配置文件脚本。
以下示例演示了使用名为 buildProfile 的项目属性和仅初始化名为 message 的额外项目属性的配置文件脚本的基本技术
val buildProfile: String? by project (1)
apply(from = "profile-${buildProfile ?: "default"}.gradle.kts") (2)
tasks.register("greeting") {
// Store the message into a variable, because referencing extras from the task action
// is not compatible with the configuration cache.
val message = project.extra["message"]
doLast {
println(message) (3)
profile-default.gradle.kts
val message by extra("foobar") (4)
profile-test.gradle.kts
val message by extra("testing 1 2 3") (4)
profile-prod.gradle.kts
val message by extra("Hello, world!") (4)
if (!hasProperty('buildProfile')) ext.buildProfile = 'default' (1)
apply from: "profile-${buildProfile}.gradle" (2)
tasks.register('greeting') {
// Store the message into a variable, because referencing extras from the task action
// is not compatible with the configuration cache.
def message = project.message
doLast {
println message (3)
profile-default.gradle
ext.message = 'foobar' (4)
profile-test.gradle
ext.message = 'testing 1 2 3' (4)
profile-prod.gradle
ext.message = 'Hello, world!' (4)
需要记住的一点是,高层条件语句会使构建更难理解和维护,类似于它们如何使面向对象代码复杂化。这同样适用于配置文件。Gradle 为您提供了许多更好的方法来避免 Maven 通常需要广泛使用配置文件,例如通过配置相互变体的多个任务。请参阅 Maven 发布插件创建的 publishPubNamePublicationToRepoNameRepository 任务。
有关在 Gradle 中使用 Maven 配置文件的更详细讨论,请查看此博客文章。
Maven 有一个名为 process-resources 的阶段,默认情况下将目标 resources:resources 绑定到它。这为构建作者提供了对各种文件(例如 Web 资源、打包的属性文件等)执行变量替换的机会。
Gradle 的 Java 插件提供了一个 processResources 任务来执行相同的操作。这是一个 ProcessResources 任务,它将文件从配置的资源目录(默认情况下为 src/main/resources)复制到输出目录。与任何 ProcessResources 或 Copy 任务一样,您可以将其配置为执行文件过滤、重命名和内容过滤。
例如,这是一个将源文件视为 Groovy SimpleTemplateEngine 模板的配置,为这些模板提供 version 和 buildNumber 属性
tasks {
processResources {
expand("version" to version, "buildNumber" to currentBuildNumber)
许多 Maven 构建都包含某种形式的集成测试,Maven 通过一组额外的阶段来支持这些测试:pre-integration-test、integration-test、post-integration-test 和 verify。它还使用 Failsafe 插件代替 Surefire,这样失败的集成测试不会自动导致构建失败(因为您可能需要清理资源,例如正在运行的应用程序服务器)。
这种行为很容易在 Gradle 中使用源集复制,正如我们在关于Java 和 JVM 项目中的测试一章中所解释的那样。然后,您可以配置一个清理任务,例如一个关闭测试服务器的任务,以便在集成测试之后始终运行,无论它们成功还是失败,都使用Task.finalizedBy()。
如果您确实不想让集成测试导致构建失败,那么您可以使用 Java 测试章节的“测试执行”部分中描述的 Test.ignoreFailures 设置。
源集还为您提供了很大的灵活性,可以放置集成测试的源文件。您可以轻松地将它们保存在与单元测试相同的目录中,或者更优选地,保存在单独的源目录中,例如 src/integTest/java。要支持其他类型的测试,只需添加更多源集和 Test 任务。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>2.17</version>
<executions>
<execution>
<id>validate</id>
<phase>validate</phase>
<configuration>
<configLocation>checkstyle.xml</configLocation>
<encoding>UTF-8</encoding>
<consoleOutput>true</consoleOutput>
<failsOnError>true</failsOnError>
<linkXRef>false</linkXRef>
</configuration>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
在迁移到 Gradle 时,可以安全地忽略配置块之外的所有内容。在这种情况下,相应的 Gradle 配置如下
checkstyle {
config = resources.text.fromFile("checkstyle.xml", "UTF-8")
isShowViolations = true
isIgnoreFailures = false
checkstyle {
config = resources.text.fromFile('checkstyle.xml', 'UTF-8')
showViolations = true
ignoreFailures = false
Checkstyle 任务会自动添加为 check 任务的依赖项,其中也包括 test。如果您想确保 Checkstyle 在测试之前运行,那么只需使用 mustRunAfter(…) 方法指定一个顺序
许多 Maven 构建依赖于 AntRun 插件来定制构建,而无需实现自定义 Maven 插件的开销。Gradle 没有等效的插件,因为 Ant 在 Gradle 构建中是第一类公民,通过 ant 对象。例如,您可以像这样使用 Ant 的 Echo 任务
您可能会遇到在 Gradle 中没有对应项的 Maven 插件,特别是如果您或您组织中的某人编写了自定义插件。这种情况依赖于您理解 Gradle(以及可能还有 Maven)的工作方式,因为您通常必须编写自己的插件。
为了迁移的目的,Maven 插件有两种主要类型
为什么这很重要?因为如果您使用后者之一,您可以轻松地将其重新实现为自定义 Gradle 任务类型。只需定义与 mojo 参数对应的任务输入和输出,并将执行逻辑转换为任务动作。
如果插件依赖于 Maven 项目,那么您将不得不重写它。不要从考虑 Maven 插件如何工作开始,而是查看它试图解决什么问题。然后尝试找出如何在 Gradle 中解决该问题。您可能会发现两种构建模型差异足够大,以至于“抄写”Maven 插件代码到 Gradle 插件中将无效。从积极的方面来看,该插件可能比原始 Maven 插件更容易编写,因为 Gradle 具有更丰富的构建模型和 API。
如果您确实需要实现自定义逻辑,无论是通过构建脚本还是插件,请查看与插件开发相关的指南。另外,请务必熟悉 Gradle 的Groovy DSL 参考,它提供了您将使用的 API 的全面文档。它详细介绍了标准配置块(以及支持它们的D对象)、系统中的核心类型(Project、Task 等)以及标准任务类型集。主要入口点是Project 接口,因为它是支持构建脚本的顶级对象。
Gradle - Gradle 构建系统