Gradle 的依赖管理引擎是变体感知的。
在上一节中,Gradle 构建了一个解析的依赖关系图。在图解析期间,Gradle 根据构建的需求选择了每个依赖项的适当变体。

变体代表组件的不同使用方式,例如用于 Java 编译、原生链接或文档。每个变体可能拥有自己的构件和依赖关系。
Gradle 使用属性来确定选择哪个变体。这些属性为每个变体添加了上下文,描述了何时应该使用它们。
组件
让我们回顾上一节中对组件的描述。一个组件
-
包含变体
-
其中包含一个或多个构件
-
其中包含零个或多个依赖关系
-
由元数据描述
-

在上面的示例中,org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1
是组件。它有一个模块 org.jetbrains.kotlinx:kotlinx-serialization-json
和一个版本 1.5.1
。

变体和属性
变体代表组件的不同版本或方面,例如 api
与 implementation
或 jar
与 classes
。
在上面的示例中,org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1
有四个变体:jvm
、android
、js
和 native
。
属性是类型安全的键值对,供消费者和生产者在变体选择期间使用:属性 : 值
。
在上面的示例中,org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1
的变体有两个重要的属性
变体 | 属性 1 | 属性 2 |
---|---|---|
JVM |
|
|
Android |
|
|
Javascript |
|
|
原生 |
|
|
请注意,org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1
的元数据展示了更多描述其变体的属性,例如 org.gradle.libraryelements
或 org.gradle.category
。然而,Gradle 在依赖解析期间可能会使用其中的任意数量,因为有些属性比其他属性更相关。
"variants": [
...
{
"name": "jsLegacyRuntimeElements-published",
"attributes": {
"org.gradle.category": "library",
"org.gradle.usage": "kotlin-runtime",
"org.jetbrains.kotlin.js.compiler": "legacy",
"org.jetbrains.kotlin.platform.type": "js"
}
},
{
"name": "jvmRuntimeElements-published",
"attributes": {
"org.gradle.category": "library",
"org.gradle.libraryelements": "jar",
"org.gradle.usage": "java-runtime",
"org.jetbrains.kotlin.platform.type": "jvm"
}
},
...
]
Gradle 使用两种类型的属性来将可用变体与构建所需的变体进行匹配:
-
消费者属性:定义可解析配置所请求的变体所需的特征。
-
生产者属性:每个变体都有一组描述其用途的属性。
变体属性匹配
组件可以定义多少个变体没有任何限制。
典型的组件将至少包含一个实现变体,但也可能提供额外的变体,例如测试装置、文档或源代码。此外,组件可以根据消费者为相同的用途提供不同的变体。例如,在编译期间,组件可能为 Linux、Windows 和 macOS 提供不同的头文件。
Gradle 通过使用属性匹配算法匹配消费者指定的属性与生产者定义的属性来执行变体感知选择。

变体名称主要用于调试和错误消息。它在变体匹配中不起作用;匹配过程中只使用变体的属性。 |
属性匹配算法
Gradle 的依赖解析引擎遵循以下属性匹配算法来确定最佳候选(或在找不到匹配项时失败):

步骤 1:寻找兼容候选
将请求的属性与候选属性进行匹配。
-
对于每个候选
-
对于每个候选属性
-
如果候选属性缺失,则将候选标记为缺失。
-
否则,如果候选属性与请求的属性完全匹配,则候选保持兼容。
-
否则,如果候选属性通过兼容性测试,则候选保持兼容。
-
否则,排除该候选。
-
-
步骤 2:最多匹配属性
过滤后,Gradle 检查一个候选是否是所有其他候选的严格超集。
-
如果只剩下一个候选,则该候选获胜。
-
如果剩下多个候选,检查其中一个
-
匹配的属性多于其他任何候选。
-
与另一个候选相比没有缺失属性。
-
-
如果存在这样的严格超集,则该候选获胜。
步骤 3:使用请求属性进行消歧
如果剩下多个候选,Gradle 尝试消歧
如果仍然剩下多个候选,Gradle 处理无序属性(不在优先顺序列表中的属性)
-
对于每个请求的属性,没有优先顺序
-
如果存在消歧规则,则应用该规则。
-
如果所有剩余候选的某个请求属性具有相同的值,则忽略该属性。
-
-
如果只剩下一个候选,则该候选获胜。
-
如果没有候选剩下,Gradle 回退到最兼容的候选(步骤 2)。
步骤 4:使用额外属性进行消歧
如果仍然剩下多个候选,Gradle 考虑额外属性
-
这些属性仅存在于某些候选中。
-
缺乏额外属性的候选优先。
-
考虑属性优先顺序。
首先,Gradle 处理优先级额外属性
-
对于每个额外属性,按优先顺序
-
如果某些候选具有该属性而其他候选没有
-
缺乏该额外属性的候选优先。
-
具有额外属性的候选被排除。
-
-
-
如果只剩下一个候选,则该候选获胜。
-
如果没有候选剩下,Gradle 回退到原始兼容候选(步骤 1)。
然后,Gradle 处理无序额外属性
-
对于每个剩余的额外属性,没有定义优先顺序
-
如果该属性仅存在于部分候选中
-
没有该属性的候选优先。
-
具有额外属性的候选被排除。
-
-
-
如果只剩下一个候选,则该候选获胜。
-
如果没有候选剩下,Gradle 回退到原始兼容候选(步骤 1)。
步骤 5:使用额外属性存在性进行消歧
如果仍然剩下候选,Gradle 根据请求属性排除候选
-
对于每个额外属性
-
如果所有候选都提供相同的值,则忽略它。
-
否则,移除提供该值的候选。
-
-
如果只剩下一个候选,则该候选获胜。
步骤 6:失败条件
除非另有说明,如果在任何时候没有候选剩下,则解析失败。
此外,Gradle 输出步骤 1 中所有兼容候选的列表,以帮助调试属性匹配失败。
插件和生态系统可以通过实现兼容性规则、消歧规则和定义属性的优先顺序来影响选择算法。优先级较高的属性将按顺序用于排除候选。
例如,在 Java 生态系统中,org.gradle.usage
属性的优先级高于 org.gradle.libraryelements
。这意味着如果两个候选在 org.gradle.usage
和 org.gradle.libraryelements
都具有兼容值,Gradle 将选择通过 org.gradle.usage
消歧规则的候选。
变体感知解析过程有两个例外:
|
一个简单示例
让我们来看一个示例,其中消费者正在尝试使用库进行编译。
首先,消费者详细说明它将如何使用依赖解析的结果。这通过在消费者的可解析配置上设置属性来实现。
在这种情况下,消费者希望解析匹配 org.gradle.usage=java-api
的变体。
接下来,生产者暴露了其组件的不同变体:
-
API 变体(命名为
apiElements
),具有属性org.gradle.usage=java-api
-
运行时变体(命名为
runtimeElements
),具有属性org.gradle.usage=java-runtime
最后,Gradle 评估变体并选择正确的变体:
-
消费者请求具有属性
org.gradle.usage=java-api
的变体。 -
生产者的
apiElements
变体匹配此请求。 -
生产者的
runtimeElements
变体不匹配。
消费者请求属性 | 生产者可用属性 | 生产者变体 | 匹配? |
---|---|---|---|
|
|
|
✅ 是 |
|
|
|
❌ 否 |
因此,Gradle 选择 apiElements
变体并将其构件和依赖关系提供给消费者。
一个复杂示例
在实际场景中,消费者和生产者通常都使用多个属性。
例如,Gradle 中的 Java 库项目将涉及多个属性:
属性 | 描述 |
---|---|
|
描述变体的用途。 |
|
描述变体如何处理依赖关系(例如,shadow jar、fat jar、普通 jar)。 |
|
描述变体的打包方式(例如,classes 或 jar)。 |
|
描述变体目标的 最低版本 Java。 |
|
描述变体目标的 JVM 类型。 |
让我们考虑一个场景,其中消费者希望在 Java 8 上使用库运行测试,而生产者支持两个版本:Java 8 和 Java 11。
步骤 1:消费者指定需求。
消费者希望解析一个变体,该变体
-
可在运行时使用(
org.gradle.usage=java-runtime
)。 -
可在 至少 Java 8 上运行(
org.gradle.jvm.version=8
)。
步骤 2:生产者暴露多个变体。
生产者为 API 和运行时使用提供了 Java 8 和 Java 11 的变体:
变体名称 | 属性 |
---|---|
|
|
|
|
|
|
|
|
步骤 3:Gradle 匹配属性。
Gradle 将消费者请求的属性与生产者的变体进行比较:
-
消费者请求具有
org.gradle.usage=java-runtime
和org.gradle.jvm.version=8
属性的变体。 -
runtime8Elements
和runtime11Elements
都匹配org.gradle.usage=java-runtime
属性。 -
API 变体(
apiJava8Elements
和apiJava11Elements
)因不匹配org.gradle.usage=java-runtime
而被丢弃。 -
选择
runtime8Elements
变体,因为它与 Java 8 兼容。 -
runtime11Elements
变体不兼容,因为它需要 Java 11。
消费者请求属性 | 生产者可用属性 | 生产者变体 | 匹配? |
---|---|---|---|
|
|
|
✅ 已选择 |
|
|
|
❌ 不兼容 |
|
|
|
❌ 已丢弃 |
|
|
|
❌ 已丢弃 |
Gradle 选择 runtime8Elements
并将其构件和依赖关系提供给消费者。
但是,如果消费者设置 org.gradle.jvm.version=7
会发生什么?
在这种情况下,依赖解析将失败,并出现错误解释没有合适的变体。Gradle 知道消费者需要与 Java 7 兼容的库,但生产者的最低版本是 8。
如果消费者请求 org.gradle.jvm.version=15
,Gradle 可以选择 Java 8 或 Java 11 变体。然后,Gradle 会选择兼容的最高版本——Java 11。
可视化变体信息
Gradle 提供内置任务来可视化变体选择过程,并显示涉及的生产者和消费者属性。
Outgoing variants report
报告任务 outgoingVariants
显示可供项目消费者选择的变体列表。它显示每个变体的能力、属性和构件。
此任务类似于 dependencyInsight
报告任务。
默认情况下,outgoingVariants
会打印所有变体的信息。它提供了可选参数 --variant <variantName>
来选择单个变体进行显示。它还接受 --all
标志以包含关于传统和已弃用配置的信息,或接受 --no-all
以排除此信息。
以下是在新生成的 java-library
项目上运行 outgoingVariants
任务的输出:
> Task :outgoingVariants -------------------------------------------------- Variant apiElements -------------------------------------------------- API elements for the 'main' feature. Capabilities - new-java-library:lib:unspecified (default capability) Attributes - org.gradle.category = library - org.gradle.dependency.bundling = external - org.gradle.jvm.version = 11 - org.gradle.libraryelements = jar - org.gradle.usage = java-api Artifacts - build/libs/lib.jar (artifactType = jar) Secondary Variants (*) -------------------------------------------------- Secondary Variant classes -------------------------------------------------- Description = Directories containing compiled class files for main. Attributes - org.gradle.category = library - org.gradle.dependency.bundling = external - org.gradle.jvm.version = 11 - org.gradle.libraryelements = classes - org.gradle.usage = java-api Artifacts - build/classes/java/main (artifactType = java-classes-directory) -------------------------------------------------- Variant mainSourceElements (i) -------------------------------------------------- Description = List of source directories contained in the Main SourceSet. Capabilities - new-java-library:lib:unspecified (default capability) Attributes - org.gradle.category = verification - org.gradle.dependency.bundling = external - org.gradle.verificationtype = main-sources Artifacts - src/main/java (artifactType = directory) - src/main/resources (artifactType = directory) -------------------------------------------------- Variant runtimeElements -------------------------------------------------- Runtime elements for the 'main' feature. Capabilities - new-java-library:lib:unspecified (default capability) Attributes - org.gradle.category = library - org.gradle.dependency.bundling = external - org.gradle.jvm.version = 11 - org.gradle.libraryelements = jar - org.gradle.usage = java-runtime Artifacts - build/libs/lib.jar (artifactType = jar) Secondary Variants (*) -------------------------------------------------- Secondary Variant classes -------------------------------------------------- Description = Directories containing compiled class files for main. Attributes - org.gradle.category = library - org.gradle.dependency.bundling = external - org.gradle.jvm.version = 11 - org.gradle.libraryelements = classes - org.gradle.usage = java-runtime Artifacts - build/classes/java/main (artifactType = java-classes-directory) -------------------------------------------------- Secondary Variant resources -------------------------------------------------- Description = Directories containing the project's assembled resource files for use at runtime. Attributes - org.gradle.category = library - org.gradle.dependency.bundling = external - org.gradle.jvm.version = 11 - org.gradle.libraryelements = resources - org.gradle.usage = java-runtime Artifacts - build/resources/main (artifactType = java-resources-directory)
从中可以看到 java 库暴露的两个主要变体:apiElements
和 runtimeElements
。请注意,主要区别在于 org.gradle.usage
属性,其值分别为 java-api
和 java-runtime
。正如它们所指示的,这区分了需要放在消费者的编译类路径上的内容,以及需要放在运行时类路径上的内容。
它还显示次要变体,这些变体仅限于 Gradle 项目且不发布。例如,apiElements
中的次要变体 classes
使得 Gradle 在针对 java-library
项目进行编译时可以跳过 JAR 创建。
关于无效可消费配置的信息
一个项目不能有多个具有相同属性和能力的配置。在这种情况下,项目将构建失败。
为了能够可视化此类问题,outgoing 变体报告以宽松的方式处理这些错误。这允许报告显示关于该问题的信息。
Resolvable configurations report
Gradle 还提供了互补的报告任务 resolvableConfigurations
,该任务显示项目的可解析配置,即可以添加依赖并解析的配置。
默认情况下,resolvableConfigurations
打印所有纯粹可解析配置的信息。这些配置被标记为可解析,但未标记为可消费。尽管一些可解析配置也标记为可消费,但这些是传统配置,不应在构建脚本中添加依赖项。
此报告提供:
-
一个可选参数
-
--configuration <configurationName>
→ 选择要显示的单个配置。
-
-
包含或排除传统和已弃用配置的标志
-
--all
→ 包含关于传统和已弃用配置的信息。 -
--no-all
→ 排除此信息。
-
-
控制扩展配置部分中传递扩展的标志
-
--recursive
→ 列出传递而不是直接扩展的配置。 -
--no-recursive
→ 排除此信息。
-
以下是在新生成的 java-library
项目上运行 resolvableConfigurations
任务的输出:
> Task :resolvableConfigurations -------------------------------------------------- Configuration compileClasspath -------------------------------------------------- Description = Compile classpath for source set 'main'. Attributes - org.gradle.category = library - org.gradle.dependency.bundling = external - org.gradle.jvm.environment = standard-jvm - org.gradle.jvm.version = 11 - org.gradle.libraryelements = classes - org.gradle.usage = java-api Extended Configurations - compileOnly - implementation -------------------------------------------------- Configuration runtimeClasspath -------------------------------------------------- Description = Runtime classpath of source set 'main'. Attributes - org.gradle.category = library - org.gradle.dependency.bundling = external - org.gradle.jvm.environment = standard-jvm - org.gradle.jvm.version = 11 - org.gradle.libraryelements = jar - org.gradle.usage = java-runtime Extended Configurations - implementation - runtimeOnly [...]
从中可以看到用于解析依赖关系的两个主要配置,compileClasspath
和 runtimeClasspath
,以及它们对应的测试配置(已截断)。
现在我们理解了变体选择和属性匹配,让我们继续依赖解析的构件解析阶段。这个阶段也是变体感知的。
下一步: 了解构件解析 >>