每个软件项目的源代码和构建逻辑都应以有意义的方式组织。此页面列出了实现可读、可维护项目的最佳实践。以下各节还将介绍常见问题以及如何避免这些问题。

分离特定语言的源文件

Gradle 的语言插件建立了发现和编译源代码的约定。例如,应用 Java 插件的项目将自动编译目录 src/main/java 中的代码。其他语言插件也遵循相同的模式。目录路径的最后一部分通常指示源文件的预期语言。

一些编译器能够跨编译同一源目录中的多种语言。Groovy 编译器可以处理将 Java 和 Groovy 源文件混合放置在 src/main/groovy 中的情况。Gradle 建议您根据语言将源文件放在目录中,因为这样构建性能更高,并且用户和构建都可以做出更强的假设。

以下源文件树包含 Java 和 Kotlin 源文件。Java 源文件位于 src/main/java 中,而 Kotlin 源文件位于 src/main/kotlin 中。

.
├── build.gradle.kts
└── src
    └── main
        ├── java
        │   └── HelloWorld.java
        └── kotlin
            └── Utils.kt
.
├── build.gradle
└── src
    └── main
        ├── java
        │   └── HelloWorld.java
        └── kotlin
            └── Utils.kt

为每种测试类型分离源文件

项目定义和执行不同类型的测试(例如单元测试、集成测试、功能测试或冒烟测试)非常常见。理想情况下,每种测试类型的测试源代码应存储在专用的源目录中。分离的测试源代码对可维护性和关注点分离具有积极影响,因为您可以彼此独立地运行测试类型。

请查看 示例,该示例演示了如何向基于 Java 的项目添加单独的集成测试配置。

尽可能使用标准约定

所有 Gradle 核心插件都遵循软件工程范式 约定优于配置。插件逻辑在特定上下文中为用户提供明智的默认值和标准,即约定。以 Java 插件 为例。

  • 它将目录 src/main/java 定义为编译的默认源目录。

  • 已编译源代码和其他工件(如 JAR 文件)的输出目录为 build

通过坚持默认约定,项目的新开发人员可以立即知道如何找到方向。虽然这些约定可以重新配置,但这使得构建脚本用户和作者更难以管理构建逻辑及其结果。除非您需要适应遗留项目的布局,否则请尽量坚持默认约定。请参阅相关插件的参考页面,了解其默认约定。

始终定义 settings 文件

每次调用构建时,Gradle 都会尝试查找 settings.gradle(Groovy DSL)或 settings.gradle.kts(Kotlin DSL)文件。为此,运行时会遍历目录树的层次结构,直到根目录。一旦找到 settings 文件,算法就会停止搜索。

始终将 settings.gradle 添加到构建的根目录,以避免初始性能影响。该文件可以是空的,也可以定义项目的所需名称。

多项目构建必须在多项目层次结构的根项目中有一个 settings.gradle(.kts) 文件。这是必需的,因为 settings 文件定义了哪些项目参与 多项目构建。除了定义包含的项目外,您可能还需要它来 向构建脚本类路径添加库

以下示例显示了标准的 Gradle 项目布局

.
├── settings.gradle.kts
├── subproject-one
│   └── build.gradle.kts
└── subproject-two
    └── build.gradle.kts
.
├── settings.gradle
├── subproject-one
│   └── build.gradle
└── subproject-two
    └── build.gradle

使用 buildSrc 抽象命令式逻辑

复杂的构建逻辑通常是封装为自定义 task 或二进制插件的良好候选对象。自定义 task 和插件实现不应存在于构建脚本中。只要代码不需要在多个独立项目之间共享,使用 buildSrc 就非常方便。

目录 buildSrc 被视为 包含的构建。在发现该目录后,Gradle 会自动编译此代码并将其放在构建脚本的类路径中。对于多项目构建,只能有一个 buildSrc 目录,该目录必须位于根项目目录中。与 脚本插件 相比,应优先使用 buildSrc,因为它更易于维护、重构和测试代码。

buildSrc 使用适用于 Java 和 Groovy 项目的相同 源代码约定。它还提供对 Gradle API 的直接访问。可以在 buildSrc 下的专用 build.gradle 中声明其他依赖项。

buildSrc/build.gradle.kts
repositories {
    mavenCentral()
}

dependencies {
    testImplementation("junit:junit:4.13")
}
buildSrc/build.gradle
repositories {
    mavenCentral()
}

dependencies {
    testImplementation 'junit:junit:4.13'
}

包含 buildSrc 的典型项目具有以下布局。buildSrc 下的任何代码都应使用类似于应用程序代码的包。可选地,如果需要其他配置(例如,应用插件或声明依赖项),则 buildSrc 目录可以托管构建脚本。

.
├── buildSrc
│   ├── build.gradle.kts
│   └── src
│       ├── main
│       │   └── java
│       │       └── com
│       │           └── enterprise
│       │               ├── Deploy.java
│       │               └── DeploymentPlugin.java
│       └── test
│           └── java
│               └── com
│                   └── enterprise
│                       └── DeploymentPluginTest.java
├── settings.gradle.kts
├── subproject-one
│   └── build.gradle.kts
└── subproject-two
    └── build.gradle.kts
.
├── buildSrc
│   ├── build.gradle
│   └── src
│       ├── main
│       │   └── java
│       │       └── com
│       │           └── enterprise
│       │               ├── Deploy.java
│       │               └── DeploymentPlugin.java
│       └── test
│           └── java
│               └── com
│                   └── enterprise
│                       └── DeploymentPluginTest.java
├── settings.gradle
├── subproject-one
│   └── build.gradle
└── subproject-two
    └── build.gradle

buildSrc 中的更改会导致整个项目变为过时。

因此,在进行小的增量更改时,--no-rebuild 命令行选项 通常有助于获得更快的反馈。请记住定期运行完整构建。

gradle.properties 文件中声明属性

在 Gradle 中,属性可以在构建脚本、gradle.properties 文件中或作为命令行上的参数进行定义。

通常在命令行上声明属性以用于临时场景。例如,您可能希望传入特定的属性值,以便仅为此构建调用控制运行时行为。构建脚本中的属性很容易成为维护难题,并使构建脚本逻辑变得复杂。gradle.properties 有助于将属性与构建脚本分离,应将其作为可行的选项进行探索。它是放置 控制构建环境的属性 的好位置。

典型的项目设置是将 gradle.properties 文件放在构建的根目录中。或者,如果您希望它应用于计算机上的所有构建,则该文件也可以位于 GRADLE_USER_HOME 目录中。

.
├── gradle.properties
└── settings.gradle.kts
├── subproject-a
│   └── build.gradle.kts
└── subproject-b
    └── build.gradle.kts
.
├── gradle.properties
└── settings.gradle
├── subproject-a
│   └── build.gradle
└── subproject-b
    └── build.gradle

避免重叠的任务输出

Task 应定义输入和输出,以获得 增量构建功能 的性能优势。在声明 Task 的输出时,请确保用于写入输出的目录在项目中的所有 Task 中都是唯一的。

混合或覆盖不同 Task 生成的输出文件会损害最新检查,从而导致构建速度变慢。反过来,这些文件系统更改可能会阻止 Gradle 的 构建缓存 正确识别和缓存原本可以缓存的 Task。

使用自定义 Gradle 发行版标准化构建

企业通常希望通过定义通用约定或规则来标准化组织中所有项目的构建平台。您可以使用初始化脚本来实现此目的。初始化脚本 使跨单台计算机上的所有项目应用构建逻辑变得非常容易。例如,声明内部仓库及其凭据。

这种方法有一些缺点。首先,您必须在公司中的所有开发人员之间沟通设置过程。此外,统一更新初始化脚本逻辑可能具有挑战性。

自定义 Gradle 发行版是解决此问题的实用解决方案。自定义 Gradle 发行版由标准 Gradle 发行版以及一个或多个自定义初始化脚本组成。初始化脚本与发行版捆绑在一起,并在每次运行构建时应用。开发人员只需将其签入的 Wrapper 文件指向自定义 Gradle 发行版的 URL 即可。

自定义 Gradle 发行版还可以在发行版的根目录中包含 gradle.properties 文件,该文件提供组织范围的 控制构建环境的属性集

以下步骤是创建自定义 Gradle 发行版的典型步骤

  1. 实现用于下载和重新打包 Gradle 发行版的逻辑。

  2. 使用所需的逻辑定义一个或多个初始化脚本。

  3. 将初始化脚本与 Gradle 发行版捆绑在一起。

  4. 将 Gradle 发行版归档文件上传到 HTTP 服务器。

  5. 更改所有项目的 Wrapper 文件,以指向自定义 Gradle 发行版的 URL。

build.gradle
plugins {
    id 'base'
}

// This is defined in buildSrc
import org.gradle.distribution.DownloadGradle

version = '0.1'

tasks.register('downloadGradle', DownloadGradle) {
    description = 'Downloads the Gradle distribution with a given version.'
    gradleVersion = '4.6'
}

tasks.register('createCustomGradleDistribution', Zip) {
    description = 'Builds custom Gradle distribution and bundles initialization scripts.'

    dependsOn downloadGradle

    def projectVersion = project.version
    archiveFileName = downloadGradle.gradleVersion.map { gradleVersion ->
        "mycompany-gradle-${gradleVersion}-${projectVersion}-bin.zip"
    }

    from zipTree(downloadGradle.destinationFile)

    from('src/init.d') {
        into "${downloadGradle.distributionNameBase.get()}/init.d"
    }
}