变体和属性
变体(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 及其核心插件定义的标准属性可能很有用。
作为插件作者,这些属性以及它们的定义方式,可以作为在您的生态系统插件中构建您自己的属性集的基础。
生态系统无关的标准属性
属性名称 | 描述 | 值 | 兼容性和消歧规则 |
---|---|---|---|
表示变体的主要用途 |
|
遵循生态系统语义(例如 |
|
表示此软件组件的类别 |
|
遵循生态系统语义(例如,在 JVM 上 |
|
表示 |
|
遵循生态系统语义(例如,在 JVM 世界中, |
|
表示 |
|
无默认值,不兼容 |
|
指示如何访问变体的依赖项。 |
|
遵循生态系统语义(例如,在 JVM 世界中, |
|
指示生成此输出的验证任务类型。 |
|
无默认值,不兼容 |
当变体上存在带有孵化值 org.gradle.category=verification
的 Category
属性时,该变体被视为仅在验证时使用的变体。
这些变体旨在仅包含运行验证任务的结果,例如测试结果或代码覆盖率报告。它们不可发布,如果添加到已发布的组件中,将产生错误。
属性名称 | 描述 | 值 | 兼容性和消歧规则 |
---|---|---|---|
|
组件级别属性,派生 |
基于状态方案,并基于源仓库存在默认方案。 |
基于正在使用的方案 |
JVM 生态系统特定属性
除了上面定义的生态系统无关属性外,JVM 生态系统还添加了以下属性
属性名称 | 描述 | 值 | 兼容性和消歧规则 |
---|---|---|---|
指示 JVM 版本兼容性。 |
对于 Java 1.4 及更早版本,使用 |
默认为 Gradle 使用的 JVM 版本,低版本兼容高版本,优先选择最高兼容版本。 |
|
指示变体针对特定的 JVM 环境进行了优化。 |
常见值为 |
如果存在多个变体,该属性用于优先选择一个变体而不是另一个,但通常所有值都兼容。默认值为 |
|
指示生成此输出的 TestSuite 的名称。 |
值为 Suite 的名称。 |
无默认值,不兼容 |
JVM 生态系统还包含许多关于不同属性的兼容性和消歧规则。希望了解更多信息的读者可以查看 org.gradle.api.internal.artifacts.JavaEcosystemSupport
的代码。
Native 生态系统特定属性
除了上面定义的生态系统无关属性外,原生生态系统还添加了以下属性
属性名称 | 描述 | 值 | 兼容性和消歧规则 |
---|---|---|---|
指示二进制文件是否使用调试符号构建 |
布尔值 |
不适用 |
|
指示二进制文件是否使用优化标志构建 |
布尔值 |
不适用 |
|
指示二进制文件的目标架构 |
|
无 |
|
指示二进制文件的目标操作系统 |
|
无 |
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
处理程序中的属性 schema 中声明
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)
}
为了使用兼容性(Compatibility)和消歧(Disambiguation)规则来解决 属性匹配 期间多个可选变体之间的歧义,必须在 schema 中注册属性。
每个配置都包含一个属性容器。属性可以配置以设置值
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 提供了可以为每个属性定义的 属性兼容性规则。兼容性规则的作用是解释哪些属性值根据消费者请求的内容是兼容的。
属性兼容性规则必须通过 属性 schema 注册。
属性消歧规则
当依赖项的多个变体与消费者请求的属性兼容时,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 中的 属性 schema 访问。这确保了规则全局应用于依赖项解析。
将 Maven/Ivy 映射到 Gradle 变体
Maven 和 Ivy 都没有变体的概念,只有 Gradle Module Metadata 才原生支持变体。Gradle 仍然可以通过使用不同的变体派生策略来与 Maven 和 Ivy 配合使用。
Gradle Module Metadata 是一种用于发布到 Maven、Ivy 和其他类型仓库的模块的元数据格式。它类似于 pom.xml
或 ivy.xml
元数据文件,但此格式包含有关变体的详细信息。
有关更多信息,请参阅 {metadata-file-spec}[Gradle Module Metadata 规范]。
Maven POM 元数据到变体的映射
发布到 Maven 仓库的模块在被 Gradle 解析时会自动转换为变体感知模块。
Gradle 无法知道发布了哪种组件
-
表示 Gradle 平台的 BOM
-
用作 super-POM 的 BOM
-
既是平台又是库的 POM
Gradle 中 Java 项目使用的默认策略是派生 8 种不同的变体
-
两个“库”变体(属性
org.gradle.category
=library
)-
compile
变体映射<scope>compile</scope>
依赖项。此变体等同于 Java Library 插件的apiElements
变体。此范围的所有依赖项都被视为API 依赖项。 -
runtime
变体映射<scope>compile</scope>
和<scope>runtime</scope>
依赖项。此变体等同于 Java Library 插件的runtimeElements
变体。这些范围的所有依赖项都被视为运行时依赖项。-
在这两种情况下,
<dependencyManagement>
依赖项都不会转换为约束
-
-
-
表示组件的源 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
,但所有约束都强制执行
-
您可以通过查看手册的导入 BOMs部分,了解更多关于平台和强制平台变体的使用。默认情况下,无论何时您声明对 Maven 模块的依赖,Gradle 都会查找 library
变体。然而,使用 platform
或 enforcedPlatform
关键字,Gradle 现在会查找其中一个“平台”变体,这允许您从 POM 文件导入约束,而不是依赖项。
将 Ivy 文件映射到变体
Gradle 没有为 Ivy 文件实现内置的派生策略。Ivy 是一种灵活的格式,允许您发布任意文件并可以进行大量自定义。
如果您想为 Ivy 的编译和运行时变体实现派生策略,您可以使用组件元数据规则来实现。组件元数据规则 API 允许您访问 Ivy 配置并根据它们创建变体。如果您知道所有您正在使用的 Ivy 模块都已在没有进一步自定义 ivy.xml
文件的情况下使用 Gradle 发布,您可以将以下规则添加到您的构建中
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 生态系统属性。变体的依赖项和工件取自底层配置。如果并非所有使用的 Ivy 模块都遵循此模式,则可以调整规则或仅将其应用于选定的模块集。
对于所有没有变体的 Ivy 模块,Gradle 都有一个回退选择方法。Gradle 不执行变体感知解析,而是选择 default
配置或显式命名的配置。