变体与属性
变体 (Variants) 代表组件的不同版本或方面,例如 api
与 implementation
。属性 (Attributes) 根据消费者的需求定义选择哪个变体。
例如,一个库可能有 api
和 implementation
变体。此处,消费者需要一个外部的 implementation
变体
configurations {
implementation {
attributes {
attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.EXTERNAL))
}
}
}
例如,一个构建可能有 debug
和 release
变体。这会根据属性选择 debug
变体。
configurations {
compileClasspath {
attributes {
attribute(TargetConfiguration.TARGET_ATTRIBUTE, objects.named("debug"))
}
}
}
属性 通过比较请求的属性与可用的属性,帮助 Gradle 匹配正确的变体
attribute(TargetConfiguration.TARGET_ATTRIBUTE, objects.named("debug"))
这会将 TargetConfiguration.TARGET_ATTRIBUTE
设置为 "debug"
,意味着 Gradle 将尝试解析具有 "debug" 变体的依赖项,而不是其他可用的变体(例如 "release")。
Gradle 定义的标准属性
作为 Gradle 用户,属性通常作为实现细节而被隐藏。但理解 Gradle 及其核心插件定义的标准属性可能会很有用。
作为插件作者,这些属性及其定义方式可以作为你在生态系统插件中构建自己的属性集的基础。
生态系统无关的标准属性
属性名称 | 描述 | 值 | 兼容性和消歧规则 |
---|---|---|---|
指示变体的主要用途 |
从 Usage 中定义的常量构建的 |
遵循生态系统语义(例如, |
|
指示此软件组件的类别 |
从 Category 中定义的常量构建的 |
遵循生态系统语义(例如,在 JVM 上 |
|
指示 |
从 LibraryElements 中定义的常量构建的 |
遵循生态系统语义(例如,在 JVM 世界中, |
|
指示 |
从 DocsType 中定义的常量构建的 |
无默认值,不兼容 |
|
指示如何访问变体的依赖项。 |
从 Bundling 中定义的常量构建的 |
遵循生态系统语义(例如,在 JVM 世界中, |
|
指示生成此输出的是哪种验证任务。 |
从 VerificationType 中定义的常量构建的 |
无默认值,不兼容 |
当变体上存在具有孵化值 org.gradle.category=verification
的 Category
属性时,该变体被视为仅用于验证时段的变体。
这些变体仅用于包含运行验证任务的结果,例如测试结果或代码覆盖率报告。它们不可发布,如果添加到已发布的组件中会产生错误。
属性名称 | 描述 | 值 | 兼容性和消歧规则 |
---|---|---|---|
|
组件级别属性,派生 |
基于状态方案,默认方案基于源仓库。 |
基于正在使用的方案 |
JVM 生态系统特定属性
除了上面定义的生态系统无关属性外,JVM 生态系统还添加了以下属性
属性名称 | 描述 | 值 | 兼容性和消歧规则 |
---|---|---|---|
指示 JVM 版本兼容性。 |
对于 Java 1.4 及以前的版本,使用 |
默认为 Gradle 使用的 JVM 版本,较低版本与较高版本兼容,优先选择最高兼容版本。 |
|
指示变体已针对特定的 JVM 环境进行了优化。 |
常用值为 |
如果有多个可用变体,此属性用于优先选择一个变体,但通常所有值都是兼容的。默认值为 |
|
指示生成此输出的 TestSuite 的名称。 |
值为 Suite 的名称。 |
无默认值,不兼容 |
JVM 生态系统还包含许多针对不同属性的兼容性和消歧规则。想要了解更多信息的读者可以查看 org.gradle.api.internal.artifacts.JavaEcosystemSupport
的代码。
原生生态系统特定属性
除了上面定义的生态系统无关属性外,原生生态系统还添加了以下属性
属性名称 | 描述 | 值 | 兼容性和消歧规则 |
---|---|---|---|
指示二进制文件是否包含调试符号构建 |
布尔值 |
不适用 (N/A) |
|
指示二进制文件是否包含优化标志构建 |
布尔值 |
不适用 (N/A) |
|
指示二进制文件的目标架构 |
从 MachineArchitecture 中定义的常量构建的 |
无 |
|
指示二进制文件的目标操作系统 |
从 OperatingSystemFamily 中定义的常量构建的 |
无 |
Gradle 插件生态系统特定属性
对于 Gradle 插件开发,Gradle 7.0 开始支持以下属性。Gradle 插件变体可以通过此属性指定与 Gradle API 版本的兼容性。
属性名称 | 描述 | 值 | 兼容性和消歧规则 |
---|---|---|---|
指示 Gradle API 版本兼容性。 |
有效的 Gradle 版本字符串。 |
默认为当前运行的 Gradle,较低版本与较高版本兼容,优先选择最高兼容版本。 |
使用标准属性
对于此示例,假设你正在创建一个针对不同 JVM 版本具有不同变体的库。
plugins {
id("java-library")
}
configurations {
named("apiElements") {
attributes {
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 17)
}
}
}
plugins {
id 'java-library'
}
configurations {
apiElements {
attributes {
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 17)
}
}
}
在消费项目(使用该库的项目)中,你可以在声明依赖项时指定 JVM 版本属性。
plugins {
id("application")
}
dependencies {
implementation(project(":lib")) {
attributes {
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 17)
}
}
}
plugins {
id 'application'
}
dependencies {
implementation(project(':lib')) {
attributes {
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 17)
}
}
}
通过定义和使用 JVM 版本属性,你可以确保你的库及其消费者与指定的 JVM 版本兼容。本质上,这确保了 Gradle 解析到与所需 JVM 版本匹配的变体。
查看和调试属性
dependencyInsight
任务对于检查特定依赖项及其属性(包括它们如何解析)非常有用
$ ./gradlew dependencyInsight --configuration compileClasspath --dependency com.example:your-library
> Task :dependencyInsight
com.example:your-library:1.0 (compileClasspath)
variant "apiElements" [
org.gradle.api.attributes.Attribute: org.gradle.api.attributes.Usage = [java-api]
org.gradle.api.attributes.Attribute: org.gradle.api.attributes.Usage = [java-runtime]
org.gradle.api.attributes.Attribute: org.gradle.api.attributes.JavaLanguageVersion = [1.8]
]
variant "runtimeElements" [
org.gradle.api.attributes.Attribute: org.gradle.api.attributes.Usage = [java-runtime]
org.gradle.api.attributes.Attribute: org.gradle.api.attributes.JavaLanguageVersion = [1.8]
]
Selection reasons:
- By constraint from configuration ':compileClasspath'
- Declared in build.gradle.kts
Resolved to:
com.example:your-library:1.0 (runtime)
Additional Information:
- Dependency declared in the 'implementation' configuration
- No matching variants found for the requested attributes in the 'compileClasspath' configuration
声明自定义属性
扩展 Gradle 时使用自定义属性,务必考虑它们的长期影响,特别是如果你计划发布库。自定义属性允许你在插件中集成变体感知的依赖管理,但使用这些属性的库也必须确保消费者能够正确解释它们。这通常通过应用相应的插件来完成,该插件定义了兼容性和消歧规则。
如果你的插件是公开可用的且库已发布到公共仓库,引入新属性将成为一项重大责任。发布的属性必须在插件的未来版本中保持支持或具有兼容层,以确保向后兼容性。
以下是在 Gradle 插件中声明和使用自定义属性的示例
// Define a custom attribute
val myAttribute = Attribute.of("com.example.my-attribute", String::class.java)
configurations {
create("myConfig") {
// Set custom attribute
attributes {
attribute(myAttribute, "special-value")
}
}
}
dependencies {
// Apply the custom attribute to a dependency
add("myConfig","com.google.guava:guava:31.1-jre") {
attributes {
attribute(myAttribute, "special-value")
}
}
}
// Define a custom attribute
def myAttribute = Attribute.of("com.example.my-attribute", String)
// Create a custom configuration
configurations {
create("myConfig") {
// Set custom attribute
attributes {
attribute(myAttribute, "special-value")
}
}
}
dependencies {
// Apply the custom attribute to a dependency
add("myConfig", "com.google.guava:guava:31.1-jre") {
attributes {
attribute(myAttribute, "special-value")
}
}
}
在此示例中: - 定义了一个自定义属性 my-attribute
。 - 该属性设置在自定义配置 (myConfig
) 上。 - 添加依赖项时,应用自定义属性以匹配配置。
如果发布包含此属性的库,请确保消费者应用了理解和处理 my-attribute
的插件。
在构建脚本或插件中创建属性
属性是有类型的。可以通过 Attribute<T>.of
方法创建属性
// An attribute of type `String`
val myAttribute = Attribute.of("my.attribute.name", String::class.java)
// An attribute of type `Usage`
val myUsage = Attribute.of("my.usage.attribute", Usage::class.java)
// An attribute of type `String`
def myAttribute = Attribute.of("my.attribute.name", String)
// An attribute of type `Usage`
def myUsage = Attribute.of("my.usage.attribute", Usage)
属性类型支持大多数 Java 原生类,例如 String
和 Integer
。或任何扩展 org.gradle.api.Named
的类型。
属性应始终在 dependencies
handler 上的属性模式中声明
dependencies.attributesSchema {
// registers this attribute to the attributes schema
attribute(myAttribute)
attribute(myUsage)
}
dependencies.attributesSchema {
// registers this attribute to the attributes schema
attribute(myAttribute)
attribute(myUsage)
}
必须向模式注册属性,才能使用兼容性和消歧规则,这些规则可以在属性匹配期间解决多个可选变体之间的歧义。
每个配置都有一个属性容器。可以配置属性来设置值
configurations {
create("myConfiguration") {
attributes {
attribute(myAttribute, "my-value")
}
}
}
configurations {
myConfiguration {
attributes {
attribute(myAttribute, 'my-value')
}
}
}
对于类型扩展 Named
的属性,属性的值必须通过对象工厂创建
configurations {
"myConfiguration" {
attributes {
attribute(myUsage, project.objects.named(Usage::class.java, "my-value"))
}
}
}
configurations {
myConfiguration {
attributes {
attribute(myUsage, project.objects.named(Usage, 'my-value'))
}
}
}
处理属性匹配
在 Gradle 中,属性匹配和属性消歧是解析具有不同属性的依赖项的关键机制。
属性匹配允许 Gradle 根据预定义规则选择兼容的依赖项变体,即使没有完全匹配的变体。另一方面,属性消歧有助于 Gradle 在存在多个兼容选项时选择最合适的变体。
属性兼容性规则
属性让引擎能够选择兼容的变体。有些情况下,生产者可能没有消费者完全请求的内容,但有一个可用的变体。
此示例定义并注册了一个自定义兼容性规则,以确保根据依赖项与特定 Java 版本的兼容性来选择依赖项
// Define the compatibility rule class
class TargetJvmVersionCompatibilityRule : AttributeCompatibilityRule<Int> {
// Implement the execute method which will check compatibility
override fun execute(details: CompatibilityCheckDetails<Int>) {
// Switch case to check the consumer value for supported Java versions
when (details.consumerValue) {
8, 11 -> details.compatible() // Compatible with Java 8 and 11
else -> details.incompatible()
}
}
}
// Register the compatibility rule within the dependencies block
dependencies {
attributesSchema {
// Add the compatibility rule for the TargetJvmVersion attribute
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE) {
// Add the defined compatibility rule to this attribute
compatibilityRules.add(TargetJvmVersionCompatibilityRule::class.java)
}
}
}
// Define the compatibility rule
class TargetJvmVersionCompatibilityRule implements AttributeCompatibilityRule<Integer> {
@Override
void execute(CompatibilityCheckDetails<Integer> details) {
switch (details.consumerValue) {
case 8:
case 11:
details.compatible() // Compatible with Java 8 and 11
break
default:
details.incompatible("Unsupported Java version: ${details.consumerValue}")
}
}
}
// Register a compatibility rule
dependencies {
attributesSchema {
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE) {
compatibilityRules.add(TargetJvmVersionCompatibilityRule)
}
}
}
Gradle 提供了可以为每个属性定义的属性兼容性规则。兼容性规则的作用是解释哪些属性值基于消费者请求的内容是兼容的。
属性兼容性规则必须通过属性模式注册。
属性消歧规则
当依赖项的多个变体与消费者请求的属性兼容时,Gradle 需要决定选择哪个变体。在兼容选项中确定“最佳”候选者的过程称为属性消歧。
在 Gradle 中,不同的变体可能满足消费者的请求,但并非所有变体都相同。例如,你可能有多个与消费者请求的 Java 版本兼容的库版本。消歧有助于 Gradle 根据附加条件选择最合适的版本。
你可以定义消歧规则来指导 Gradle 在找到多个候选变体时选择最合适的变体。这可以通过实现 AttributeDisambiguationRule 并在 dependencies
块内的 attributes schema 中注册它来完成
dependencies {
attributesSchema {
attribute(Usage.USAGE_ATTRIBUTE) { (1)
disambiguationRules.add(CustomDisambiguationRule::class.java) (2)
}
}
}
abstract class CustomDisambiguationRule @Inject constructor(
private val objects: ObjectFactory
) : AttributeDisambiguationRule<Usage> {
override fun execute(details: MultipleCandidatesDetails<Usage>) {
// Prefer the JAVA_API usage over others (e.g., JAVA_RUNTIME) when multiple candidates exist
details.closestMatch(objects.named(Usage::class.java, Usage.JAVA_API)) (3)
}
}
dependencies {
attributesSchema {
attribute(Usage.USAGE_ATTRIBUTE) { (1)
disambiguationRules.add(CustomDisambiguationRule) (2)
}
}
}
abstract class CustomDisambiguationRule implements AttributeDisambiguationRule<Usage> {
private final ObjectFactory objects
@Inject
CustomDisambiguationRule(ObjectFactory objects) {
this.objects = objects
}
@Override
void execute(MultipleCandidatesDetails<Usage> details) {
// Prefer the JAVA_API usage over others (e.g., JAVA_RUNTIME) when multiple candidates exist
details.closestMatch(objects.named(Usage, Usage.JAVA_API)) (3)
}
}
-
属性选择:指定要消歧的属性(例如,
Usage.USAGE_ATTRIBUTE
,一个内置的 Gradle 属性)。 -
规则注册:在
dependencies
的attributesSchema
块中使用disambiguationRules.add()
注册自定义消歧规则。 -
自定义逻辑:在单独的类中实现
AttributeDisambiguationRule
接口。execute
方法定义了 Gradle 如何在候选者中进行选择。在此示例中,它优先选择JAVA_API
用途。
属性消歧规则必须通过属性匹配策略注册,该策略通过DependencyHandler 中的属性模式访问。这确保了规则全局应用于依赖解析。
从 Maven/Ivy 映射到 Gradle 变体
Maven 和 Ivy 都没有变体的概念,变体仅受 Gradle 模块元数据原生支持。Gradle 仍然可以通过使用不同的变体派生策略来处理 Maven 和 Ivy。
Gradle 模块元数据是发布在 Maven、Ivy 和其他类型仓库上的模块的元数据格式。它类似于 pom.xml
或 ivy.xml
元数据文件,但这种格式包含有关变体的详细信息。
有关更多信息,请参阅 {metadata-file-spec}[Gradle 模块元数据规范]。
Maven POM 元数据到变体的映射
发布到 Maven 仓库的模块在被 Gradle 解析时会自动转换为变体感知的模块。
Gradle 无法知道发布了哪种组件
-
代表 Gradle 平台的 BOM
-
用作超级 POM 的 BOM
-
既是平台又是库的 POM
Gradle 中 Java 项目使用的默认策略是派生 8 种不同的变体
-
两个“库”变体(属性
org.gradle.category
=library
) -
代表组件源 jar 的“sources”变体
-
代表组件 javadoc jar 的“javadoc”变体
-
从
<dependencyManagement>
块派生的四个“平台”变体(属性org.gradle.category
=platform
)-
platform-compile
变体将<scope>compile</scope>
依赖管理依赖项映射为依赖约束。 -
platform-runtime
变体将<scope>compile</scope>
和<scope>runtime</scope>
依赖管理依赖项都映射为依赖约束。 -
enforced-platform-compile
与platform-compile
类似,但所有约束都是强制的 -
enforced-platform-runtime
与platform-runtime
类似,但所有约束都是强制的
-
通过查阅手册的导入 BOM 部分,你可以更深入地了解平台和强制平台变体的使用。默认情况下,每当你声明对 Maven 模块的依赖项时,Gradle 都会查找 library
变体。然而,使用 platform
或 enforcedPlatform
关键字,Gradle 现在会查找“平台”变体之一,这允许你从 POM 文件导入约束,而不是依赖项。
Ivy 文件到变体的映射
Gradle 没有为 Ivy 文件实现内置的派生策略。Ivy 是一种灵活的格式,允许你发布任意文件,并且可以进行高度定制。
如果你想为 Ivy 实现 compile 和 runtime 变体的派生策略,可以使用组件元数据规则来实现。组件元数据规则 API 允许你访问 Ivy 配置并基于它们创建变体。如果你知道所有你正在消费的 Ivy 模块都是使用 Gradle 发布且未对 ivy.xml
文件进行进一步定制,你可以将以下规则添加到你的构建中
abstract class IvyVariantDerivationRule @Inject internal constructor(objectFactory: ObjectFactory) : ComponentMetadataRule {
private val jarLibraryElements: LibraryElements
private val libraryCategory: Category
private val javaRuntimeUsage: Usage
private val javaApiUsage: Usage
init {
jarLibraryElements = objectFactory.named(LibraryElements.JAR)
libraryCategory = objectFactory.named(Category.LIBRARY)
javaRuntimeUsage = objectFactory.named(Usage.JAVA_RUNTIME)
javaApiUsage = objectFactory.named(Usage.JAVA_API)
}
override fun execute(context: ComponentMetadataContext) {
// This filters out any non Ivy module
if(context.getDescriptor(IvyModuleDescriptor::class) == null) {
return
}
context.details.addVariant("runtimeElements", "default") {
attributes {
attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, jarLibraryElements)
attribute(Category.CATEGORY_ATTRIBUTE, libraryCategory)
attribute(Usage.USAGE_ATTRIBUTE, javaRuntimeUsage)
}
}
context.details.addVariant("apiElements", "compile") {
attributes {
attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, jarLibraryElements)
attribute(Category.CATEGORY_ATTRIBUTE, libraryCategory)
attribute(Usage.USAGE_ATTRIBUTE, javaApiUsage)
}
}
}
}
dependencies {
components { all<IvyVariantDerivationRule>() }
}
abstract class IvyVariantDerivationRule implements ComponentMetadataRule {
final LibraryElements jarLibraryElements
final Category libraryCategory
final Usage javaRuntimeUsage
final Usage javaApiUsage
@Inject
IvyVariantDerivationRule(ObjectFactory objectFactory) {
jarLibraryElements = objectFactory.named(LibraryElements, LibraryElements.JAR)
libraryCategory = objectFactory.named(Category, Category.LIBRARY)
javaRuntimeUsage = objectFactory.named(Usage, Usage.JAVA_RUNTIME)
javaApiUsage = objectFactory.named(Usage, Usage.JAVA_API)
}
void execute(ComponentMetadataContext context) {
// This filters out any non Ivy module
if(context.getDescriptor(IvyModuleDescriptor) == null) {
return
}
context.details.addVariant("runtimeElements", "default") {
attributes {
attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, jarLibraryElements)
attribute(Category.CATEGORY_ATTRIBUTE, libraryCategory)
attribute(Usage.USAGE_ATTRIBUTE, javaRuntimeUsage)
}
}
context.details.addVariant("apiElements", "compile") {
attributes {
attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, jarLibraryElements)
attribute(Category.CATEGORY_ATTRIBUTE, libraryCategory)
attribute(Usage.USAGE_ATTRIBUTE, javaApiUsage)
}
}
}
}
dependencies {
components { all(IvyVariantDerivationRule) }
}
该规则基于每个 Ivy 模块的 compile
配置创建一个 apiElements
变体,并基于 default
配置创建一个 runtimeElements
变体。对于每个变体,它设置了相应的Java 生态系统属性。变体的依赖项和 Artifact 来自底层配置。如果并非所有消费的 Ivy 模块都遵循此模式,则可以调整规则或仅应用于选定的一组模块。
对于所有没有变体的 Ivy 模块,Gradle 有一个回退选择方法。Gradle 不执行变体感知解析,而是选择 default
配置或显式命名的配置。