本节介绍Gradle提供的直接影响依赖关系解析引擎行为的机制. 与本章介绍的其他概念(如依赖关系约束组件元数据规则)不同 ,这些概念都是解析的输入 ,以下机制使您可以编写直接注入解析引擎的规则. 因此,它们可以看作是蛮力解决方案,可能会隐藏未来的问题(例如,如果添加了新的依赖项). 因此,一般建议仅在其他手段不足的情况下使用以下机制. 如果要编写 ,则应始终首选依赖项约束,因为它们是为用户发布的.

Using dependency resolve rules

将为每个已解析的依赖项执行一个依赖项解析规则,并提供了一个强大的api,用于在解析依赖项之前处理请求的依赖项. 当前,该功能提供了更改请求的依赖项的组,名称和/或版本的功能,从而允许在解析过程中将依赖项替换为完全不同的模块.

依赖关系解析规则提供了一种非常强大的方法来控制依赖关系解析过程,并且可用于实现依赖关系管理中的各种高级模式. 下面概述了其中一些模式. 或更多信息和代码示例,请参阅API文档中的ResolutionStrategy类.

Implementing a custom versioning scheme

在某些公司环境中,可以在Gradle构建中声明的模块版本列表由外部维护和审核. 依赖关系解析规则提供了该模式的巧妙实现:

  • 在构建脚本中,开发人员使用模块组和名称声明依赖关系,但使用占位符版本,例如: default .

  • 通过依赖关系解析规则将default版本解析为特定版本,该规则在已批准模块的公司目录中查找该版本.

可以将规则实施整齐地封装在公司插件中,并在组织内的所有内部版本之间共享.

示例1.使用自定义版本控制方案
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']
}
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")
}

Blacklisting a particular version with a replacement

依赖关系解析规则提供了一种机制,可以将特定版本的依赖关系列入黑名单并提供替换版本. 如果某个依赖项版本已损坏并且不应使用,当依赖项解析规则导致该版本被已知的良好版本替换时,这将很有用. 损坏的模块的一个示例是声明对某个库的依赖关系,该依赖关系在任何公共存储库中都找不到,但是还有许多其他原因导致不需要特定的模块版本,而首选其他版本.

在下面的示例中,想象1.2.1版包含重要的修复程序,应该始终优先于1.2 . 提供的规则将强制执行此操作:每当遇到1.2版时,它将被1.2.1替换. 请注意,这与如上所述的强制版本不同,因为此模块的任何其他版本都不会受到影响. 这意味着"最新"的冲突解决策略将仍然选择1.3版, 1.3是该版本也被以过渡方式拉出.

例子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'
        }
    }
}
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")
        }
    }
}

使用富版本约束拒绝指令是有区别的:如果在图中找到了拒绝版本,则富版本将导致构建失败,或者在使用动态依赖项时选择一个非拒绝版本. 在这里,我们处理请求的版本 ,以便在找到被拒绝的版本时选择其他版本. 换句话说,这是针对拒绝版本的解决方案 ,而丰富版本限制允许声明意图 (您不应使用此版本).

Using module replacement rules

It is preferable to express module conflicts in terms of capabilities conflicts. However, if there’s no such rule declared or that you are working on versions of Gradle which do not support capabilities, Gradle provides tooling to work around those issues.

模块替换规则允许构建声明旧库已被新库替换. 新库取代旧库的一个很好的例子是google-collections > guava migration. 创建google-collection的团队决定将模块名称从com.google.collections:google-collections更改为com.google.guava:guava . 这是行业中的一种合法情况:团队需要能够更改其维护的产品名称,包括模块坐标. 重命名模块坐标会影响冲突解决.

为了解释对解决冲突的影响,让我们考虑google-collections > guava场景. 这两个库都可能被拉到同一个依赖图中. 例如, 我们的项目依赖于guava但是我们的一些依赖关系引入了旧版的google-collections . 这可能会导致运行时错误,例如在测试或应用程序执行期间. Gradle不会自动解决google-collections > guava冲突,因为它不被视为版本冲突 . 这是因为两个库的模块坐标完全不同,并且当groupmodule坐标相同时会激活冲突解决方案,但是在依赖关系图中有不同的版本可用(有关更多信息,请参阅冲突解决方案一节). 解决此问题的传统方法是:

  • 声明排除规则,以避免将google-collections拉入图表. 这可能是最流行的方法.

  • 避免引入旧式库的依赖项.

  • 如果新版本不再引入旧版库,请升级依赖版本.

  • 降级为google-collections . 不建议这样做,只是出于完整性考虑.

传统方法行之有效,但它们不够笼统. 例如,一个组织要解决所有项目中的google-collections > guava冲突解决问题. 可以声明某些模块已被其他模块替代. 这使组织可以将有关模块更换的信息包括在公司插件套件中,并全面解决企业中所有由Gradle支持的项目的问题.

示例3.声明模块更换
build.gradle
dependencies {
    modules {
        module("com.google.collections:google-collections") {
            replacedBy("com.google.guava:guava", "google-collections is now part of Guava")
        }
    }
}
build.gradle.kts
dependencies {
    modules {
        module("com.google.collections:google-collections") {
            replacedBy("com.google.guava:guava", "google-collections is now part of Guava")
        }
    }
}

For more examples and detailed API, refer to the DSL reference for ComponentMetadataHandler.

当我们声明用guava代替google-collections时会发生什么? Gradle可以使用此信息来解决冲突. Gradle会考虑比任何版本的google-collections更新/更好的每个版本的guava . 另外,Gradle将确保在类路径/已解析文件列表中仅存在guava jar. 请注意,如果依赖关系图中仅显示google-collections (例如,没有guava ),则Gradle不会急切地将其替换为guava . 模块更换是Gradle用于解决冲突的信息. 如果没有冲突(例如,图中只有google-collections或只有guava ),则不使用替换信息.

当前不可能声明给定的模块被一组模块替换. 但是,可以声明多个模块被单个模块替换.

Using dependency substitution rules

依赖关系替换规则的工作方式与依赖关系解决规则相似. 实际上,可以使用依赖关系替换规则来实现依赖关系解析规则的许多功能. 它们允许将项目和模块依赖项透明地替换为指定的替换项. 与依赖关系解析规则不同,依赖关系替换规则允许项目和模块依赖关系可以互换替换.

在配置中添加依赖项替换规则会更改解析该配置的时间. 在构造任务图时,无需解析首次使用的配置,而是解析配置. 如果在任务执行期间对配置进行了进一步修改,或者配置依赖于在执行另一任务期间发布的模块,则可能会产生意想不到的后果.

解释:

  • 可以将Configuration声明为任何任务的输入,并且在解决配置时,该配置可以包括项目依赖项.

  • 如果项目依赖项是任务的输入(通过配置),则用于构建项目工件的任务必须添加到任务依赖项中.

  • 为了确定作为任务输入的项目依赖关系,Gradle需要解析Configuration输入.

  • 由于Gradle任务图在任务执行开始后便是固定的,因此Gradle需要在执行任何任务之前执行此解决方案.

在没有依赖替换规则的情况下,Gradle知道外部模块依赖永远不会传递引用项目依赖. 通过简单的图形遍历,可以轻松确定配置的项目依赖项的完整集合. 使用此功能,Gradle不能再进行此假设,并且必须执行完全解析才能确定项目依赖项.

Substituting an external module dependency with a project dependency

一种替代依赖的用例是使用模块的本地开发版本代替从外部存储库下载的模块. 这对于测试依赖项的本地修补版本可能很有用.

可以在指定版本或不指定版本的情况下声明要替换的模块.

例子4.用一个项目代替一个模块
build.gradle
configurations.all {
    resolutionStrategy.dependencySubstitution {
        substitute module("org.utils:api") because "we work with the unreleased development version" with project(":api")
        substitute module("org.utils:util:2.5") with project(":util")
    }
}
build.gradle.kts
configurations.all {
    resolutionStrategy.dependencySubstitution {
        substitute(module("org.utils:api")).apply {
            with(project(":api"))
            because("we work with the unreleased development version")
        }
        substitute(module("org.utils:util:2.5")).with(project(":util"))
    }
}

请注意,替代项目必须包含在多项目构建中(通过settings.gradle ). 依赖关系替换规则负责将模块依赖关系替换为项目依赖关系,并连接所有任务依赖关系,但不将项目隐式包含在构建中.

Substituting a project dependency with a module replacement

使用替换规则的另一种方法是用多项目构建中的模块替换项目依赖项. 通过允许从存储库中下载而不是构建项目依赖项的子集,这对于加快大型多项目构建的开发速度可能非常有用.

必须使用指定的版本声明要用作替换模块的模块.

例子5.用一个模块代替一个项目
build.gradle
configurations.all {
    resolutionStrategy.dependencySubstitution {
        substitute project(":api") because "we use a stable version of org.utils:api" with module("org.utils:api:1.3")
    }
}
build.gradle.kts
configurations.all {
    resolutionStrategy.dependencySubstitution {
        substitute(project(":api")).apply {
            with(module("org.utils:api:1.3"))
            because("we use a stable version of org.utils:api")
        }
    }
}

当项目依赖关系已替换为模块依赖关系时,该项目仍将包含在整个多项目构建中. 但是,为了解决依赖的Configuration将不会执行构建替换后的依赖项的任务.

Conditionally substituting a dependency

依赖替换的一个常见用例是允许在多项目构建中更灵活地组装子项目. 这对于开发外部依赖的本地修补版本或在大型多项目构建中构建模块的子集很有用.

以下示例使用依赖项替换规则将所有模块依赖项替换为org.example组,但org.example是必须找到与依赖项名称匹配的本地项目.

例子6.有条件地替换一个依赖
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
                }
            }
        }
    }
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)
                    }
                }
            }
        }
    }

请注意,替代项目必须包含在多项目构建中(通过settings.gradle ). 依赖关系替换规则负责将模块依赖关系替换为项目依赖关系,但不会隐式将项目包含在构建中.

Disabling transitive resolution

默认情况下,Gradle解析依赖项元数据指定的所有传递依赖项. 有时,例如,如果元数据不正确或定义了较大的传递依赖关系图,则此行为可能是不希望的. 您可以通过将ModuleDependency.setTransitive(boolean)设置为false来告诉Gradle为依赖项禁用传递依赖项管理. 结果,只有主工件才可以解决声明的依赖项.

例子7.为声明的依赖项禁用传递依赖项解析
build.gradle
dependencies {
    implementation('com.google.guava:guava:23.0') {
        transitive = false
    }
}
build.gradle.kts
dependencies {
    implementation("com.google.guava:guava:23.0") {
        isTransitive = false
    }
}

禁用传递依赖项解析可能需要您在构建脚本中声明必要的运行时依赖项,否则将自动解决. 否则可能会导致运行时类路径问题.

项目可以决定完全禁用传递依赖项解析. 您或者不想依赖发布到使用的存储库的元数据,或者想要完全控制图形中的依赖项. 有关更多信息,请参见Configuration.setTransitive(boolean) .

例子8.在配置级别禁用传递依赖解析
build.gradle
configurations.all {
    transitive = false
}

dependencies {
    implementation 'com.google.guava:guava:23.0'
}
build.gradle.kts
configurations.all {
    isTransitive = false
}

dependencies {
    implementation("com.google.guava:guava:23.0")
}

Changing configuration dependencies prior to resolution

有时,插件可能要在解析配置之前先修改其相关性. withDependencies方法允许以编程方式添加,删除或修改依赖项.

Example 9. Modifying dependencies on a configuration
build.gradle
configurations {
    implementation {
        withDependencies { DependencySet dependencies ->
            ExternalModuleDependency dep = dependencies.find { it.name == 'to-modify' } as ExternalModuleDependency
            dep.version {
                strictly "1.2"
            }
        }
    }
}
build.gradle.kts
configurations {
    create("implementation") {
        withDependencies {
            val dep = this.find { it.name == "to-modify" } as ExternalModuleDependency
            dep.version {
                strictly("1.2")
            }
        }
    }
}

Setting default configuration dependencies

如果未为配置显式设置依赖项,则可以使用默认依赖项来配置配置. 此功能的主要用例是开发使用用户可能会覆盖的版本控制工具的插件. 通过指定默认依赖关系,只有在用户未指定要使用的特定版本时,插件才能使用该工具的默认版本.

示例10.指定配置的默认依赖项
build.gradle
configurations {
    pluginTool {
        defaultDependencies { dependencies ->
            dependencies.add(project.dependencies.create("org.gradle:my-util:1.0"))
        }
    }
}
build.gradle.kts
configurations {
    create("pluginTool") {
        defaultDependencies {
            add(project.dependencies.create("org.gradle:my-util:1.0"))
        }
    }
}

Excluding a dependency from a configuration completely

在依赖项声明中排除依赖项类似,您可以使用Configuration.exclude(java.util.Map)完全排除特定配置的传递性依赖项. 对于配置上声明的所有依赖关系,这将自动排除传递依赖关系.

例子11.排除特定配置的传递依赖
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'
}
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")
}

Matching dependencies to repositories

Gradle公开了一个API,以声明存储库可能包含或不包含的内容. 此功能提供了对哪个存储库提供哪些工件的精细控制,这可以是控制依赖项来源的一种方法.

请转至有关存储库内容过滤的部分,以了解有关此功能的更多信息.

Enabling Ivy dynamic resolve mode

Gradle的Ivy存储库实现支持等同于Ivy的动态解析模式. 通常,Gradle会对ivy.xml文件中包含的每个依赖项定义使用rev属性. 在动态解析模式下,对于给定的依赖项定义,Gradle将更喜欢revConstraint属性而不是rev属性. 如果revConstraint属性不存在,则使用rev属性.

要启用动态解析模式,您需要在存储库定义中设置适当的选项. 下面显示了两个示例. 请注意,动态解析模式仅适用于Gradle的Ivy存储库. 它不适用于Maven存储库或自定义Ivy DependencyResolver实现.

例子12.启用动态解析模式
build.gradle
// Can enable dynamic resolve mode when you define the repository
repositories {
    ivy {
        url "http://repo.mycompany.com/repo"
        resolve.dynamicMode = true
    }
}

// Can use a rule instead to enable (or disable) dynamic resolve mode for all repositories
repositories.withType(IvyArtifactRepository) {
    resolve.dynamicMode = true
}
build.gradle.kts
// Can enable dynamic resolve mode when you define the repository
repositories {
    ivy {
        url = uri("http://repo.mycompany.com/repo")
        resolve.isDynamicMode = true
    }
}

// Can use a rule instead to enable (or disable) dynamic resolve mode for all repositories
repositories.withType<IvyArtifactRepository> {
    resolve.isDynamicMode = true
}