多项目构建中的子项目通常共享一些公共依赖项。

structuring builds 3

为了避免在每个子项目构建脚本中复制粘贴相同的 Java 版本和库,Gradle 提供了一个特殊的目录用于存储共享构建逻辑,该逻辑可以自动应用于子项目。

buildSrc 中共享逻辑

buildSrc 是 Gradle 识别并保护的目录,它具有一些优势:

  1. 可重用的构建逻辑:

    buildSrc 允许您以结构化的方式组织和集中管理自定义构建逻辑、Task 和插件。在 buildSrc 中编写的代码可以在整个项目中重用,从而更易于维护和共享通用构建功能。

  2. 与主构建隔离:

    放置在 buildSrc 中的代码与项目的其他构建脚本是隔离的。这有助于使主构建脚本更清晰,更专注于项目特定的配置。

  3. 自动编译和类路径:

    buildSrc 目录的内容会自动编译并包含在主构建的类路径中。这意味着在 buildSrc 中定义的类和插件可以直接在您项目的构建脚本中使用,无需额外配置。

  4. 易于测试:

    由于 buildSrc 是一个独立的构建,因此可以轻松测试您的自定义构建逻辑。您可以为构建代码编写测试,确保其行为符合预期。

  5. Gradle 插件开发:

    如果您正在为项目开发自定义 Gradle 插件,buildSrc 是一个方便放置插件代码的地方。这使得插件在您的项目中易于访问。

buildSrc 目录被视为一个 包含构建 (included build)

对于多项目构建,只能有一个 buildSrc 目录,它必须位于根项目目录中。

使用 buildSrc 的缺点是,对其进行的任何更改都会使项目中的每个 Task 失效,并需要重新运行。

buildSrc 使用适用于 Java、Groovy 和 Kotlin 项目的相同源代码约定。它还提供了对 Gradle API 的直接访问。

一个典型的包含 buildSrc 的项目具有以下布局:

.
├── buildSrc
│   ├── src
│   │   └──main
│   │      └──kotlin
│   │         └──MyCustomTask.kt    (1)
│   ├── shared.gradle.kts   (2)
│   └── build.gradle.kts
├── api
│   ├── src
│   │   └──...
│   └── build.gradle.kts    (3)
├── services
│   └── person-service
│       ├── src
│       │   └──...
│       └── build.gradle.kts    (3)
├── shared
│   ├── src
│   │   └──...
│   └── build.gradle.kts
└── settings.gradle.kts
1 创建 MyCustomTask Task。
2 一个共享构建脚本。
3 使用 MyCustomTask Task 和共享构建脚本。
.
├── buildSrc
│   ├── src
│   │   └──main
│   │      └──groovy
│   │         └──MyCustomTask.groovy    (1)
│   ├── shared.gradle   (2)
│   └── build.gradle
├── api
│   ├── src
│   │   └──...
│   └── build.gradle    (3)
├── services
│   └── person-service
│       ├── src
│       │   └──...
│       └── build.gradle    (3)
├── shared
│   ├── src
│   │   └──...
│   └── build.gradle
└── settings.gradle
1 创建 MyCustomTask Task。
2 一个共享构建脚本。
3 使用 MyCustomTask Task 和共享构建脚本。

buildSrc 中,创建构建脚本 shared.gradle(.kts)。它包含多个子项目共有的依赖项和其他构建信息。

shared.gradle.kts
repositories {
    mavenCentral()
}

dependencies {
    implementation("org.slf4j:slf4j-api:1.7.32")
}
shared.gradle
repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.slf4j:slf4j-api:1.7.32'
}

buildSrc 中,还创建了 MyCustomTask。它是一个辅助 Task,用作多个子项目构建逻辑的一部分。

MyCustomTask.kt
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction

open class MyCustomTask : DefaultTask() {
    @TaskAction
    fun calculateSum() {
        // Custom logic to calculate the sum of two numbers
        val num1 = 5
        val num2 = 7
        val sum = num1 + num2

        // Print the result
        println("Sum: $sum")
    }
}
MyCustomTask.groovy
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction

class MyCustomTask extends DefaultTask {
    @TaskAction
    void calculateSum() {
        // Custom logic to calculate the sum of two numbers
        int num1 = 5
        int num2 = 7
        int sum = num1 + num2

        // Print the result
        println "Sum: $sum"
    }
}

MyCustomTask Task 在 apishared 项目的构建脚本中使用。由于它是 buildSrc 的一部分,该 Task 会自动可用。

shared.gradle(.kts) 文件也会被应用。

build.gradle.kts
// Apply any other configurations specific to your project

// Use the build script defined in buildSrc
apply(from = rootProject.file("buildSrc/shared.gradle.kts"))

// Use the custom task defined in buildSrc
tasks.register<MyCustomTask>("myCustomTask")
build.gradle
// Apply any other configurations specific to your project

// Use the build script defined in buildSrc
apply from: rootProject.file('buildSrc/shared.gradle')

// Use the custom task defined in buildSrc
tasks.register('myCustomTask', MyCustomTask)

使用约定插件共享逻辑

Gradle 推荐组织构建逻辑的方式是使用其插件系统。

我们可以编写一个插件来封装项目中几个子项目共有的构建逻辑。这种插件称为约定插件(convention plugin)

虽然编写插件超出本节范围,但构建 Gradle 项目的推荐方式是将公共构建逻辑放在位于 buildSrc 中的约定插件中。

让我们看一个示例项目:

.
├── buildSrc
│   ├── src
│   │   └──main
│   │      └──kotlin
│   │         └──myproject.java-conventions.gradle.kts  (1)
│   └── build.gradle.kts
├── api
│   ├── src
│   │   └──...
│   └── build.gradle.kts    (2)
├── services
│   └── person-service
│       ├── src
│       │   └──...
│       └── build.gradle.kts    (2)
├── shared
│   ├── src
│   │   └──...
│   └── build.gradle.kts    (2)
└── settings.gradle.kts
1 创建 myproject.java-conventions 约定插件。
2 应用 myproject.java-conventions 约定插件。
.
├── buildSrc
│   ├── src
│   │   └──main
│   │      └──groovy
│   │         └──myproject.java-conventions.gradle  (1)
│   └── build.gradle
├── api
│   ├── src
│   │   └──...
│   └── build.gradle    (2)
├── services
│   └── person-service
│       ├── src
│       │   └──...
│       └── build.gradle    (2)
├── shared
│   ├── src
│   │   └──...
│   └── build.gradle    (2)
└── settings.gradle
1 创建 myproject.java-conventions 约定插件。
2 应用 myproject.java-conventions 约定插件。

此构建包含三个子项目:

settings.gradle.kts
rootProject.name = "dependencies-java"
include("api", "shared", "services:person-service")
settings.gradle
rootProject.name = 'dependencies-java'
include 'api', 'shared', 'services:person-service'

buildSrc 目录中创建的约定插件的源代码如下:

buildSrc/src/main/kotlin/myproject.java-conventions.gradle.kts
plugins {
    id("java")
}

group = "com.example"
version = "1.0"

repositories {
    mavenCentral()
}

dependencies {
    testImplementation("junit:junit:4.13")
}
buildSrc/src/main/groovy/myproject.java-conventions.gradle
plugins {
    id 'java'
}

group = 'com.example'
version = '1.0'

repositories {
    mavenCentral()
}

dependencies {
    testImplementation "junit:junit:4.13"
}

为了使约定插件编译,需要在 buildSrc 目录的构建文件中应用基本配置:

buildSrc/build.gradle.kts
plugins {
    `kotlin-dsl`
}

repositories {
    mavenCentral()
}
buildSrc/build.gradle
plugins {
    id 'groovy-gradle-plugin'
}

约定插件被应用于 apisharedperson-service 子项目。

api/build.gradle.kts
plugins {
    id("myproject.java-conventions")
}

dependencies {
    implementation(project(":shared"))
}
shared/build.gradle.kts
plugins {
    id("myproject.java-conventions")
}
services/person-service/build.gradle.kts
plugins {
    id("myproject.java-conventions")
}

dependencies {
    implementation(project(":shared"))
    implementation(project(":api"))
}
api/build.gradle
plugins {
    id 'myproject.java-conventions'
}

dependencies {
    implementation project(':shared')
}
shared/build.gradle
plugins {
    id 'myproject.java-conventions'
}
services/person-service/build.gradle
plugins {
    id 'myproject.java-conventions'
}

dependencies {
    implementation project(':shared')
    implementation project(':api')
}

不要使用跨项目配置

在子项目之间共享构建逻辑的一种不恰当方式是使用 subprojects {}allprojects {} DSL 结构进行跨项目配置

避免使用 subprojects {}allprojects {}

使用跨项目配置,可以将构建逻辑注入子项目,这在其构建脚本中并不明显。

从长远来看,跨项目配置通常会变得越来越复杂,成为负担。跨项目配置还可能引入项目之间的配置时耦合,这会阻止按需配置(configuration-on-demand)等优化措施正常工作。

约定插件 vs 跨项目配置

跨项目配置最常见的两种用法可以使用约定插件更好地建模:

  1. 将插件或其他配置应用于特定类型的子项目。
    通常,跨项目配置逻辑是“如果子项目类型为 X,则配置 Y”。这相当于直接将 X-conventions 插件应用于子项目。

  2. 从特定类型的子项目提取信息。
    此用例可以使用传出配置变体 (outgoing configuration variants) 建模。