复合构建是包含其他构建的构建。

构建构建4

复合构建类似于 Gradle 多项目构建,只不过不是包含,而是包含subprojects整个。builds

复合构建允许您:

  • 组合通常独立开发的构建,例如,在应用程序使用的库中尝试修复错误时。

  • 将大型多项目构建分解为更小、更独立的块,这些块可以根据需要独立或一起工作。

包含在复合构建中的构建称为包含构建。包含的构建不与复合构建或其他包含的构建共享任何配置。每个包含的构建都是独立配置和执行的。

定义复合构建

以下示例演示了如何将两个通常单独开发的 Gradle 构建组合成一个复合构建。

my-composite
├── gradle
├── gradlew
├── settings.gradle.kts
├── build.gradle.kts
├── my-app
│   ├── settings.gradle.kts
│   └── app
│       ├── build.gradle.kts
│       └── src/main/java/org/sample/my-app/Main.java
└── my-utils
    ├── settings.gradle.kts
    ├── number-utils
    │   ├── build.gradle.kts
    │   └── src/main/java/org/sample/numberutils/Numbers.java
    └── string-utils
        ├── build.gradle.kts
        └── src/main/java/org/sample/stringutils/Strings.java

my-utils项目构建生成两个 Java 库,number-utils以及string-utils.构建my-app使用这些库中的函数生成可执行文件。

构建my-app不直接依赖于my-utils.相反,它声明对以下生成的库的二进制依赖关系my-utils

my-app/app/build.gradle.kts
plugins {
    id("application")
}

application {
    mainClass = "org.sample.myapp.Main"
}

dependencies {
    implementation("org.sample:number-utils:1.0")
    implementation("org.sample:string-utils:1.0")
}
my-app/app/build.gradle
plugins {
    id 'application'
}

application {
    mainClass = 'org.sample.myapp.Main'
}

dependencies {
    implementation 'org.sample:number-utils:1.0'
    implementation 'org.sample:string-utils:1.0'
}

通过定义复合构建--include-build

命令--include-build行参数将执行的构建转换为复合构建,将包含的构建中的依赖项替换为执行的构建。

./gradlew run --include-build ../my-utils例如, run from的输出my-app

$ ./gradlew --include-build ../my-utils run
> Task :app:processResources NO-SOURCE
> Task :my-utils:string-utils:compileJava
> Task :my-utils:string-utils:processResources NO-SOURCE
> Task :my-utils:string-utils:classes
> Task :my-utils:string-utils:jar
> Task :my-utils:number-utils:compileJava
> Task :my-utils:number-utils:processResources NO-SOURCE
> Task :my-utils:number-utils:classes
> Task :my-utils:number-utils:jar
> Task :app:compileJava
> Task :app:classes

> Task :app:run
The answer is 42


BUILD SUCCESSFUL in 0s
6 actionable tasks: 6 executed

通过设置文件定义复合构建

通过使用Settings.includeBuild(java.lang.Object)声明文件中包含的构建,可以使上述安排持久化settings.gradle(.kts)

设置文件可用于同时添加子项目和包含的构建。

包含的构建按位置添加:

设置.gradle.kts
includeBuild("my-utils")

在示例中,settings.gradle(.kts) 文件组合了其他单独的构建:

settings.gradle.kts
rootProject.name = "my-composite"

includeBuild("my-app")
includeBuild("my-utils")
settings.gradle
rootProject.name = 'my-composite'

includeBuild 'my-app'
includeBuild 'my-utils'

要执行构建run中的任务,请运行。my-appmy-composite./gradlew my-app:app:run

您可以选择定义一个依赖于的run任务,以便您可以执行:my-compositemy-app:app:run./gradlew run

build.gradle.kts
tasks.register("run") {
    dependsOn(gradle.includedBuild("my-app").task(":app:run"))
}
build.gradle
tasks.register('run') {
    dependsOn gradle.includedBuild('my-app').task(':app:run')
}

包括定义 Gradle 插件的构建

包含构建的一个特殊情况是定义 Gradle 插件的构建。

应使用设置文件块includeBuild内的语句包含这些构建。pluginManagement {}

使用这种机制,包含的构建还可以提供一个可以应用于设置文件本身的设置插件:

settings.gradle.kts
pluginManagement {
    includeBuild("../url-verifier-plugin")
}
settings.gradle
pluginManagement {
    includeBuild '../url-verifier-plugin'
}

对包含的构建的限制

大多数构建都可以包含在组合中,包括其他组合构建。有一些限制。

在常规构建中,Gradle 确保每个项目都有唯一的项目路径。它使项目可识别和可寻址,而不会发生冲突。

在复合构建中,Gradle 向包含的构建中的每个项目添加额外的资格,以避免项目路径冲突。在复合构建中标识项目的完整路径称为构建树路径。它由包含的构建的构建路径和项目的项目路径组成。

默认情况下,构建路径和项目路径源自磁盘上的目录名称和结构。由于包含的构建可以位于磁盘上的任何位置,因此它们的构建路径由包含目录的名称确定。这有时会导致冲突。

总而言之,包含的版本必须满足以下要求:

  • 每个包含的构建都必须具有唯一的构建路径。

  • 每个包含的构建路径不得与主构建的任何项目路径冲突。

这些条件保证了即使在复合构建中也可以唯一地标识每个项目。

如果出现冲突,解决它们的方法是更改​​包含的构建的构建名称:

设置.gradle.kts
includeBuild("some-included-build") {
    name = "other-name"
}

当一个复合构建包含在另一个复合构建中时,两个构建具有相同的父代。换句话说,嵌套的复合构建结构被展平了。

与复合构建交互

与复合构建的交互通常类似于常规的多项目构建。可以执行任务、运行测试并将构建导入到 IDE 中。

执行任务

包含的构建中的任务可以从命令行或 IDE 执行,其方式与常规多项目构建中的任务相同。执行任务将导致执行任务依赖项,以及从其他包含的构建构建依赖项工件所需的那些任务。

您可以使用完全限定路径(例如:included-build-name:project-name:taskName.项目和任务名称可以缩写

$ ./gradlew :included-build:subproject-a:compileJava
> Task :included-build:subproject-a:compileJava

$ ./gradlew :i-b:sA:cJ
> Task :included-build:subproject-a:compileJava

从命令行中排除任务,您需要提供该任务的完全限定路径。

包含的构建任务会自动执行以生成所需的依赖项工件,或者包含的构建可以声明对包含的构建中的任务的依赖关系

导入IDE

复合构建最有用的功能之一是 IDE 集成。

导入复合构建允许来自单独 Gradle 构建的源轻松地一起开发。对于每个包含的构建,每个子项目都作为 IntelliJ IDEA 模块或 Eclipse 项目包含在内。配置源依赖项,提供跨构建导航和重构。

声明由包含的构建替换的依赖项

默认情况下,Gradle 将配置每个包含的构建以确定它可以提供的依赖项。执行此操作的算法很简单。 Gradle 将检查包含的构建中项目的组和名称,并替换任何外部依赖项匹配的项目依赖项${project.group}:${project.name}

默认情况下,不会为主构建注册替换

要使主构建的(子)项目可通过 寻址${project.group}:${project.name},您可以告诉 Gradle 将主构建视为包含构建,通过自包含它:includeBuild(".")

在某些情况下,Gradle 确定的默认替换是不够的,或者必须针对特定组合进行更正。对于这些情况,可以明确声明所包含构建的替换。

例如,名为 的单项目构建anonymous-library会生成一个 Java 实用程序库,但不会声明 group 属性的值:

build.gradle.kts
plugins {
    java
}
build.gradle
plugins {
    id 'java'
}

当此构建包含在组合中时,它将尝试替换依赖模块undefined:anonymous-libraryundefined是 的默认值project.group,并且anonymous-library是根项目名称)。显然,这在复合构建中没有用。

要在复合构建中使用未发布的库,您可以显式声明它提供的替换:

settings.gradle.kts
includeBuild("anonymous-library") {
    dependencySubstitution {
        substitute(module("org.sample:number-utils")).using(project(":"))
    }
}
settings.gradle
includeBuild('anonymous-library') {
    dependencySubstitution {
        substitute module('org.sample:number-utils') using project(':')
    }
}

通过此配置,my-app复合构建将用org.sample:number-utils对 的根项目的依赖项替换对 的任何依赖项anonymous-library

停用配置的包含构建替换

如果您需要解析也可作为包含的构建的一部分提供的模块的已发布版本,则可以在已解析的配置的ResolutionStrategy上停用包含的构建替换规则。这是必要的,因为规则在构建中全局应用,并且默认情况下,Gradle 在解析过程中不考虑已发布的版本。

例如,我们创建一个单独的publishedRuntimeClasspath配置,该配置被解析为也存在于本地构建之一中的模块的已发布版本。这是通过停用全局依赖替换规则来完成的:

build.gradle.kts
configurations.create("publishedRuntimeClasspath") {
    resolutionStrategy.useGlobalDependencySubstitutionRules = false

    extendsFrom(configurations.runtimeClasspath.get())
    isCanBeConsumed = false
    attributes.attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_RUNTIME))
}
build.gradle
configurations.create('publishedRuntimeClasspath') {
    resolutionStrategy.useGlobalDependencySubstitutionRules = false

    extendsFrom(configurations.runtimeClasspath)
    canBeConsumed = false
    attributes.attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, Usage.JAVA_RUNTIME))
}

一个用例是比较已发布的 JAR 文件和本地构建的 JAR 文件。

必须声明包含的构建替换的情况

许多构建将作为包含的构建自动运行,无需声明替换。以下是一些需要声明替换的常见情况:

  • 当该archivesBaseName属性用于设置已发布工件的名称时。

  • default当发布以外的配置时。

  • MavenPom.addFilter()用于发布与项目名称不匹配的工件时。

  • 当使用maven-publishivy-publish插件进行发布且发布坐标不匹配时${project.group}:${project.name}

复合构建替换不起作用的情况

某些构建在包含在组合中时将无法正常运行,即使显式声明了依赖项替换也是如此。此限制是因为替换的项目依赖项将始终指向default目标项目的配置。每当为项目的默认配置指定的工件和依赖项与发布到存储库的内容不匹配时,复合构建可能会表现出不同的行为。

以下是发布的模块元数据可能与项目默认配置不同的一些情况:

  • default当发布以外的配置时。

  • 当使用maven-publishivy-publish插件时。

  • POMivy.xml文件作为发布的一部分进行调整时。

当包含在复合构建中时,使用这些功能的构建无法正常运行。

取决于包含的构建中的任务

虽然包含的构建彼此隔离并且无法声明直接依赖关系,但复合构建可以声明其包含的构建的任务依赖关系。使用Gradle.getIncludedBuilds()Gradle.includedBuild(java.lang.String)访问包含的构建,并通过IncludedBuild.task(java.lang.String)方法获取任务引用。

使用这些 API,可以声明对特定包含的构建中的任务的依赖关系:

build.gradle.kts
tasks.register("run") {
    dependsOn(gradle.includedBuild("my-app").task(":app:run"))
}
build.gradle
tasks.register('run') {
    dependsOn gradle.includedBuild('my-app').task(':app:run')
}

或者,您可以在部分或全部包含的构建中声明对具有特定路径的任务的依赖关系:

build.gradle.kts
tasks.register("publishDeps") {
    dependsOn(gradle.includedBuilds.map { it.task(":publishMavenPublicationToMavenRepository") })
}
build.gradle
tasks.register('publishDeps') {
    dependsOn gradle.includedBuilds*.task(':publishMavenPublicationToMavenRepository')
}

复合构建的局限性

当前实施的局限性包括:

  • 不支持包含不反映项目默认配置的发布的构建。
    请参阅复合构建不起作用的情况

  • 如果多个复合构建包含相同的构建,则并行运行时多个复合构建可能会发生冲突。
    Gradle 不会在 Gradle 调用之间共享共享复合构建的项目锁,以防止并发执行。