每个软件项目的源代码和构建逻辑都应以有意义的方式进行组织. 该页面列出了导致可读性,可维护性项目的最佳实践. 以下各节还介绍了常见问题以及如何避免它们.

Separate language-specific source files

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

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

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

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

Separate source files per test type

一个项目定义并执行不同类型的测试是很常见的,例如单元测试,集成测试,功能测试或冒烟测试. 最好将每种测试类型的测试源代码存储在专用的源目录中. 分离的测试源代码对可维护性和关注点分离有积极的影响,因为您可以彼此独立地运行测试类型.

以下源代码树演示了如何在基于Java的项目中将单元与集成测试分离.

.
├── build.gradle
├── gradle
│   └── integration-test.gradle
├── settings.gradle
└── src
    ├── integTest
    │   └── java
    │       └── DefaultFileReaderIntegrationTest.java
    ├── main
    │   └── java
    │       ├── DefaultFileReader.java
    │       ├── FileReader.java
    │       └── StringUtils.java
    └── test
        └── java
            └── StringUtilsTest.java
.
├── build.gradle.kts
├── gradle
│   └── integration-test.gradle.kts
├── settings.gradle.kts
└── src
    ├── integTest
    │   └── java
    │       └── DefaultFileReaderIntegrationTest.java
    ├── main
    │   └── java
    │       ├── DefaultFileReader.java
    │       ├── FileReader.java
    │       └── StringUtils.java
    └── test
        └── java
            └── StringUtilsTest.java

Gradle在源集概念的帮助下对源代码目录进行建模. 通过将一个源集的实例指向一个或多个源代码目录,Gradle可以自动创建一个即用的相应编译任务.

例子1.集成测试源集
gradle/integration-test.gradle
sourceSets {
    integTest {
        java.srcDir file('src/integTest/java')
        resources.srcDir file('src/integTest/resources')
        compileClasspath += sourceSets.main.output + configurations.testRuntimeClasspath
        runtimeClasspath += output + compileClasspath
    }
}
gradle/integration-test.gradle.kts
val sourceSets = the<SourceSetContainer>()

sourceSets {
    create("integTest") {
        java.srcDir(file("src/integTest/java"))
        resources.srcDir(file("src/integTest/resources"))
        compileClasspath += sourceSets["main"].output + configurations["testRuntimeClasspath"]
        runtimeClasspath += output + compileClasspath
    }
}

源集仅负责编译源代码,而不处理执行字节码. 为了执行测试,需要建立相应的Test类型的任务.

例子2.集成测试任务
gradle/integration-test.gradle
task integTest(type: Test) {
    description = 'Runs the integration tests.'
    group = 'verification'
    testClassesDirs = sourceSets.integTest.output.classesDirs
    classpath = sourceSets.integTest.runtimeClasspath
    mustRunAfter test
}

check.dependsOn integTest
gradle/integration-test.gradle.kts
tasks.register<Test>("integTest") {
    description = "Runs the integration tests."
    group = "verification"
    testClassesDirs = sourceSets["integTest"].output.classesDirs
    classpath = sourceSets["integTest"].runtimeClasspath
    mustRunAfter(tasks["test"])
}

tasks.named("check") {
    dependsOn("integTest")
}

Use standard conventions as much as possible

所有Gradle核心插件在配置方面均遵循软件工程范例约定 . 插件逻辑可在特定上下文中为用户提供合理的默认值和标准,约定. 让我们以Java插件为例.

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

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

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

Always define a settings file

Gradle会在每次调用构建时尝试找到settings.gradle (Groovy DSL)或settings.gradle.kts (Kotlin DSL)文件. 为此,运行时将目录树的层次结构移至根目录. 一旦找到设置文件,该算法即停止搜索.

始终将settings.gradle添加到构建的根目录中,以避免对最初的性能产生影响. 此建议适用于单个项目构建以及多个项目构建. 该文件可以为空,也可以定义所需的项目名称.

具有设置文件的典型Gradle项目如下所示:

.
├── build.gradle
└── settings.gradle
.
├── build.gradle.kts
└── settings.gradle.kts

Use buildSrc to abstract imperative logic

复杂的构建逻辑通常很适合作为自定义任务或二进制插件进行封装. 自定义任务和插件实现不应存在于构建脚本中. 只要不需要在多个独立项目之间共享代码,使用buildSrc非常方便.

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

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

例子3.定制的buildSrc构建脚本
buildSrc/build.gradle
repositories {
    mavenCentral()
}

dependencies {
    testImplementation 'junit:junit:4.12'
}
buildSrc/build.gradle.kts
repositories {
    mavenCentral()
}

dependencies {
    testImplementation("junit:junit:4.12")
}

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

.
├── build.gradle
├── buildSrc
│   ├── build.gradle
│   └── src
│       ├── main
│       │   └── java
│       │       └── com
│       │           └── enterprise
│       │               ├── Deploy.java
│       │               └── DeploymentPlugin.java
│       └── test
│           └── java
│               └── com
│                   └── enterprise
│                       └── DeploymentPluginTest.java
└── settings.gradle
.
├── build.gradle.kts
├── buildSrc
│   ├── build.gradle.kts
│   └── src
│       ├── main
│       │   └── java
│       │       └── com
│       │           └── enterprise
│       │               ├── Deploy.java
│       │               └── DeploymentPlugin.java
│       └── test
│           └── java
│               └── com
│                   └── enterprise
│                       └── DeploymentPluginTest.java
└── settings.gradle.kts
buildSrc的更改会导致整个项目过时. 因此,在进行小的增量更改时,-- --no-rebuild命令行选项通常有助于获得更快的反馈. 不过,请记住要定期或至少在完成后运行完整版本.

Declare properties in gradle.properties file

In Gradle, properties can be defined in the build script, in a gradle.properties file or as parameters on the command line.

在临时方案中,通常在命令行上声明属性. 例如,您可能希望仅针对构建的这一调用传递一个特定的属性值来控制运行时行为. 构建脚本中的属性很容易引起维护麻烦,并且使构建脚本逻辑复杂化. gradle.properties有助于使属性与构建脚本保持分离,应将其视为可行的选项. 这是放置控制构建环境的属性的好位置.

典型的项目设置将gradle.properties文件放置在构建的根目录中. 另外,如果您想将该文件应用于计算机上的所有内部版本,则该文件也可以位于GRADLE_USER_HOME目录中.

.
├── build.gradle
├── gradle.properties
└── settings.gradle
.
├── build.gradle.kts
├── gradle.properties
└── settings.gradle.kts

Avoid overlapping task outputs

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

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

Standardizing builds with a custom Gradle distribution

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

该方法有一些缺点. 首先,您必须在公司中所有开发人员之间交流设置过程. 此外,统一更新初始化脚本逻辑可能会带来挑战.

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

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

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

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

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

  3. 将初始化脚本与Gradle分发包捆绑在一起.

  4. 将Gradle发行档案上传到HTTP服务器.

  5. 更改所有项目的包装器文件,以指向自定义Gradle分发的URL.

您可以在标准-all Gradle发行版的samples目录中找到一个涵盖步骤1至3的示例( userguide/organizingGradleProjects/customGradleDistribution ).

例子4.建立一个自定义的Gradle发行版
build.gradle
plugins {
    id 'base'
}

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

version = '0.1'

task downloadGradle(type: DownloadGradle) {
    description = 'Downloads the Gradle distribution with a given version.'
    gradleVersion = '4.6'
}

task createCustomGradleDistribution(type: Zip) {
    description = 'Builds custom Gradle distribution and bundles initialization scripts.'

    dependsOn downloadGradle

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

    from zipTree(downloadGradle.destinationFile)

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