Apache Ant 是一个在 Java 世界中历史悠久的构建工具,尽管使用它的团队数量正在减少,但它仍然被广泛使用。虽然灵活,但它缺乏约定和 Gradle 提供的许多强大功能。迁移到 Gradle 是值得的,这样你的构建可以变得更精简、更简单、更快,同时仍然保留你使用 Ant 时享受的灵活性。你还将受益于对多项目构建的强大支持以及易于使用、灵活的依赖管理。

从 Ant 迁移到 Gradle 的最大挑战在于没有所谓的标准 Ant 构建。这使得提供具体说明变得困难。幸运的是,Gradle 与 Ant 有一些很棒的集成功能,可以使过程相对顺利。从基于 Ivy 的依赖管理迁移并不困难,因为 Gradle 有一个基于依赖配置的类似模型,可以与 Ivy 兼容的仓库一起工作。

我们将首先概述从 Ant 迁移构建到 Gradle 时应考虑的事项,并提供一些关于如何进行的通用指导。

一般指导原则

将构建从 Ant 迁移到 Gradle 时,应牢记现有构建的性质以及期望达到的目标。是想要一个镜像现有 Ant 构建结构的 Gradle 构建?还是想要迁移到一个更符合 Gradle 习惯的方式?你主要寻求的好处是什么?

为了更好地理解,请考虑以下对比场景

  • 通过 ant.importBuild() 导入的构建

    这种方法快速、简单,适用于许多基于 Ant 的构建。你最终会得到一个与原始 Ant 构建基本相同的构建,只是你的 Ant 目标变成了 Gradle 任务。甚至目标之间的依赖关系也被保留。

    缺点是你仍然在使用 Ant 构建,并且必须继续维护它。你还失去了 Gradle 约定、其许多插件、其依赖管理等优势。你仍然可以使用增量构建信息来增强构建,但这比正常 Gradle 构建需要更多的努力。

  • 符合 Gradle 习惯的构建

    如果你想让构建面向未来,这才是最终目标。利用 Gradle 的约定和插件将使构建更小、更易于维护,其结构对许多 Java 开发者来说很熟悉。你还会发现更容易利用 Gradle 的强大功能来提高构建性能。

    主要缺点是执行迁移所需的额外工作,特别是如果现有构建复杂且有许多项目间依赖关系。然而,这些构建通常从转向符合 Gradle 习惯的方式中获益最多。此外,Gradle 提供了许多可以简化迁移的功能,例如可以直接在 Gradle 构建中使用核心和自定义 Ant 任务

理想情况下,你长期目标是接近第二个选项,但不必一步到位。

以下是一系列步骤,可帮助你决定要采取的方法以及如何进行

  1. 保留旧的 Ant 构建和新的 Gradle 构建并存。

    你知道 Ant 构建是工作的,所以你应该保留它,直到你确信 Gradle 构建能够产生所有相同的 artifact 并能完成你需要的所有事情。这也意味着用户可以尝试 Gradle 构建而无需创建源代码树的新副本。

    在准备好切换之前,不要尝试更改构建的目录和文件结构。

  2. 开发一种机制来验证两个构建产生相同的 artifact。

    这是至关重要的一步,以确保你的部署和测试不会中断。即使是小的更改,例如 JAR 中 manifest 文件的内容,也可能导致问题。如果你的 Gradle 构建产生与 Ant 构建相同的输出,这将为你和其他人提供切换的信心,并更容易实施能带来最大收益的重大更改。

  3. 确定你的构建是否是多项目构建。

    多项目构建通常比单项目构建更难迁移,需要更多工作。我们在迁移多项目构建一节中提供了一些专门的建议来帮助完成此过程。

  4. 确定每个项目使用哪些插件。

    我们预计绝大多数 Ant 构建是针对基于 JVM 的项目的,Gradle 为这些项目提供了丰富的插件,能满足你所需的大部分功能。Gradle 插件包括随 Gradle 打包的核心插件以及在插件门户上的有用社区插件。

    即使Java 插件或其衍生插件(例如Java Library 插件)与你的构建不太匹配,至少也应该考虑使用Base 插件,因为它提供了生命周期任务。

  5. 导入 Ant 构建或从头创建 Gradle 构建。

    这一步很大程度上取决于你的构建需求。如果一系列 Gradle 插件可以完成你的 Ant 构建所做的绝大部分工作,那么创建不依赖于 Ant 构建的全新 Gradle 构建脚本可能更有意义。你可以自行实现缺失的部分,或使用现有的 Ant 任务

    另一种方法是将 Ant 构建导入 Gradle 构建脚本,然后逐步替换 Ant 构建功能。这允许你在每个阶段都有一个工作的 Gradle 构建,但这需要一些工作来使 Gradle 任务与 Ant 任务正确协作。你可以在使用导入的构建中了解更多信息。

  6. 为现有目录和文件结构配置你的构建

    Gradle 利用约定来消除旧构建相关的许多样板代码,并使熟悉这些约定的用户更容易使用新构建。但这并不意味着你必须遵循它们。

    Gradle 提供了许多配置选项,允许进行一定程度的定制。这些选项通常通过提供约定的插件来提供。例如,用于生产 Java 代码的标准源目录结构 — src/main/java — 由 Java 插件提供,该插件允许你配置不同的源路径。许多路径可以通过Project对象上的属性进行修改。

  7. 如果愿意,可以迁移到标准的 Gradle 约定

    一旦你确信 Gradle 构建产生了与 Ant 构建相同的 artifact 和其他资源,就可以考虑迁移到标准约定,例如源目录路径。这样做将允许你移除覆盖这些约定所需的额外配置。更改后,新的团队成员也会发现更容易使用该构建。

    是否值得付出此步骤的努力和潜在中断,取决于你,这反过来又取决于你的具体构建和团队。

本章的其余部分涵盖了你在迁移过程中可能遇到的一些常见场景,例如依赖管理和使用 Ant 任务。

使用导入的构建

使用配置缓存不支持导入 Ant 构建。你需要完成向 Gradle 的转换才能获得缓存的好处。

许多迁移的第一步将涉及使用 ant.importBuild()导入 Ant 构建。那么,如何在不一次性替换所有内容的情况下转向标准的 Gradle 构建呢?

需要记住的重要一点是,Ant 目标变成了真正的 Gradle 任务,这意味着你可以修改它们的任务依赖关系、附加额外的任务操作等等。这允许你用原生 Gradle 任务替换等效的 Ant 任务,同时保留与现有其他任务的任何关联。

例如,想象你有一个想要从 Ant 迁移到 Gradle 的 Java 库项目。Gradle 构建脚本中有一行导入 Ant 构建,现在你想要使用标准的 Gradle 机制来编译 Java 源文件。但是,你想要继续使用现有的 package 任务来创建库的 JAR 文件。

用图示表示,该场景如下所示,其中每个框代表一个目标/任务

ant task migration

思路是用标准的 Gradle compileJava 任务替换 Ant 的 build 任务。此替换过程涉及几个步骤

  1. 应用Java Library 插件

    这提供了图中所示的 compileJava 任务。

  2. 重命名旧的 build 任务。

    名称 buildBase 插件(通过 Java Library 插件)提供的标准 build 任务冲突。

  3. 配置编译以使用现有目录结构。

    Ant 构建很可能不符合标准的 Gradle 目录结构,因此你需要告诉 Gradle 在哪里找到源文件以及将编译后的类放置在哪里,以便 package 可以找到它们。

  4. 更新任务依赖关系。

    compileJava 必须依赖于 preparepackage 必须依赖于 compileJava 而不是 ant_build,并且 assemble 必须依赖于 package 而不是标准的 Gradle jar 任务。

应用插件就像在 Gradle 构建脚本开头(即在 ant.importBuild() 之前)插入一个 plugins {} 块一样简单。以下是应用 Java Library 插件的方法

build.gradle.kts
plugins {
    `java-library`
}
build.gradle
plugins {
    id 'java-library'
}

要重命名 build 任务,请使用接受转换器的 AntBuilder.importBuild() 变体,如下所示

build.gradle.kts
ant.importBuild("build.xml") { oldTargetName ->
    if (oldTargetName == "build") "ant_build" else oldTargetName  (1)
}
build.gradle
ant.importBuild('build.xml') { String oldTargetName ->
    return oldTargetName == 'build' ? 'ant_build' : oldTargetName  (1)
}
1 build 目标重命名为 ant_build 并保留所有其他目标不变

配置不同的源路径在构建 Java & JVM 项目中描述。你可以用类似的方式更改编译后的类的输出目录。

例如,如果原始 Ant 构建将这些路径存储在 Ant 属性中:Java 源文件的 src.dir 和输出的 classes.dir。以下是配置 Gradle 使用这些路径的方法

示例 3. 配置源集
build.gradle.kts
sourceSets {
    main {
        java.setSrcDirs(listOf(ant.properties["src.dir"]))
        java.destinationDirectory = file(ant.properties["classes.dir"] ?: layout.buildDirectory.dir("classes"))
    }
}
build.gradle
sourceSets {
    main {
        java {
            srcDirs = [ ant.properties['src.dir'] ]
            destinationDirectory = file(ant.properties['classes.dir'])
        }
    }
}

最终你应该切换到你的项目类型的标准目录结构,这样你就可以移除此定制。

最后一步很简单,涉及使用 Task.dependsOn 属性和 Task.dependsOn() 方法来分离和链接任务。该属性适用于替换依赖关系,而该方法是添加到现有依赖关系的推荐方式。

以下是示例场景所需的任务依赖配置,它应该在 Ant 构建导入之后

build.gradle.kts
tasks {
    compileJava {
        dependsOn("prepare")  (1)
    }
    named("package") {
        setDependsOn(listOf(compileJava))  (2)
    }
    assemble {
        setDependsOn(listOf("package"))  (3)
    }
}
build.gradle
compileJava.dependsOn 'prepare'  (1)
tasks.named('package') { dependsOn = [ 'compileJava' ] }  (2)
assemble.dependsOn = [ 'package' ]  (3)
1 使编译依赖于 prepare 任务
2 packageant_build 任务中分离出来,并使其依赖于 compileJava
3 assemble 从标准的 Gradle jar 任务中分离出来,并使其改为依赖于 package

这四个步骤将成功地用 Gradle 实现替换旧的 Ant 编译。即使是这样小的迁移,也能让你受益于 Gradle 的增量 Java 编译,从而加快构建速度。

这是一个分阶段迁移的示例。在此阶段,将资源处理(例如属性文件)和打包与编译一起进行可能更有意义。

你需要问自己的一个重要问题是,每个阶段要迁移多少任务。一次性迁移的任务越多越好,但风险随着 Ant 构建中受更改影响的自定义步骤数量而增加。

例如,如果 Ant 构建遵循相当标准的编译、静态资源、打包和单元测试方法,那么将所有这些一起迁移可能值得。但是,如果构建对编译后的类执行了一些额外的处理,或者在处理静态资源时做了独特的事情,那么将这些任务分成单独的阶段可能更值得。

管理依赖关系

Ant 构建通常采用以下两种方法之一来处理二进制依赖关系(例如库)

  • 将它们与项目一起存储在本地 "lib" 目录中

  • 使用Apache Ivy 来管理它们

它们每种都需要不同的技术来迁移到 Gradle,但你会发现无论哪种情况,过程都很简单。让我们在以下各节中详细查看每种情况。

从目录提供依赖关系

当你尝试迁移一个将其依赖关系存储在文件系统(本地或网络上)中的构建时,应该考虑是否最终要转向使用远程仓库的托管依赖关系。这是因为你可以通过以下两种方式之一将文件系统依赖关系纳入 Gradle 构建中

如果你采用第一种方法,则更容易迁移到从 Maven 或 Ivy 兼容仓库提供的托管依赖关系,但这要求所有文件都符合命名约定 "<moduleName>-<version>.<extension>"。

如果你的依赖关系按照标准的 Maven 仓库布局存储 — <repoDir>/<group>/<module>/<version> — 那么你可以使用 file:// URL 定义一个自定义 Maven 仓库

为了演示这两种技术,考虑一个在其 libs 目录中包含以下库 JAR 文件的项目

libs
├── our-custom.jar
├── awesome-framework-2.0.jar
└── utility-library-1.0.jar

文件 our-custom.jar 没有版本号,因此必须将其添加为文件依赖关系。另外两个 JAR 匹配所需的命名约定,可以声明为从平面目录仓库检索的普通模块依赖关系

以下示例构建脚本演示了如何将所有这些库整合到构建中

build.gradle.kts
repositories {
    flatDir {
        name = "libs dir"
        dir(file("libs"))  (1)
    }
}

dependencies {
    implementation(files("libs/our-custom.jar"))  (2)
    implementation(":awesome-framework:2.0")     (3)
    implementation(":utility-library:1.0")  (3)
}
build.gradle
repositories {
    flatDir {
        name = 'libs dir'
        dir file('libs')  (1)
    }
}

dependencies {
    implementation files('libs/our-custom.jar')  (2)
    implementation ':awesome-framework:2.0'  (3)
    implementation ':utility-library:1.0'  (3)
}
1 指定包含 JAR 文件的目录路径
2 为无版本号的 JAR 声明一个文件依赖关系
3 使用标准依赖坐标声明依赖关系 — 注意没有指定 group,但每个标识符都有一个前导的 :,表示一个空的 group

以上示例将把 our-custom.jarawesome-framework-2.0.jarutility-library-1.0.jar 添加到 implementation 配置中,该配置用于编译项目的代码。

你也可以在这些模块依赖关系中指定 group,即使它们实际上没有 group。这是因为平面目录仓库会简单地忽略此信息。然后,如果你稍后添加一个普通的 Maven 或 Ivy 兼容仓库,Gradle 将从该仓库下载那些声明了 group 的模块依赖关系,而不是从平面目录仓库下载。

迁移 Ivy 依赖关系

Apache Ivy 是一个独立的依赖管理工具,与 Ant 一起广泛使用。它的工作方式与 Gradle 类似。实际上,它们都允许你

  • 定义自己的配置

  • 彼此扩展配置

  • 将依赖关系附加到配置

  • 从 Ivy 兼容仓库解析依赖关系

  • 将 artifact 发布到 Ivy 兼容仓库

最显著的区别是 Gradle 为特定类型的项目提供了标准配置。例如,Java 插件定义了像 implementationtestImplementationruntimeOnly 这样的配置。如果需要,你可以定义自己的依赖配置

因此,从 Ivy 迁移到 Gradle 通常很直接

  • 将模块描述符中的依赖声明转录到 Gradle 构建脚本的dependencies {} 块中,理想情况下使用你应用的任何插件提供的标准配置。

  • 将模块描述符中的任何配置声明转录到构建脚本的configurations {} 块中,适用于无法被 Gradle 标准配置替换的任何自定义配置。

  • 将 Ivy settings 文件中的解析器转录到构建脚本的repositories {} 块中。

有关更多信息,请参阅关于管理依赖配置声明依赖关系声明仓库的章节。

Ivy 提供了几个 Ant 任务,用于处理 Ivy 获取依赖关系的过程。该过程的基本步骤包括

  1. 配置(Configure) — 应用 Ivy settings 文件中定义的配置

  2. 解析(Resolve) — 定位声明的依赖关系,并在必要时将其下载到缓存

  3. 检索(Retrieve) — 将缓存的依赖关系复制到另一个目录

Gradle 的过程类似,但你无需显式调用前两个步骤,因为它会自动执行。第三个步骤根本不会发生 — 除非你创建一个任务来完成 — 因为 Gradle 通常直接在类路径中使用依赖缓存中的文件,并将其作为组装应用程序包的来源。

让我们更详细地看看 Ivy 的步骤如何映射到 Gradle

配置

Gradle 大多数与依赖相关的配置都已内置在构建脚本中,就像你看到的 dependencies {} 块等元素一样。另一个特别重要的配置元素是 resolutionStrategy,可以从依赖配置中访问。这提供了许多你可能从 Ivy 的冲突管理器中获得的功能,是控制传递性依赖关系和缓存的强大方法。

一些 Ivy 配置选项在 Gradle 中没有等效项。例如,没有锁定策略,因为 Gradle 保证其依赖缓存是并发安全的。没有“最新策略”方法,因为采用可靠的单一冲突解决策略更简单。如果选择了“错误”版本,可以使用强制版本或其他解析选项进行覆盖。

有关更多信息,请参阅关于控制传递性依赖关系的章节。

解析

在构建开始时,Gradle 会自动解析你声明的任何依赖关系,并将其下载到其缓存。Gradle 在仓库中搜索这些依赖关系,搜索顺序由仓库声明的顺序定义。

值得注意的是,Gradle 支持与 Ivy 相同的动态版本语法,因此你仍然可以使用 1.0.+ 这样的约定。你还可以使用特殊的 latest.integrationlatest.release 标签。如果你决定使用此类动态变化的依赖关系,可以通过 resolutionStrategy 配置它们的缓存行为。

如果你正在使用动态和/或变化的依赖关系,可能还需要考虑依赖锁定。这是一种使构建更可靠并确保可重现性的方法。

检索

如前所述,Gradle 不会自动从依赖缓存中复制文件。其标准任务通常直接使用这些文件。如果你想将依赖关系复制到本地目录,可以在构建脚本中使用像这样的 Copy 任务

build.gradle.kts
tasks.register<Copy>("retrieveRuntimeDependencies") {
    into(layout.buildDirectory.dir("libs"))
    from(configurations.runtimeClasspath)
}
build.gradle
tasks.register('retrieveRuntimeDependencies', Copy) {
    into layout.buildDirectory.dir('libs')
    from configurations.runtimeClasspath
}

配置也是一个文件集合,因此可以在 from() 配置中使用。你可以使用类似的技术将配置附加到编译任务或生成文档的任务。有关更多示例以及 Gradle 文件 API 的信息,请参阅关于处理文件的章节。

发布 artifact

使用 Ivy 管理依赖关系的项目通常也使用它来将 JAR 和其他 artifact 发布到仓库。如果你正在迁移这样的构建,那么你会很高兴知道 Gradle 内置支持将 artifact 发布到 Ivy 兼容仓库。

在尝试迁移构建的这一特定方面之前,请阅读发布章节,了解 Gradle 的发布模型。该章节的示例基于 Maven 仓库,但相同的模型也适用于 Ivy 仓库。

基本的迁移过程如下

完成所有这些后,您将能够为每个发布项生成一个 Ivy 模块描述符,并将其发布到一个或多个仓库。

假设您已定义了一个名为 "myLibrary" 的发布项和一个名为 "myRepo" 的仓库。那么 Ivy 的 Ant 任务将映射到以下 Gradle 任务:

  • <deliver>generateDescriptorFileForMyLibraryPublication

  • <publish>publishMyLibraryPublicationToMyRepoRepository

还有一个方便的 publish 任务,可以将所有发布项发布到所有仓库。如果您想将发布项限制到特定仓库,请查阅 Publishing 章节的相关部分

关于依赖版本

默认情况下,Ivy 在生成模块描述符时会自动将依赖的动态版本替换为解析后的“静态”版本。Gradle 不会模仿这种行为,声明的依赖版本会保持不变。

您可以使用 Nebula Ivy Resolved Plugin 来复制默认的 Ivy 行为。或者,您可以自定义描述符文件,使其包含您想要的版本。

处理自定义 Ant 任务

Ant 的一个优点是创建自定义任务并将其集成到构建中相当容易。如果您有这样的任务,那么将其迁移到 Gradle 构建有两种主要选项:

第一个选项通常快速且简单。如果您想将任务集成到增量构建中,则必须使用增量构建运行时 API。您通常还需要处理 Ant 的路径和文件集,这可能不太方便。

第二个选项更适合长期使用。Gradle 任务类型通常比 Ant 任务更简单,因为它们不必处理基于 XML 的接口。您还可以获得 Gradle 丰富 API 的好处。这种方法使得基于类型化属性的类型安全增量构建 API 成为可能。

处理文件

Ant 有许多用于处理文件的任务,其中大部分在 Gradle 中都有对应的功能。与 Ant 到 Gradle 迁移的其他领域一样,您可以在 Gradle 构建中使用这些 Ant 任务。但是,我们强烈建议尽可能迁移到原生的 Gradle 结构,以便构建可以受益于以下方面:

  • 增量构建

  • 更容易与构建的其他部分集成,例如依赖配置

  • 更符合惯例的构建脚本

使用没有直接对应功能的 Ant 任务(例如 <checksum><chown>)可能很方便。然而,从长远来看,最好将这些转换为使用标准 Java API 或第三方库的原生 Gradle 任务类型。

以下是 Ant 构建中最常用的与文件相关的元素及其 Gradle 对应功能:

  • <copy> — 首选 Gradle Copy 任务类型

  • <zip>(加上 Java 变体)— 首选 Zip 任务类型(以及 JarWarEar

  • <unzip> — 首选使用 Project.zipTree() 方法与 Copy 任务结合使用

您可以在 处理文件 章节中看到 Gradle 文件 API 的几个示例,并了解更多信息。

关于路径和文件集

Ant 使用类似路径的结构和文件集的概念,使用户能够处理文件和目录的集合。Gradle 有一个更简单、更强大的模型,它基于 FileCollections 和 FileTrees,可以在构建中作为对象进行处理。这两种类型都允许基于 Ant 的 glob 语法进行过滤,例如 **/books_*。您可以在 处理文件 章节中了解更多关于这些类型和 Gradle 文件 API 的其他方面。

如果需要与需要 Ant 路径和文件集的 Ant 任务交互,您可以通过 ant 对象在构建中构建它们。关于 Ant 集成 的章节包含同时使用 <path><fileset> 的示例。此外,FileCollection 上还有一个方法可以将文件集合转换为文件集或类似的 Ant 类型。

迁移 Ant 属性

Ant 使用属性映射来存储可在整个构建中重用的值。这种方法的最大缺点是属性值都是字符串,并且属性本身表现得像全局变量。

在 Gradle 中与 Ant 属性交互

有时您可能希望直接在 Gradle 构建中使用某个 Ant 任务,并且该任务需要设置一个或多个 Ant 属性。

如果是这种情况,您可以轻松地通过 ant 对象设置这些属性,如从 Gradle 使用 Ant 章节所述。

Gradle 确实使用了一种类似的方式,即项目属性,这是一种合理的方式来参数化构建。这些属性可以从命令行、gradle.properties 文件中设置,或通过特殊命名的系统属性和环境变量设置。

如果您有现有的 Ant 属性文件,您可以将其内容复制到项目的 gradle.properties 文件中。只需注意以下几点:

  • gradle.properties 中设置的属性不会覆盖在构建脚本中使用相同名称定义的额外项目属性

  • 导入的 Ant 任务不会自动“看到”Gradle 项目属性 — 您必须将它们复制到 Ant 属性映射中才能实现这一点

另一个需要理解的重要因素是,Gradle 构建脚本与面向对象的 API 一起工作,并且通常最好尽可能使用任务、源集和其他对象的属性。例如,此构建脚本片段创建了将 Javadoc 文档打包为 JAR 并解压它的任务,通过它们的属性链接任务:

build.gradle.kts
val tmpDistDir = layout.buildDirectory.dir("dist")

tasks.register<Jar>("javadocJarArchive") {
    from(tasks.javadoc)  (1)
    archiveClassifier = "javadoc"
}

tasks.register<Copy>("unpackJavadocs") {
    from(zipTree(tasks.named<Jar>("javadocJarArchive").get().archiveFile))  (2)
    into(tmpDistDir)  (3)
}
build.gradle
def tmpDistDir = layout.buildDirectory.dir('dist')

tasks.register('javadocJarArchive', Jar) {
    from javadoc  (1)
    archiveClassifier = 'javadoc'
}

tasks.register('unpackJavadocs', Copy) {
    from zipTree(javadocJarArchive.archiveFile)  (2)
    into tmpDistDir  (3)
}
1 打包所有 javadoc 的输出文件 — 相当于 from javadoc.destinationDir
2 使用由 javadocJar 任务持有的 Javadoc JAR 的位置
3 使用名为 tmpDistDir 的项目属性来定义 'dist' 目录的位置

正如您从 tmpDistDir 的示例中看到的,通常需要通过属性定义路径,这就是为什么 Gradle 也提供了额外属性,可以附加到项目、任务和一些其他类型的对象上。

迁移多项目构建

多项目构建是迁移的一个特殊挑战,因为 Ant 没有标准的方法来组织它们或处理项目间依赖。

幸运的是,Gradle 的多项目支持可以处理相当多样化的项目结构,并且在构建和维护多项目构建方面提供了比 Ant 更健壮和有用的支持。ant.importBuild() 方法还可以透明地处理 <ant><antcall> 任务,这允许分阶段迁移。

以下步骤重点介绍了一种迁移多项目构建的建议方法:

  1. 首先学习Gradle 如何配置多项目构建

  2. 在构建的每个项目中创建一个 Gradle 构建脚本,将其内容设置为此行:

    ant.importBuild 'build.xml'
    ant.importBuild("build.xml")

    build.xml 替换为与项目对应的实际 Ant 构建文件的路径。如果没有对应的 Ant 构建文件,则将 Gradle 构建脚本留空。即使您的构建不适合这种迁移方法,也请继续执行这些步骤,看看是否仍有可能进行分阶段迁移。

  3. 创建一个 settings 文件,包含所有现在拥有 Gradle 构建脚本的项目。

  4. 实现项目间依赖。

    您的多项目构建中的某些项目将依赖于该构建中一个或多个其他项目生成的制品。此类项目需要确保它们依赖的项目已经生成了制品,并且知道这些制品的路径。

    确保生成所需的制品通常意味着通过 <ant> 任务调用其他项目的构建。不幸的是,这会绕过 Gradle 构建,抵消您对 Gradle 构建脚本所做的任何更改。您需要将使用 <ant> 任务的目标替换为 Gradle 任务依赖

    例如,您的 web 项目依赖于同一次构建中的一个“util”库。web 项目的 Ant 构建文件可能包含如下目标:

    web/build.xml
    <target name="buildRequiredProjects">
        <ant dir="${root.dir}/util" target="build"/>  (1)
    </target>
    1 root.dir 必须由构建定义

    这可以在相应的 Gradle 构建脚本中用项目间任务依赖来替换,如下例所示,该示例假定“web”项目的“compile”任务需要事先构建“util”:

    web/build.gradle.kts
    ant.importBuild("build.xml")
    
    tasks {
        named<Task>("compile") {
            setDependsOn(listOf(":util:build"))
        }
    }
    web/build.gradle
    ant.importBuild 'build.xml'
    
    compile.dependsOn = [ ':util:build' ]

    这不像 Gradle 的项目依赖那样健壮或强大,但它解决了眼前的问题,而无需对构建进行重大更改。只需注意删除或覆盖任何委托给其他子项目的任务的依赖,例如 buildRequiredProjects 任务。

  5. 找出不依赖于其他项目的项目,并将它们迁移到更符合惯例的 Gradle 构建脚本。

    遵循本指南其余部分的建议来迁移单个项目构建。如前所述,您应尽可能使用 Gradle 标准插件。这可能意味着您需要为每个构建添加一个额外的 copy 任务,将生成的制品复制到其余 Ant 构建所期望的位置。

  6. 当项目仅依赖于已完全迁移到 Gradle 构建的项目时,再迁移这些项目。

    此时,您应该能够切换到使用附加到相应依赖配置的正式项目依赖。

  7. 一旦 Ant 构建的任何部分都不再依赖于某个项目,就可以清理该项目了。

    我们在步骤 5 中提到,您可能需要添加 copy 任务来满足依赖的 Ant 构建的要求。一旦这些构建被迁移,此类构建逻辑将不再需要,应该被移除。

在此过程结束时,您应该拥有一个您确信可以正常工作的 Gradle 构建,并且构建逻辑比以前少得多。

延伸阅读

本章涵盖了从 Ant 构建迁移到 Gradle 的主要特定主题。剩下的是一些在迁移后可能有用的一些其他领域:

最后,本指南仅涉及了 Gradle 的部分功能,我们鼓励您从用户手册的其他章节中了解其余部分。