Gradle 提供了几种机制来直接影响依赖解析引擎的行为。

与作为解析过程输入的依赖约束组件元数据规则不同,这些机制允许您将规则直接注入解析引擎。由于它们的直接影响,它们可能被视为可能掩盖潜在问题的暴力解决方案,例如新依赖项的引入。

通常建议仅在其他方法不足时才使用解析规则

如果您正在开发一个,最好使用依赖约束,因为它们会与您的消费者共享。

以下是 Gradle 中的关键解析策略

# 策略 信息

1

强制使用依赖项版本

强制使用依赖项的特定版本。

2

模块替换

用一个模块替换另一个模块并给出解释。

3

依赖项替换

动态替换依赖项。

4

组件选择规则

控制允许使用模块的哪些版本。拒绝已知有问题或不受欢迎的特定版本。

5

设置默认依赖项

当未显式声明依赖项时,自动向配置添加依赖项。

6

排除传递性依赖项

排除您不希望包含在依赖图中的传递性依赖项。

7

强制失败的解析策略

在解析过程中发生特定条件时强制构建失败。

8

禁用传递性依赖项

依赖项默认是传递的,但您可以为单个依赖项禁用此行为。

9

依赖项解析规则和其他条件

在依赖项解析时直接转换或过滤它们,以及其他特殊情况。

1. 强制使用依赖项版本

您可以强制使用依赖项的特定版本,无论构建脚本的其他部分可能请求或解析哪个版本。

这对于确保一致性并避免因使用相同依赖项的不同版本而引起的冲突非常有用。

build.gradle.kts
configurations {
    "compileClasspath" {
        resolutionStrategy.force("commons-codec:commons-codec:1.9")
    }
}

dependencies {
    implementation("org.apache.httpcomponents:httpclient:4.5.4")
}
build.gradle
configurations {
    compileClasspath {
        resolutionStrategy.force 'commons-codec:commons-codec:1.9'
    }
}

dependencies {
    implementation 'org.apache.httpcomponents:httpclient:4.5.4'
}
强制使用依赖项的特定版本应该是一个有意识且经过深思熟虑的决定。这可能导致冲突或意外行为,特别是在传递性依赖项依赖于不同版本时。

2. 模块替换

虽然通常最好使用能力来管理模块冲突,但在某些情况下(尤其是在使用旧版本的 Gradle 时)需要采用不同的方法。在这些情况下,模块替换规则提供了一种解决方案,允许您指定一个旧库已被一个新库替换。

模块替换规则允许您声明一个旧库已被一个新库替换。例如,从 google-collections 迁移到 guava 涉及将模块从 com.google.collections:google-collections 重命名为 com.google.guava:guava。由于模块坐标不同,Gradle 不会将此类更改视为版本冲突,因此会影响冲突解决。

考虑一个场景,两个库都出现在依赖图中。您的项目依赖于 guava,但一个传递性依赖项引入了 google-collections。这可能导致运行时错误,因为 Gradle 不会自动将其解析为冲突。常见的解决方案包括

  • 声明排除规则以避免 google-collections

  • 避免引入旧库的依赖项。

  • 升级不再使用 google-collections 的依赖项。

  • 降级到 google-collections(不推荐)。

  • 分配能力,使 google-collectionsguava 互斥。

对于大型项目,这些方法可能不够。通过声明模块替换,您可以在所有项目中全局解决此问题,使组织能够整体处理此类冲突。

build.gradle.kts
dependencies {
    modules {
        module("com.google.collections:google-collections") {
            replacedBy("com.google.guava:guava", "google-collections is now part of Guava")
        }
    }
}
build.gradle
dependencies {
    modules {
        module("com.google.collections:google-collections") {
            replacedBy("com.google.guava:guava", "google-collections is now part of Guava")
        }
    }
}

一旦声明,Gradle 在冲突解决期间将 guava 的任何版本视为优于 google-collections,确保只有 guava 出现在 classpath 中。但是,如果只有 google-collections 模块存在,除非发生冲突,否则它不会被自动替换。

有关更多示例,请参阅 ComponentMetadataHandler 的 DSL 参考。

Gradle 目前不支持用多个模块替换一个模块,但可以用单个模块替换多个模块。

3. 依赖项替换

依赖项替换规则允许用指定的替代项替换项目和模块依赖项,使其可互换。虽然类似于依赖项解析规则,但它们通过启用项目和模块依赖项之间的替换提供了更大的灵活性。

但是,添加依赖项替换规则会影响配置解析的时机。配置不是在第一次使用时解析,而是在任务图构建期间解析,如果配置稍后被修改或依赖于任务执行期间发布的模块,这可能会导致问题。

解释

  • 配置可以作为任务的输入,并在解析时包含项目依赖项。

  • 如果项目依赖项是任务的输入(通过配置),则构建这些 artifact 的任务将作为依赖项添加。

  • 为了确定作为任务输入的项目依赖项,Gradle 必须解析配置输入。

  • 由于 Gradle 任务图在任务执行开始后就固定了,因此 Gradle 需要在执行任何任务之前执行此解析。

如果没有替换规则,Gradle 会假设外部模块依赖项不引用项目依赖项,从而简化依赖项遍历。有了替换规则后,此假设不再成立,因此 Gradle 必须完全解析配置才能确定项目依赖项。

用项目依赖项替换外部模块依赖项

依赖项替换可用于将外部模块替换为本地开发的项目,这在测试模块的补丁版本或未发布版本时非常有用。

无论是否指定版本,都可以替换外部模块

build.gradle.kts
configurations.all {
    resolutionStrategy.dependencySubstitution {
        substitute(module("org.utils:api"))
            .using(project(":api")).because("we work with the unreleased development version")
        substitute(module("org.utils:util:2.5")).using(project(":util"))
    }
}
build.gradle
configurations.all {
    resolutionStrategy.dependencySubstitution {
        substitute module("org.utils:api") using project(":api") because "we work with the unreleased development version"
        substitute module("org.utils:util:2.5") using project(":util")
    }
}
  • 被替换的项目必须是多项目构建的一部分(通过 settings.gradle 包含)。

  • 替换将模块依赖项替换为项目依赖项,并设置任务依赖项,但不会自动将该项目包含在构建中。

用模块替换项目依赖项

您还可以使用替换规则在多项目构建中用外部模块替换项目依赖项。

此技术可以通过允许从仓库下载某些依赖项而不是在本地构建来加速开发

build.gradle.kts
configurations.all {
    resolutionStrategy.dependencySubstitution {
        substitute(project(":api"))
            .using(module("org.utils:api:1.3")).because("we use a stable version of org.utils:api")
    }
}
build.gradle
configurations.all {
    resolutionStrategy.dependencySubstitution {
        substitute project(":api") using module("org.utils:api:1.3") because "we use a stable version of org.utils:api"
    }
}
  • 被替换的模块必须包含版本。

  • 即使替换后,该项目仍然是多项目构建的一部分,但在解析配置时不会执行构建它的任务。

有条件地替换依赖项

您可以使用依赖项替换规则在多项目构建中有条件地将模块依赖项替换为本地项目。

当您想在依赖项存在本地开发版本时使用该版本,否则回退到外部模块时,这特别有用

build.gradle.kts
configurations.all {
    resolutionStrategy.dependencySubstitution.all {
        requested.let {
            if (it is ModuleComponentSelector && it.group == "org.example") {
                val targetProject = findProject(":${it.module}")
                if (targetProject != null) {
                    useTarget(targetProject)
                }
            }
        }
    }
}
build.gradle
configurations.all {
    resolutionStrategy.dependencySubstitution.all { DependencySubstitution dependency ->
        if (dependency.requested instanceof ModuleComponentSelector && dependency.requested.group == "org.example") {
            def targetProject = findProject(":${dependency.requested.module}")
            if (targetProject != null) {
                dependency.useTarget targetProject
            }
        }
    }
}
  • 仅当找到与依赖项名称匹配的本地项目时才会发生替换。

  • 本地项目必须已包含在多项目构建中(通过 settings.gradle)。

用另一个变体替换依赖项

您可以将依赖项替换为另一个变体,例如在平台依赖项和常规库依赖项之间切换。

当您的构建过程需要根据特定条件更改依赖项类型时,这非常有用

configurations.all {
    resolutionStrategy.dependencySubstitution {
        all {
            if (requested is ModuleComponentSelector && requested.group == "org.example" && requested.version == "1.0") {
                useTarget(module("org.example:library:1.0")).because("Switching from platform to library variant")
            }
        }
    }
}
  • 替换基于请求的依赖项的属性(例如 group 和 version)。

  • 这种方法允许您从平台组件切换到库,反之亦然。

  • 它使用 Gradle 的变体感知引擎,根据配置和消费者属性确保选择正确的变体。

在处理复杂的依赖图时,通常需要这种灵活性,因为不同组件类型(平台、库)需要动态切换。

用属性替换依赖项

基于属性替换依赖项允许您通过针对特定属性(例如平台与常规库)来覆盖组件的默认选择。

此技术对于在复杂构建中管理平台和库依赖项非常有用,特别是当您想使用常规库但平台依赖项声明错误时

lib/build.gradle.kts
dependencies {
    // This is a platform dependency but you want the library
    implementation(platform("com.google.guava:guava:28.2-jre"))
}
lib/build.gradle
dependencies {
    // This is a platform dependency but you want the library
    implementation platform('com.google.guava:guava:28.2-jre')
}

在此示例中,替换规则针对 com.google.guava:guava 的平台版本,并将其替换为常规库版本

consumer/build.gradle.kts
configurations.all {
    resolutionStrategy.dependencySubstitution {
        substitute(platform(module("com.google.guava:guava:28.2-jre")))
            .using(module("com.google.guava:guava:28.2-jre"))
    }
}
consumer/build.gradle
configurations.all {
    resolutionStrategy.dependencySubstitution {
        substitute(platform(module('com.google.guava:guava:28.2-jre'))).
            using module('com.google.guava:guava:28.2-jre')
    }
}

如果没有 platform 关键字,替换将不会专门针对平台依赖项。

以下规则执行相同的替换,但使用更精细的变体表示法,允许自定义依赖项的属性

consumer/build.gradle.kts
configurations.all {
    resolutionStrategy.dependencySubstitution {
        substitute(variant(module("com.google.guava:guava:28.2-jre")) {
            attributes {
                attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.REGULAR_PLATFORM))
            }
        }).using(module("com.google.guava:guava:28.2-jre"))
    }
}
consumer/build.gradle
configurations.all {
    resolutionStrategy.dependencySubstitution {
        substitute variant(module('com.google.guava:guava:28.2-jre')) {
            attributes {
                attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category, Category.REGULAR_PLATFORM))
            }
        } using module('com.google.guava:guava:28.2-jre')
    }
}

通过使用基于属性的替换,您可以精确控制哪些依赖项被替换,确保 Gradle 在您的构建中解析正确的版本和变体。

有关完整参考,请参阅 DependencySubstitutions API

复合构建中,您必须匹配完全相同的请求依赖项属性的规则不适用。使用复合构建时,Gradle 会自动匹配请求的属性。换句话说,如果包含另一个构建,则意味着您将替换模块的所有变体替换为包含构建中的等效变体。

用具有能力(Capabilities)的依赖项替换依赖项

您可以将依赖项替换为包含特定能力(Capabilities)的不同变体。能力允许您指定依赖项的特定变体提供一组相关特性或功能,例如测试夹具

此示例使用能力将其常规依赖项替换为其测试夹具

build.gradle.kts
configurations.testCompileClasspath {
    resolutionStrategy.dependencySubstitution {
        substitute(module("com.acme:lib:1.0")).using(variant(module("com.acme:lib:1.0")) {
            capabilities {
                requireCapability("com.acme:lib-test-fixtures")
            }
        })
    }
}
build.gradle
configurations.testCompileClasspath {
    resolutionStrategy.dependencySubstitution {
        substitute(module('com.acme:lib:1.0'))
            .using variant(module('com.acme:lib:1.0')) {
            capabilities {
                requireCapability('com.acme:lib-test-fixtures')
            }
        }
    }
}

在此,我们将常规的 com.acme:lib:1.0 依赖项替换为其 lib-test-fixtures 变体。requireCapability 函数指定新变体必须具有 com.acme:lib-test-fixtures 能力,确保为测试目的选择了正确版本的依赖项。

替换规则中的能力用于精确匹配依赖项,Gradle 只替换匹配所需能力的依赖项。

有关变体替换 API 的完整参考,请参阅 DependencySubstitutions API

用分类器(Classifier)或 Artifact 替换依赖项

您可以将带有分类器(Classifier)的依赖项替换为不带分类器的依赖项,反之亦然。分类器通常用于表示同一 Artifact 的不同版本,例如特定于平台的构建或具有不同 API 的依赖项。尽管 Gradle 不鼓励使用分类器,但它提供了一种处理仍在使用分类器的情况下的替换方法。

考虑以下设置

consumer/build.gradle.kts
dependencies {
    implementation("com.google.guava:guava:28.2-jre")
    implementation("co.paralleluniverse:quasar-core:0.8.0")
    implementation(project(":lib"))
}
consumer/build.gradle
dependencies {
    implementation 'com.google.guava:guava:28.2-jre'
    implementation 'co.paralleluniverse:quasar-core:0.8.0'
    implementation project(':lib')
}

在上面的示例中,对 quasar 的第一级依赖项让我们认为 Gradle 会解析 quasar-core-0.8.0.jar,但事实并非如此。

构建失败,出现此消息

Execution failed for task ':consumer:resolve'.
> Could not resolve all files for configuration ':consumer:runtimeClasspath'.
   > Could not find quasar-core-0.8.0-jdk8.jar (co.paralleluniverse:quasar-core:0.8.0).
     Searched in the following locations:
         https://repo.maven.apache.org/maven2/co/paralleluniverse/quasar-core/0.8.0/quasar-core-0.8.0-jdk8.jar

这是因为存在对另一个项目 lib 的依赖项,而 lib 本身依赖于不同版本的 quasar-core

lib/build.gradle.kts
dependencies {
    implementation("co.paralleluniverse:quasar-core:0.7.10:jdk8")
}
lib/build.gradle
dependencies {
    implementation "co.paralleluniverse:quasar-core:0.7.10:jdk8"
}
  • 消费者依赖于不带分类器(Classifier)的 quasar-core:0.8.0

  • lib 项目依赖于带有 jdk8 分类器(Classifier)的 quasar-core:0.7.10

  • Gradle 的冲突解决选择了更高版本 (0.8.0),但 quasar-core:0.8.0 没有 jdk8 分类器(Classifier),导致解析错误。

要解决此冲突,您可以指示 Gradle 在解析 quasar-core 依赖项时忽略分类器(Classifier)

consumer/build.gradle.kts
configurations.all {
    resolutionStrategy.dependencySubstitution {
        substitute(module("co.paralleluniverse:quasar-core"))
            .using(module("co.paralleluniverse:quasar-core:0.8.0"))
            .withoutClassifier()
    }
}
consumer/build.gradle
configurations.all {
    resolutionStrategy.dependencySubstitution {
        substitute module('co.paralleluniverse:quasar-core') using module('co.paralleluniverse:quasar-core:0.8.0') withoutClassifier()
    }
}

此规则有效地将图中找到的任何对 quasar-core 的依赖项替换为不带分类器(Classifier)的依赖项。

如果您需要使用特定的分类器(Classifier)或 artifact 进行替换,可以在替换规则中指定分类器或 artifact 详情。

有关更多详细信息,请参阅

4. 组件选择规则

组件选择规则可能会影响当有多个匹配版本选择器的版本可用时应选择哪个组件实例。规则应用于每个可用版本,并允许明确拒绝某个版本。

这使得 Gradle 可以忽略不满足规则设置条件的任何组件实例。例如:

  • 对于像 1.+ 这样的动态版本,某些版本可能会被明确地从选择中拒绝。

  • 对于像 1.4 这样的静态版本,可以根据额外的组件元数据(例如 Ivy 分支属性)拒绝实例,从而允许使用后续仓库中的实例。

规则通过 ComponentSelectionRules 对象配置。配置的每个规则都将以 ComponentSelection 对象作为参数调用,该对象包含有关正在考虑的候选版本的信息。调用 ComponentSelection.reject(java.lang.String) 会导致给定的候选版本被明确拒绝,在这种情况下,该候选版本将不会被用于选择器。

以下示例展示了一个规则,该规则禁止模块的特定版本,但允许动态版本选择次优候选版本

build.gradle.kts
configurations {
    implementation {
        resolutionStrategy {
            componentSelection {
                // Accept the highest version matching the requested version that isn't '1.5'
                all {
                    if (candidate.group == "org.sample" && candidate.module == "api" && candidate.version == "1.5") {
                        reject("version 1.5 is broken for 'org.sample:api'")
                    }
                }
            }
        }
    }
}

dependencies {
    implementation("org.sample:api:1.+")
}
build.gradle
configurations {
    implementation {
        resolutionStrategy {
            componentSelection {
                // Accept the highest version matching the requested version that isn't '1.5'
                all { ComponentSelection selection ->
                    if (selection.candidate.group == 'org.sample' && selection.candidate.module == 'api' && selection.candidate.version == '1.5') {
                        selection.reject("version 1.5 is broken for 'org.sample:api'")
                    }
                }
            }
        }
    }
}

dependencies {
    implementation 'org.sample:api:1.+'
}

请注意,版本选择从最高版本开始应用。选定的版本将是所有组件选择规则接受的第一个版本。

如果没有任何规则明确拒绝某个版本,则该版本被视为可接受。

类似地,规则可以针对特定模块。模块必须以 group:module 的形式指定

build.gradle.kts
configurations {
    create("targetConfig") {
        resolutionStrategy {
            componentSelection {
                withModule("org.sample:api") {
                    if (candidate.version == "1.5") {
                        reject("version 1.5 is broken for 'org.sample:api'")
                    }
                }
            }
        }
    }
}
build.gradle
configurations {
    targetConfig {
        resolutionStrategy {
            componentSelection {
                withModule("org.sample:api") { ComponentSelection selection ->
                    if (selection.candidate.version == "1.5") {
                        selection.reject("version 1.5 is broken for 'org.sample:api'")
                    }
                }
            }
        }
    }
}

组件选择规则在选择版本时也可以考虑组件元数据。可以考虑的可能额外元数据包括 ComponentMetadataIvyModuleDescriptor

请注意,此额外信息可能并非始终可用,因此应检查其是否为 null

build.gradle.kts
configurations {
    create("metadataRulesConfig") {
        resolutionStrategy {
            componentSelection {
                // Reject any versions with a status of 'experimental'
                all {
                    if (candidate.group == "org.sample" && metadata?.status == "experimental") {
                        reject("don't use experimental candidates from 'org.sample'")
                    }
                }
                // Accept the highest version with either a "release" branch or a status of 'milestone'
                withModule("org.sample:api") {
                    if (getDescriptor(IvyModuleDescriptor::class)?.branch != "release" && metadata?.status != "milestone") {
                        reject("'org.sample:api' must have testing branch or milestone status")
                    }
                }
            }
        }
    }
}
build.gradle
configurations {
    metadataRulesConfig {
        resolutionStrategy {
            componentSelection {
                // Reject any versions with a status of 'experimental'
                all { ComponentSelection selection ->
                    if (selection.candidate.group == 'org.sample' && selection.metadata?.status == 'experimental') {
                        selection.reject("don't use experimental candidates from 'org.sample'")
                    }
                }
                // Accept the highest version with either a "release" branch or a status of 'milestone'
                withModule('org.sample:api') { ComponentSelection selection ->
                    if (selection.getDescriptor(IvyModuleDescriptor)?.branch != "release" && selection.metadata?.status != 'milestone') {
                        selection.reject("'org.sample:api' must be a release branch or have milestone status")
                    }
                }
            }
        }
    }
}

声明组件选择规则时,ComponentSelection 参数是始终必需的参数。

5. 默认依赖项

您可以为配置设置默认依赖项,以确保在未指定显式依赖项时使用默认版本。

这对于依赖于带版本工具并希望在用户未指定版本时提供默认值的插件非常有用

build.gradle.kts
configurations {
    create("pluginTool") {
        defaultDependencies {
            add(project.dependencies.create("org.gradle:my-util:1.0"))
        }
    }
}
build.gradle
configurations {
    pluginTool {
        defaultDependencies { dependencies ->
            dependencies.add(project.dependencies.create("org.gradle:my-util:1.0"))
        }
    }
}

在此示例中,pluginTool 配置将使用 org.gradle:my-util:1.0 作为默认依赖项,除非指定了其他版本。

6. 排除传递性依赖项

要完全排除特定配置的传递性依赖项,请使用 Configuration.exclude(Map) 方法。

排除传递性依赖项应该是一个有意识且经过深思熟虑的选择。移除其他库所依赖的依赖项可能导致运行时错误或意外行为。如果您选择排除某些内容,请确保您的代码不依赖于它——最好有全面的测试覆盖作为支持,以便及早发现潜在问题。

此方法将自动从配置中声明的所有依赖项中排除指定的传递性依赖项

build.gradle.kts
configurations {
    "implementation" {
        exclude(group = "commons-collections", module = "commons-collections")
    }
}

dependencies {
    implementation("commons-beanutils:commons-beanutils:1.9.4")
    implementation("com.opencsv:opencsv:4.6")
}
build.gradle
configurations {
    implementation {
        exclude group: 'commons-collections', module: 'commons-collections'
    }
}

dependencies {
    implementation 'commons-beanutils:commons-beanutils:1.9.4'
    implementation 'com.opencsv:opencsv:4.6'
}

在此示例中,无论 commons-collections 依赖项是直接依赖项还是传递性依赖项,都将从 implementation 配置中排除它。

以下是 commons-beanutils 库的简化用法,其中我们只对 POJO(Plain Old Java Object)调用 PropertyUtils.setSimpleProperty()

src/main/java/Main.java
import org.apache.commons.beanutils.PropertyUtils;

public class Main {
    public static void main(String[] args) throws Exception {
        Object person = new Person();
        PropertyUtils.setSimpleProperty(person, "name", "Bart Simpson");
        PropertyUtils.setSimpleProperty(person, "age", 38);
    }
}

这种特定用法不需要 commons-collections,我们已经通过测试覆盖对其进行了验证。通过排除它,我们有效地表明我们只使用了 commons-beanutils 的一个子集。然而,这可能存在风险。如果我们的代码更具动态性或调用错误处理代码路径,我们可能会遇到运行时问题。

例如,如果尝试设置一个不存在的属性,库可能会因为缺少 commons-collections 中的类而抛出 NoClassDefFoundError,而不是一个明确的异常,因为该路径依赖于被排除的库。

Gradle 处理排除与 Maven 不同——它评估整个依赖图。只有当依赖项的所有路径都同意时,排除才生效。

例如,如果另一个依赖项(如 opencsv)也引入了 commons-beanutils(但没有排除),则传递性依赖项 commons-collections 仍将出现

build.gradle.kts
dependencies {
    implementation("commons-beanutils:commons-beanutils:1.9.4") {
        exclude(group = "commons-collections", module = "commons-collections")
    }
    implementation("com.opencsv:opencsv:4.6") // depends on 'commons-beanutils' without exclude and brings back 'commons-collections'
}
build.gradle
dependencies {
    implementation('commons-beanutils:commons-beanutils:1.9.4') {
        exclude group: 'commons-collections', module: 'commons-collections'
    }
    implementation 'com.opencsv:opencsv:4.6' // depends on 'commons-beanutils' without exclude and brings back 'commons-collections'
}

要完全排除 commons-collections,您还必须从 opencsv 中排除它

build.gradle.kts
dependencies {
    implementation("commons-beanutils:commons-beanutils:1.9.4") {
        exclude(group = "commons-collections", module = "commons-collections")
    }
    implementation("com.opencsv:opencsv:4.6") {
        exclude(group = "commons-collections", module = "commons-collections")
    }
}
build.gradle
dependencies {
    implementation('commons-beanutils:commons-beanutils:1.9.4') {
        exclude group: 'commons-collections', module: 'commons-collections'
    }
    implementation('com.opencsv:opencsv:4.6') {
        exclude group: 'commons-collections', module: 'commons-collections'
    }
}

使用排除应该是一个有意识且经过充分测试的决定。如果排除的依赖项在任何代码路径中被需要,您的构建可能会在运行时失败。

历史上,排除也曾被用作弥补其他构建系统限制的权宜之计,但现在有更好的替代方案

  • 使用依赖约束来更新或覆盖版本,而不是排除不需要的版本。

  • 使用组件元数据规则来移除不必要的依赖项——例如错误地添加到库元数据中的编译时依赖项。这会告诉 Gradle 完全忽略该依赖项(即元数据是错误的)。请注意,这些规则不会被发布,因此如果您正在创建一个供其他人使用的库,显式的 exclude 可能更合适。

  • 使用能力(Capabilities)来解决互斥的依赖项冲突。有些库不能共存,因为它们提供了相同的能力——例如 log4jlog4j-over-slf4j,或者 com.google.collectionsguava。如果 Gradle 不知道冲突,请使用组件元数据规则声明重叠的能力。如果您正在开发一个库,请避免强制执行特定的选择——让消费者决定使用哪个实现。

7. 强制失败的解析策略

可以使用以下方法强制版本冲突失败

  • failOnNonReproducibleResolution()

  • failOnDynamicVersions()

  • failOnChangingVersions()

  • failOnVersionConflict()

当发现相同依赖项的版本冲突时,这将导致构建失败

build.gradle.kts
configurations.all {
    resolutionStrategy {
        failOnVersionConflict()
    }
}
build.gradle
configurations.all {
    resolutionStrategy {
        failOnVersionConflict()
    }
}

8. 禁用传递性依赖项

默认情况下,Gradle 会解析给定模块的所有传递性依赖项。

但是,在某些情况下您可能需要禁用此行为,例如当您需要更好地控制依赖项或依赖项元数据不正确时。

您可以通过将 ModuleDependency.setTransitive(boolean) 设置为 false 来告诉 Gradle 为依赖项禁用传递性依赖项管理。

在以下示例中,已为 guava 依赖项禁用传递性依赖项解析。

build.gradle.kts
dependencies {
    implementation("com.google.guava:guava:23.0") {
        isTransitive = false
    }
}
build.gradle
dependencies {
    implementation('com.google.guava:guava:23.0') {
        transitive = false
    }
}

这确保只解析 guava 的主 Artifact,并且不包含其任何传递性依赖项。

禁用传递性依赖项解析可能要求您在构建脚本中声明必要的运行时依赖项,否则这些依赖项会被自动解析。不这样做可能导致运行时类路径问题。

如果您想在所有依赖项中全局禁用传递性解析,可以在配置级别设置此行为。

build.gradle.kts
configurations.all {
    isTransitive = false
}

dependencies {
    implementation("com.google.guava:guava:23.0")
}
build.gradle
configurations.all {
    transitive = false
}
dependencies {
    implementation 'com.google.guava:guava:23.0'
}

这将禁用项目中所有依赖项的传递性解析。请注意,这可能要求您手动声明运行时所需的任何传递性依赖项。

有关更多信息,请参见 Configuration.setTransitive(boolean)

9. 依赖项解析规则和其他条件

依赖项解析规则在解析每个依赖项时执行,提供了一个强大的 API,可以在解析最终确定之前修改依赖项的属性,例如 group、name 或 version。

这允许对依赖项解析进行高级控制,使您能够在解析过程中用一个模块替换另一个模块。

此功能对于实现高级依赖项管理模式特别有用。使用 依赖项解析规则,您可以将依赖项重定向到特定版本,甚至完全不同的模块,从而可以在整个项目中强制使用一致的版本或覆盖有问题的依赖项。

build.gradle.kts
configurations.all {
    resolutionStrategy {
        eachDependency {
            if (requested.group == "com.example" && requested.name == "old-library") {
                useTarget("com.example:new-library:1.0.0")
                because("Our license only allows use of version 1")
            }
        }
    }
}
build.gradle
configurations.all {
    resolutionStrategy {
        eachDependency {
            if (requested.group == "com.example" && requested.name == "old-library") {
                useTarget("com.example:new-library:1.0.0")
                because("Our license only allows use of version 1")
            }
        }
    }
}

在此示例中,如果请求对 com.example:old-library 的依赖项,它将在解析期间被替换为 com.example:new-library:1.0.0

有关更高级的用法和更多示例,请参阅 API 文档中的 ResolutionStrategy 类。

实现自定义版本控制方案

在某些企业环境中,Gradle 构建中的模块版本是在外部维护和审计的。依赖项解析规则 提供了一种实现此目标的有效方法。

  • 开发人员在构建脚本中声明依赖项时使用模块的 group 和 name,但指定一个占位符版本,如 default

  • 然后,依赖项解析规则将 default 版本解析为已批准的版本,该版本从批准的模块的企业目录中检索。

这种方法确保只使用经批准的版本,同时允许开发人员使用简化且一致的版本控制方案。

规则实现可以封装在一个企业插件中,从而易于在组织内的所有项目中应用。

build.gradle.kts
configurations.all {
    resolutionStrategy.eachDependency {
        if (requested.version == "default") {
            val version = findDefaultVersionInCatalog(requested.group, requested.name)
            useVersion(version.version)
            because(version.because)
        }
    }
}

data class DefaultVersion(val version: String, val because: String)

fun findDefaultVersionInCatalog(group: String, name: String): DefaultVersion {
    //some custom logic that resolves the default version into a specific version
    return DefaultVersion(version = "1.0", because = "tested by QA")
}
build.gradle
configurations.all {
    resolutionStrategy.eachDependency { DependencyResolveDetails details ->
        if (details.requested.version == 'default') {
            def version = findDefaultVersionInCatalog(details.requested.group, details.requested.name)
            details.useVersion version.version
            details.because version.because
        }
    }
}

def findDefaultVersionInCatalog(String group, String name) {
    //some custom logic that resolves the default version into a specific version
    [version: "1.0", because: 'tested by QA']
}

在这种设置中,每当开发人员将 default 指定为版本时,解析规则就会将其替换为企业目录中经批准的版本。

这种策略确保符合企业政策,同时为开发人员提供灵活性和易用性。将此逻辑封装在插件中也确保了跨多个项目的一致性。

替换不需要的依赖项版本

依赖项解析规则 提供了一种强大的机制,用于阻止特定版本的依赖项并将其替换为备用版本。

当已知特定版本存在问题时(例如,引入 bug 的版本或依赖于公共仓库中不可用的库),这尤其有用。通过定义解析规则,您可以自动将有问题的版本替换为稳定版本。

考虑这样一种情况:库的 1.2 版本已损坏,但 1.2.1 版本包含重要的修复,应始终使用它来代替。使用解析规则,您可以强制执行此替换:“任何时候请求版本 1.2 时,它都将被替换为 1.2.1。”与强制使用某个版本不同,此规则仅影响特定版本 1.2,而不影响其他版本。

build.gradle.kts
configurations.all {
    resolutionStrategy.eachDependency {
        if (requested.group == "org.software" && requested.name == "some-library" && requested.version == "1.2") {
            useVersion("1.2.1")
            because("fixes critical bug in 1.2")
        }
    }
}
build.gradle
configurations.all {
    resolutionStrategy.eachDependency { DependencyResolveDetails details ->
        if (details.requested.group == 'org.software' && details.requested.name == 'some-library' && details.requested.version == '1.2') {
            details.useVersion '1.2.1'
            details.because 'fixes critical bug in 1.2'
        }
    }
}

如果依赖项图中也存在版本 1.3,那么即使有了此规则,Gradle 的默认冲突解决策略仍将选择 1.3 作为最新版本。

与富版本约束的区别: 使用 富版本 约束,您可以直接拒绝某些版本,导致构建失败;或者,如果使用动态依赖项,则选择非拒绝版本。相比之下,此处所示的依赖项解析规则会操纵请求的版本,在发现被拒绝的版本时将其替换为已知良好的版本。这种方法是处理被拒绝版本的解决方案,而富版本约束是关于表达避免特定版本的意图。

延迟影响已解析的依赖项

插件可以通过有条件地添加依赖项或在用户未指定版本时设置首选版本来延迟影响依赖项。

以下是说明这些用例的两个示例。

此示例演示如何根据某个条件(延迟评估)将依赖项添加到配置中。

build.gradle.kts
configurations {
    implementation {
        dependencies.addLater(project.provider {
            val dependencyNotation = conditionalLogic()
            if (dependencyNotation != null) {
                project.dependencies.create(dependencyNotation)
            } else {
                null
            }
        })
    }
}
build.gradle
configurations {
    implementation {
        dependencies.addLater(project.provider {
            def dependencyNotation = conditionalLogic()
            if (dependencyNotation != null) {
                return project.dependencies.create(dependencyNotation)
            } else {
                return null
            }
        })
    }
}

在这种情况下,addLater 用于延迟依赖项的评估,使其仅在满足特定条件时才添加。

在此示例中,构建脚本设置了依赖项的首选版本,如果在未明确指定版本时将使用此版本。

build.gradle.kts
dependencies {
    implementation("org:foo")

    // Can indiscriminately be added by build logic
    constraints {
        implementation("org:foo:1.0") {
            version {
                // Applied to org:foo if no other version is specified
                prefer("1.0")
            }
        }
    }
}
build.gradle
dependencies {
    implementation("org:foo")

    // Can indiscriminately be added by build logic
    constraints {
        implementation("org:foo:1.0") {
            version {
                // Applied to org:foo if no other version is specified
                prefer("1.0")
            }
        }
    }
}

这确保 org:foo 使用版本 1.0,除非用户指定了其他版本。