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 出现在类路径中。但是,如果 google-collections 是唯一存在的模块,则除非发生冲突,否则它不会自动替换。

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

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

3. 依赖替换

依赖替换规则允许将项目和模块依赖替换为指定的替代方案,使其可互换。虽然与依赖解析规则相似,但它们通过允许在项目和模块依赖之间进行替换来提供更大的灵活性。

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

解释

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

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

  • 为了确定作为任务输入的项目依赖,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")
            }
        }
    }
}
  • 替换基于请求依赖的属性(如组和版本)。

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

  • 它使用 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 会自动匹配请求的属性。换句话说,如果您包含另一个构建,则您正在用包含的构建中的等效变体替换被替换模块的所有变体,这是隐式的。

用具有能力的依赖替换依赖

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

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

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

用分类器或构件替换依赖

您可以将带有分类器的依赖替换为不带分类器的依赖,反之亦然。分类器通常用于表示同一构件的不同版本,例如特定于平台的构建或具有不同 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 的依赖,而该项目本身依赖于不同版本的 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"
}
  • 消费者依赖于不带分类器的 quasar-core:0.8.0

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

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

为了解决此冲突,您可以指示 Gradle 在解析 quasar-core 依赖时忽略分类器

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 的依赖替换为不带分类器的依赖。

如果您需要用特定的分类器或构件进行替换,您可以在替换规则中指定分类器或构件详细信息。

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

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(普通 Java 对象)调用 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 可能更合适。

  • 使用能力来解决互斥的依赖冲突。有些库不能共存,因为它们提供相同的能力——例如 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 的主构件,并且其任何传递性依赖都不会被包含。

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

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

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 来修改依赖的属性(如组、名称或版本),然后最终确定解析。

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

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

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 构建中的模块版本是在外部维护和审计的。依赖解析规则提供了一种有效的方法来实现这一点

  • 开发人员在构建脚本中使用模块的组和名称声明依赖,但指定一个占位符版本,如 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 指定为版本时,解析规则都会将其替换为企业目录中的批准版本。

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

替换不需要的依赖版本

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

这在已知特定版本有问题时特别有用——例如引入错误或依赖公共存储库中不可用的库的版本。通过定义解析规则,您可以自动将问题版本替换为稳定版本。

考虑一个场景,其中库的 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,除非用户指定了另一个版本。