修改依赖元数据
从仓库中拉取的每个组件都包含元数据,例如其组、名称、版本以及它提供的各种变体以及它们的构件和依赖项。
有时,此元数据可能不完整或不正确。
Gradle 提供了一个 API 来解决此问题,允许您直接在构建脚本中编写组件元数据规则。 这些规则在模块的元数据下载后但在依赖关系解析中使用之前应用。
编写组件元数据规则
组件元数据规则在构建脚本或设置脚本的 components
部分的 dependencies
块内应用。
这些规则可以通过两种方式定义
-
内联作为操作: 直接在
components
部分内。 -
作为单独的类: 实现
ComponentMetadataRule
接口。
虽然内联操作对于快速实验很方便,但通常建议将规则定义为单独的类。
作为隔离类编写的规则可以使用 @CacheableRule
注释,允许缓存其结果并避免每次解析依赖关系时重新执行。
规则应始终是可缓存的,以避免对构建性能产生重大影响并确保更快的构建时间。 |
@CacheableRule
abstract class TargetJvmVersionRule @Inject constructor(val jvmVersion: Int) : ComponentMetadataRule {
@get:Inject abstract val objects: ObjectFactory
override fun execute(context: ComponentMetadataContext) {
context.details.withVariant("compile") {
attributes {
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, jvmVersion)
attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_API))
}
}
}
}
dependencies {
components {
withModule<TargetJvmVersionRule>("commons-io:commons-io") {
params(7)
}
withModule<TargetJvmVersionRule>("commons-collections:commons-collections") {
params(8)
}
}
implementation("commons-io:commons-io:2.6")
implementation("commons-collections:commons-collections:3.2.2")
}
@CacheableRule
abstract class TargetJvmVersionRule implements ComponentMetadataRule {
final Integer jvmVersion
@Inject TargetJvmVersionRule(Integer jvmVersion) {
this.jvmVersion = jvmVersion
}
@Inject abstract ObjectFactory getObjects()
void execute(ComponentMetadataContext context) {
context.details.withVariant("compile") {
attributes {
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, jvmVersion)
attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, Usage.JAVA_API))
}
}
}
}
dependencies {
components {
withModule("commons-io:commons-io", TargetJvmVersionRule) {
params(7)
}
withModule("commons-collections:commons-collections", TargetJvmVersionRule) {
params(8)
}
}
implementation("commons-io:commons-io:2.6")
implementation("commons-collections:commons-collections:3.2.2")
}
在此示例中,TargetJvmVersionRule
类实现了 ComponentMetadataRule
,并使用 ActionConfiguration
进一步配置。
Gradle 强制隔离 ComponentMetadataRule
的实例,要求所有参数都必须是 Serializable
或可识别的 Gradle 类型。
此外,可以使用 @Inject
将 ObjectFactory
等服务注入到规则的构造函数中。
组件元数据规则可以应用于所有模块,使用 all(rule)
,或应用于特定模块,使用 withModule(groupAndName, rule)
。 通常,规则是为丰富特定模块的元数据而定制的,因此 withModule
API 是首选。
在中心位置声明规则
在设置中声明组件元数据规则是一项孵化功能 |
组件元数据规则可以在整个构建的 settings.gradle(.kts)
文件中声明,而不是在每个子项目中单独声明。 在设置中声明的规则默认应用于所有项目,除非被项目特定的规则覆盖。
dependencyResolutionManagement {
components {
withModule<GuavaRule>("com.google.guava:guava")
}
}
dependencyResolutionManagement {
components {
withModule("com.google.guava:guava", GuavaRule)
}
}
默认情况下,项目特定规则优先于设置规则。 但是,此行为可以调整
dependencyResolutionManagement {
rulesMode = RulesMode.PREFER_SETTINGS
}
dependencyResolutionManagement {
rulesMode = RulesMode.PREFER_SETTINGS
}
如果调用此方法并且项目或插件声明了规则,则会发出警告。 您可以使用此替代方法将其设为失败
dependencyResolutionManagement {
rulesMode = RulesMode.FAIL_ON_PROJECT_RULES
}
dependencyResolutionManagement {
rulesMode = RulesMode.FAIL_ON_PROJECT_RULES
}
默认行为等效于调用此方法
dependencyResolutionManagement {
rulesMode = RulesMode.PREFER_PROJECT
}
dependencyResolutionManagement {
rulesMode = RulesMode.PREFER_PROJECT
}
哪些元数据部分可以修改?
组件元数据规则 API 侧重于 Gradle 模块元数据和依赖关系 API 支持的功能。 使用元数据规则和在构建脚本中定义依赖关系/构件之间的主要区别在于,组件元数据规则直接在 变体 上操作,而构建脚本通常一次影响多个变体(例如,api
依赖关系应用于 Java 库的 api
和 runtime
变体)。
可以通过以下方法修改变体
-
allVariants
:修改组件的所有变体。 -
withVariant(name)
:修改由其名称标识的特定变体。 -
addVariant(name)
或addVariant(name, base)
:从头开始添加新变体或从现有变体(base
)复制详细信息。
可以修改以下变体详细信息
此外,还可以更改几个组件级别的属性
-
组件属性:此处唯一有意义的 属性 是
org.gradle.status
。 -
状态方案:影响在版本选择期间如何解释
org.gradle.status
属性。 -
BelongsTo 属性:用于通过虚拟平台进行 <component_capabilities.adoc#sec:declaring-capabilities-external-modules, 版本对齐>>。
模块元数据的格式会影响其如何映射到以变体为中心的表示形式
-
Gradle 模块元数据:数据结构类似于模块的
.module
文件。 -
POM 元数据:对于使用
.pom
元数据发布的模块,固定变体是按照 “将 POM 文件映射到变体” 部分中说明的方式派生的。 -
Ivy 元数据:如果模块是使用
ivy.xml
文件发布的,则可以访问 Ivy 配置来代替变体。 它们的依赖项、约束和文件可以修改。 您还可以使用addVariant(name, baseVariantOrConfiguration)
从 Ivy 配置派生变体,例如为 Java 库插件定义compile
和runtime
变体。
在使用组件元数据规则调整模块的元数据之前,请确定模块是使用 Gradle 模块元数据 (.module
文件) 还是 传统元数据 (.pom
或 ivy.xml
) 发布的
-
具有 Gradle 模块元数据的模块:这些模块通常具有完整的元数据,但仍可能发生问题。 仅当您明确识别出元数据存在问题时才应用组件元数据规则。 对于依赖关系解析问题,首先考虑使用 具有富版本的依赖约束。 如果您正在开发库,请注意依赖约束作为您自己的库元数据的一部分发布,从而更容易与使用者共享解决方案。 相比之下,组件元数据规则仅在您自己的构建中应用。
-
具有传统元数据的模块 (
.pom
或ivy.xml
):这些模块更可能具有不完整的元数据,因为这些格式不支持变体和依赖约束等功能。 此类模块可能具有被省略或错误地定义为依赖项的变体或约束。 在以下部分中,我们将探讨具有不完整元数据的 OSS 模块示例以及添加缺失信息的规则。
作为经验法则,您应该考虑您正在编写的规则是否也在您的构建上下文之外起作用。 也就是说,如果规则应用于使用它影响的模块的任何其他构建,规则是否仍然产生正确且有用的结果?
修复不正确的依赖详情
考虑在 Maven Central 上发布的 Jaxen XPath 引擎(版本 1.1.3
)。 它的 pom
文件在 compile
作用域中声明了几个不必要的依赖项,这些依赖项后来在版本 1.1.4
中删除。 如果您需要使用版本 1.1.3
,您可以使用以下规则修复元数据
@CacheableRule
abstract class JaxenDependenciesRule: ComponentMetadataRule {
override fun execute(context: ComponentMetadataContext) {
context.details.allVariants {
withDependencies {
removeAll { it.group in listOf("dom4j", "jdom", "xerces", "maven-plugins", "xml-apis", "xom") }
}
}
}
}
@CacheableRule
abstract class JaxenDependenciesRule implements ComponentMetadataRule {
void execute(ComponentMetadataContext context) {
context.details.allVariants {
withDependencies {
removeAll { it.group in ["dom4j", "jdom", "xerces", "maven-plugins", "xml-apis", "xom"] }
}
}
}
}
在 withDependencies
块中,您可以访问依赖项的完整列表,并可以使用 Java 集合方法来检查和修改该列表。 您还可以使用 add(notation, configureAction)
方法添加依赖项。 同样,您可以在 withDependencyConstraints
块中检查和修改依赖约束。
在 Jaxen 版本 1.1.4
中,dom4j
、jdom
和 xerces
依赖项仍然存在,但标记为 可选。 可选依赖项不会由 Gradle 或 Maven 自动处理,因为它们指示需要额外依赖项的 功能变体。 但是,pom
文件缺少有关这些功能及其相应依赖项的信息。 这可以通过变体和 功能 在 Gradle 模块元数据中表示,我们可以通过组件元数据规则添加这些信息。
@CacheableRule
abstract class JaxenCapabilitiesRule: ComponentMetadataRule {
override fun execute(context: ComponentMetadataContext) {
context.details.addVariant("runtime-dom4j", "runtime") {
withCapabilities {
removeCapability("jaxen", "jaxen")
addCapability("jaxen", "jaxen-dom4j", context.details.id.version)
}
withDependencies {
add("dom4j:dom4j:1.6.1")
}
}
}
}
@CacheableRule
abstract class JaxenCapabilitiesRule implements ComponentMetadataRule {
void execute(ComponentMetadataContext context) {
context.details.addVariant("runtime-dom4j", "runtime") {
withCapabilities {
removeCapability("jaxen", "jaxen")
addCapability("jaxen", "jaxen-dom4j", context.details.id.version)
}
withDependencies {
add("dom4j:dom4j:1.6.1")
}
}
}
}
在此示例中,我们使用 addVariant(name, baseVariant)
方法创建一个名为 runtime-dom4j
的新变体。 此变体表示可选功能,由功能 jaxen-dom4j
定义。 然后,我们将所需的依赖项 dom4j:dom4j:1.6.1
添加到此功能。
dependencies {
components {
withModule<JaxenDependenciesRule>("jaxen:jaxen")
withModule<JaxenCapabilitiesRule>("jaxen:jaxen")
}
implementation("jaxen:jaxen:1.1.3")
runtimeOnly("jaxen:jaxen:1.1.3") {
capabilities { requireCapability("jaxen:jaxen-dom4j") }
}
}
dependencies {
components {
withModule("jaxen:jaxen", JaxenDependenciesRule)
withModule("jaxen:jaxen", JaxenCapabilitiesRule)
}
implementation("jaxen:jaxen:1.1.3")
runtimeOnly("jaxen:jaxen:1.1.3") {
capabilities { requireCapability("jaxen:jaxen-dom4j") }
}
}
通过应用这些规则,当需要 jaxen-dom4j
功能时,Gradle 使用丰富的元数据来正确解析可选依赖项。
使作为分类 JAR 发布的变体显式化
在现代构建中,变体通常作为单独的构件发布,每个构件都由自己的 JAR 文件表示。 例如,库可以为不同的 Java 版本提供不同的 JAR,确保在运行时或编译时根据环境使用正确的版本。
例如,异步编程库 Quasar 的版本 0.7.9
(在 Maven Central 上发布)包括 quasar-core-0.7.9.jar
和 quasar-core-0.7.9-jdk8.jar
。 使用分类符(例如 jdk8
)发布 JAR 在 Maven 仓库中是一种常见做法。 但是,Maven 和 Gradle 元数据均未提供有关这些分类 JAR 的信息。 因此,没有明确的方法来确定它们的存在或变体之间(例如依赖项)的任何差异。
在 Gradle 模块元数据中,变体信息将存在。 对于已发布的 Quasar 库,我们可以使用以下规则添加此信息
@CacheableRule
abstract class QuasarRule: ComponentMetadataRule {
override fun execute(context: ComponentMetadataContext) {
listOf("compile", "runtime").forEach { base ->
context.details.addVariant("jdk8${base.capitalize()}", base) {
attributes {
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 8)
}
withFiles {
removeAllFiles()
addFile("${context.details.id.name}-${context.details.id.version}-jdk8.jar")
}
}
context.details.withVariant(base) {
attributes {
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 7)
}
}
}
}
}
@CacheableRule
abstract class QuasarRule implements ComponentMetadataRule {
void execute(ComponentMetadataContext context) {
["compile", "runtime"].each { base ->
context.details.addVariant("jdk8${base.capitalize()}", base) {
attributes {
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 8)
}
withFiles {
removeAllFiles()
addFile("${context.details.id.name}-${context.details.id.version}-jdk8.jar")
}
}
context.details.withVariant(base) {
attributes {
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 7)
}
}
}
}
}
在这种情况下,jdk8
分类符清楚地表明了目标 Java 版本,这对应于 Java 生态系统中的已知属性。 由于 Java 8 需要编译和运行时变体,因此我们使用现有的编译和运行时变体作为基础创建两个新变体。 这确保了正确设置所有其他 Java 生态系统属性,并且依赖项被结转。
我们将 TARGET_JVM_VERSION_ATTRIBUTE
分配给两个新变体的 8
,删除任何现有文件,使用 removeAllFiles()
,然后使用 addFile()
添加 jdk8
JAR。 删除文件是必要的,因为对主 JAR quasar-core-0.7.9.jar
的引用是从基本变体复制的。
最后,我们使用 attribute(TARGET_JVM_VERSION_ATTRIBUTE, 7)
丰富现有编译和运行时变体的信息,即它们以 Java 7 为目标。
通过这些更改,您现在可以为编译类路径上的所有依赖项请求 Java 8 版本,Gradle 将自动选择最合适的变体。 在 Quasar 的情况下,这将是 jdk8Compile
变体,它公开了 quasar-core-0.7.9-jdk8.jar
。
configurations["compileClasspath"].attributes {
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 8)
}
dependencies {
components {
withModule<QuasarRule>("co.paralleluniverse:quasar-core")
}
implementation("co.paralleluniverse:quasar-core:0.7.9")
}
configurations.compileClasspath.attributes {
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 8)
}
dependencies {
components {
withModule("co.paralleluniverse:quasar-core", QuasarRule)
}
implementation("co.paralleluniverse:quasar-core:0.7.9")
}
通过此配置,Gradle 将为编译类路径选择 Quasar 的 Java 8 变体。
使版本中编码的变体显式化
发布同一库的多个替代方案的另一种解决方案是使用版本控制模式,例如流行的 Guava 库所做的那样。 在这里,每个新版本都发布两次,方法是将分类符附加到版本而不是 JAR 构件。 例如,对于 Guava 28,我们可以在 Maven central 上找到 28.0-jre (Java 8) 和 28.0-android (Java 6) 版本。 仅使用 POM 元数据时,使用此模式的优点在于,可以通过版本发现两个变体。 缺点是,没有关于不同版本后缀在语义上意味着什么的信息。 因此,在发生冲突时,Gradle 只会在比较版本字符串时选择最高的版本。
将其转换为正确的变体有点棘手,因为 Gradle 首先选择模块的版本,然后选择最合适的变体。 因此,不直接支持将变体编码为版本的概念。 但是,由于两个变体始终一起发布,我们可以假设文件物理上位于同一仓库中。 并且由于它们是使用 Maven 仓库约定发布的,因此如果我们知道模块名称和版本,我们就知道每个文件的位置。 我们可以编写以下规则
@CacheableRule
abstract class GuavaRule: ComponentMetadataRule {
override fun execute(context: ComponentMetadataContext) {
val variantVersion = context.details.id.version
val version = variantVersion.substring(0, variantVersion.indexOf("-"))
listOf("compile", "runtime").forEach { base ->
mapOf(6 to "android", 8 to "jre").forEach { (targetJvmVersion, jarName) ->
context.details.addVariant("jdk$targetJvmVersion${base.capitalize()}", base) {
attributes {
attributes.attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, targetJvmVersion)
}
withFiles {
removeAllFiles()
addFile("guava-$version-$jarName.jar", "../$version-$jarName/guava-$version-$jarName.jar")
}
}
}
}
}
}
@CacheableRule
abstract class GuavaRule implements ComponentMetadataRule {
void execute(ComponentMetadataContext context) {
def variantVersion = context.details.id.version
def version = variantVersion.substring(0, variantVersion.indexOf("-"))
["compile", "runtime"].each { base ->
[6: "android", 8: "jre"].each { targetJvmVersion, jarName ->
context.details.addVariant("jdk$targetJvmVersion${base.capitalize()}", base) {
attributes {
attributes.attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, targetJvmVersion)
}
withFiles {
removeAllFiles()
addFile("guava-$version-${jarName}.jar", "../$version-$jarName/guava-$version-${jarName}.jar")
}
}
}
}
}
}
与前面的示例类似,我们为两个 Java 版本添加了运行时和编译变体。 但是,在 withFiles
块中,我们现在还为相应的 JAR 文件指定了相对路径,这允许 Gradle 找到该文件,无论它是否选择了 -jre 或 -android 版本。 该路径始终相对于所选模块版本的元数据(在本例中为 pom
)文件的位置。 因此,通过此规则,Guava 28 的两个“版本”都带有 jdk6 和 jdk8 变体。 因此,Gradle 解析为哪个版本并不重要。 变体以及正确的 JAR 文件是根据请求的 TARGET_JVM_VERSION_ATTRIBUTE
值确定的。
configurations["compileClasspath"].attributes {
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 6)
}
dependencies {
components {
withModule<GuavaRule>("com.google.guava:guava")
}
// '23.3-android' and '23.3-jre' are now the same as both offer both variants
implementation("com.google.guava:guava:23.3+")
}
configurations.compileClasspath.attributes {
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 6)
}
dependencies {
components {
withModule("com.google.guava:guava", GuavaRule)
}
// '23.3-android' and '23.3-jre' are now the same as both offer both variants
implementation("com.google.guava:guava:23.3+")
}
为原生 JAR 添加变体
带有分类符的 JAR 也用于分隔库的某些部分(对于这些部分存在多个替代方案),例如原生代码,与主构件分开。 例如,Lightweight Java Game Library (LWGJ) 就是这样做的,它将多个平台特定的 JAR 发布到 Maven central,在运行时,除了主 JAR 之外,始终需要从中选择一个。 不可能在 POM 元数据中传达此信息,因为没有通过元数据将多个构件关联起来的概念。 在 Gradle 模块元数据中,每个变体可以有任意多个文件,我们可以通过编写以下规则来利用这一点
@CacheableRule
abstract class LwjglRule: ComponentMetadataRule {
data class NativeVariant(val os: String, val arch: String, val classifier: String)
private val nativeVariants = listOf(
NativeVariant(OperatingSystemFamily.LINUX, "arm32", "natives-linux-arm32"),
NativeVariant(OperatingSystemFamily.LINUX, "arm64", "natives-linux-arm64"),
NativeVariant(OperatingSystemFamily.WINDOWS, "x86", "natives-windows-x86"),
NativeVariant(OperatingSystemFamily.WINDOWS, "x86-64", "natives-windows"),
NativeVariant(OperatingSystemFamily.MACOS, "x86-64", "natives-macos")
)
@get:Inject abstract val objects: ObjectFactory
override fun execute(context: ComponentMetadataContext) {
context.details.withVariant("runtime") {
attributes {
attributes.attribute(OperatingSystemFamily.OPERATING_SYSTEM_ATTRIBUTE, objects.named("none"))
attributes.attribute(MachineArchitecture.ARCHITECTURE_ATTRIBUTE, objects.named("none"))
}
}
nativeVariants.forEach { variantDefinition ->
context.details.addVariant("${variantDefinition.classifier}-runtime", "runtime") {
attributes {
attributes.attribute(OperatingSystemFamily.OPERATING_SYSTEM_ATTRIBUTE, objects.named(variantDefinition.os))
attributes.attribute(MachineArchitecture.ARCHITECTURE_ATTRIBUTE, objects.named(variantDefinition.arch))
}
withFiles {
addFile("${context.details.id.name}-${context.details.id.version}-${variantDefinition.classifier}.jar")
}
}
}
}
}
@CacheableRule
abstract class LwjglRule implements ComponentMetadataRule { //val os: String, val arch: String, val classifier: String)
private def nativeVariants = [
[os: OperatingSystemFamily.LINUX, arch: "arm32", classifier: "natives-linux-arm32"],
[os: OperatingSystemFamily.LINUX, arch: "arm64", classifier: "natives-linux-arm64"],
[os: OperatingSystemFamily.WINDOWS, arch: "x86", classifier: "natives-windows-x86"],
[os: OperatingSystemFamily.WINDOWS, arch: "x86-64", classifier: "natives-windows"],
[os: OperatingSystemFamily.MACOS, arch: "x86-64", classifier: "natives-macos"]
]
@Inject abstract ObjectFactory getObjects()
void execute(ComponentMetadataContext context) {
context.details.withVariant("runtime") {
attributes {
attributes.attribute(OperatingSystemFamily.OPERATING_SYSTEM_ATTRIBUTE, objects.named(OperatingSystemFamily, "none"))
attributes.attribute(MachineArchitecture.ARCHITECTURE_ATTRIBUTE, objects.named(MachineArchitecture, "none"))
}
}
nativeVariants.each { variantDefinition ->
context.details.addVariant("${variantDefinition.classifier}-runtime", "runtime") {
attributes {
attributes.attribute(OperatingSystemFamily.OPERATING_SYSTEM_ATTRIBUTE, objects.named(OperatingSystemFamily, variantDefinition.os))
attributes.attribute(MachineArchitecture.ARCHITECTURE_ATTRIBUTE, objects.named(MachineArchitecture, variantDefinition.arch))
}
withFiles {
addFile("${context.details.id.name}-${context.details.id.version}-${variantDefinition.classifier}.jar")
}
}
}
}
}
此规则与上面的 Quasar 库示例非常相似。 只是这次我们添加了五个不同的运行时变体,而编译变体没有任何需要更改的内容。 运行时变体都基于现有的 runtime 变体,我们不会更改任何现有信息。 所有 Java 生态系统属性、依赖项和主 JAR 文件仍然是每个运行时变体的一部分。 我们只设置了额外的属性 OPERATING_SYSTEM_ATTRIBUTE
和 ARCHITECTURE_ATTRIBUTE
,它们被定义为 Gradle 原生支持 的一部分。 我们添加了相应的原生 JAR 文件,以便每个运行时变体现在都带有两个文件:主 JAR 和原生 JAR。
在构建脚本中,我们现在可以请求特定的变体,如果需要更多信息才能做出决定,Gradle 将会因选择错误而失败。
Gradle 能够理解常见情况,即缺少一个属性,该属性本可以消除歧义。 在这种情况下,Gradle 没有列出有关所有可用变体上的所有属性的信息,而是有帮助地仅列出了该属性的可能值以及每个值将选择的变体。
configurations["runtimeClasspath"].attributes {
attribute(OperatingSystemFamily.OPERATING_SYSTEM_ATTRIBUTE, objects.named("windows"))
}
dependencies {
components {
withModule<LwjglRule>("org.lwjgl:lwjgl")
}
implementation("org.lwjgl:lwjgl:3.2.3")
}
configurations["runtimeClasspath"].attributes {
attribute(OperatingSystemFamily.OPERATING_SYSTEM_ATTRIBUTE, objects.named(OperatingSystemFamily, "windows"))
}
dependencies {
components {
withModule("org.lwjgl:lwjgl", LwjglRule)
}
implementation("org.lwjgl:lwjgl:3.2.3")
}
Gradle 无法选择变体,因为需要选择机器架构
> Could not resolve all files for configuration ':runtimeClasspath'. > Could not resolve org.lwjgl:lwjgl:3.2.3. Required by: project : > The consumer was configured to find a library for use during runtime, compatible with Java 11, packaged as a jar, preferably optimized for standard JVMs, and its dependencies declared externally, as well as attribute 'org.gradle.native.operatingSystem' with value 'windows'. There are several available matching variants of org.lwjgl:lwjgl:3.2.3 The only attribute distinguishing these variants is 'org.gradle.native.architecture'. Add this attribute to the consumer's configuration to resolve the ambiguity: - Value: 'x86-64' selects variant: 'natives-windows-runtime' - Value: 'x86' selects variant: 'natives-windows-x86-runtime'
通过功能使库的不同风格可用
由于很难将 可选功能变体 建模为带有 POM 元数据的单独 JAR,因此库有时包含具有不同功能集的不同 JAR。 也就是说,不是从不同的功能变体组合您的库风格,而是选择预先组合的变体之一(在一个 JAR 中提供所有内容)。 一个这样的库是著名的依赖注入框架 Guice,它在 Maven central 上发布,它提供了一个完整的风格(主 JAR)和一个减少的变体,没有面向方面的编程支持 (guice-4.2.2-no_aop.jar
)。 第二个带有分类符的变体未在 POM 元数据中提及。 通过以下规则,我们基于该文件创建编译和运行时变体,并通过名为 com.google.inject:guice-no_aop
的功能使其可选择。
@CacheableRule
abstract class GuiceRule: ComponentMetadataRule {
override fun execute(context: ComponentMetadataContext) {
listOf("compile", "runtime").forEach { base ->
context.details.addVariant("noAop${base.capitalize()}", base) {
withCapabilities {
addCapability("com.google.inject", "guice-no_aop", context.details.id.version)
}
withFiles {
removeAllFiles()
addFile("guice-${context.details.id.version}-no_aop.jar")
}
withDependencies {
removeAll { it.group == "aopalliance" }
}
}
}
}
}
@CacheableRule
abstract class GuiceRule implements ComponentMetadataRule {
void execute(ComponentMetadataContext context) {
["compile", "runtime"].each { base ->
context.details.addVariant("noAop${base.capitalize()}", base) {
withCapabilities {
addCapability("com.google.inject", "guice-no_aop", context.details.id.version)
}
withFiles {
removeAllFiles()
addFile("guice-${context.details.id.version}-no_aop.jar")
}
withDependencies {
removeAll { it.group == "aopalliance" }
}
}
}
}
}
新的变体还删除了对标准化 aop 接口库 aopalliance:aopalliance
的依赖,因为这些变体显然不需要它。 同样,这是无法在 POM 元数据中表达的信息。 我们现在可以选择 guice-no_aop
变体,并将获得正确的 JAR 文件和正确的依赖项。
dependencies {
components {
withModule<GuiceRule>("com.google.inject:guice")
}
implementation("com.google.inject:guice:4.2.2") {
capabilities { requireCapability("com.google.inject:guice-no_aop") }
}
}
dependencies {
components {
withModule("com.google.inject:guice", GuiceRule)
}
implementation("com.google.inject:guice:4.2.2") {
capabilities { requireCapability("com.google.inject:guice-no_aop") }
}
}
添加缺失的功能以检测冲突
功能的另一个用途是表达两个不同的模块(例如 log4j
和 log4j-over-slf4j
)提供相同事物的替代实现。 通过声明两者都提供相同的功能,Gradle 只接受依赖关系图中的其中一个。 此示例以及如何使用组件元数据规则解决此问题在 功能建模 部分中详细描述。
使 Ivy 模块变体感知
使用 Ivy 发布的模块默认情况下没有可用的变体。
但是,Ivy 配置可以映射到变体,因为 addVariant(name, baseVariantOrConfiguration)
接受作为基础发布的任何 Ivy 配置。 这可以用于例如定义运行时和编译变体。 可以在 此处 找到相应规则的示例。 Ivy 配置的 Ivy 详细信息(例如依赖项和文件)也可以使用 withVariant(configurationName)
API 进行修改。 但是,修改 Ivy 配置上的属性或功能无效。
对于非常特定于 Ivy 的用例,组件元数据规则 API 还提供对仅在 Ivy 元数据中找到的其他详细信息的访问。 这些详细信息通过 IvyModuleDescriptor 接口提供,可以使用 ComponentMetadataContext 上的 getDescriptor(IvyModuleDescriptor)
进行访问。
@CacheableRule
abstract class IvyComponentRule : ComponentMetadataRule {
override fun execute(context: ComponentMetadataContext) {
val descriptor = context.getDescriptor(IvyModuleDescriptor::class)
if (descriptor != null && descriptor.branch == "testing") {
context.details.status = "rc"
}
}
}
@CacheableRule
abstract class IvyComponentRule implements ComponentMetadataRule {
void execute(ComponentMetadataContext context) {
def descriptor = context.getDescriptor(IvyModuleDescriptor)
if (descriptor != null && descriptor.branch == "testing") {
context.details.status = "rc"
}
}
}
使用 Maven 元数据进行过滤
对于特定于 Maven 的用例,组件元数据规则 API 还提供对仅在 POM 元数据中找到的其他详细信息的访问。 这些详细信息通过 PomModuleDescriptor 接口提供,可以使用 ComponentMetadataContext 上的 getDescriptor(PomModuleDescriptor)
进行访问。
@CacheableRule
abstract class MavenComponentRule : ComponentMetadataRule {
override fun execute(context: ComponentMetadataContext) {
val descriptor = context.getDescriptor(PomModuleDescriptor::class)
if (descriptor != null && descriptor.packaging == "war") {
// ...
}
}
}
@CacheableRule
abstract class MavenComponentRule implements ComponentMetadataRule {
void execute(ComponentMetadataContext context) {
def descriptor = context.getDescriptor(PomModuleDescriptor)
if (descriptor != null && descriptor.packaging == "war") {
// ...
}
}
}
在组件级别修改元数据以进行对齐
虽然上面的所有示例都对组件的变体进行了修改,但也可以对组件本身的元数据进行一组有限的修改。 此信息会影响依赖关系解析期间模块的 版本选择 过程,该过程在选择组件的一个或多个变体之前执行。
组件上可用的第一个 API 是 belongsTo()
,用于创建虚拟平台,以在没有 Gradle 模块元数据的情况下对齐多个模块的版本。 它在关于 对齐未与 Gradle 一起发布的模块的版本 的部分中详细解释。
在组件级别修改元数据以根据状态进行版本选择
Gradle 和 Gradle 模块元数据还允许在整个组件而不是单个变体上设置属性。 这些属性中的每一个都带有特殊的语义,因为它们会影响版本选择,版本选择在变体选择之前完成。 虽然变体选择可以处理 任何自定义属性,但版本选择仅考虑为其实现特定语义的属性。 目前,此处唯一有意义的属性是 org.gradle.status
。
org.gradle.status
模块属性指示模块或库的生命周期状态或成熟度级别
-
integration
:这表示模块正在积极开发中,可能不稳定。 -
milestone
:具有此状态的模块比标记为integration
的模块更成熟。 -
release
:此状态表示模块稳定且已正式发布。
因此,建议仅在组件级别修改此属性(如果有)。 专用的 API setStatus(value)
可用于此目的。 要修改组件的所有变体的另一个属性,应改为使用 withAllVariants { attributes {} }
。
当解析 最新版本选择器 时,会考虑模块的状态。 具体来说,latest.someStatus
将解析为状态为 someStatus
或更成熟状态的最高模块版本。 例如,latest.integration
将选择具有最高状态的模块版本(无论其状态如何)(因为 integration
是成熟度最低的状态,如下所述),而 latest.release
将选择状态为 release
的最高模块版本。
可以通过 setStatusScheme(valueList)
API 更改模块的状态方案来影响状态的解释。 此概念模拟模块在不同发布中随时间推移经历的不同成熟度级别。 默认状态方案(从成熟度最低到最高状态排序)是 integration
、milestone
、release
。 org.gradle.status
属性必须设置为组件状态方案中的值之一。 因此,每个组件始终具有从元数据确定的状态,如下所示
-
Gradle 模块元数据:为组件上的
org.gradle.status
属性发布的值 -
Ivy 元数据:ivy.xml 中定义的
status
,如果缺失,则默认为integration
-
Pom 元数据:SNAPSHOT 版本的模块为
integration
,所有其他模块为release
以下示例演示了基于组件元数据规则中声明的自定义状态方案的 latest
选择器,该规则适用于所有模块
@CacheableRule
abstract class CustomStatusRule : ComponentMetadataRule {
override fun execute(context: ComponentMetadataContext) {
context.details.statusScheme = listOf("nightly", "milestone", "rc", "release")
if (context.details.status == "integration") {
context.details.status = "nightly"
}
}
}
dependencies {
components {
all<CustomStatusRule>()
}
implementation("org.apache.commons:commons-lang3:latest.rc")
}
@CacheableRule
abstract class CustomStatusRule implements ComponentMetadataRule {
void execute(ComponentMetadataContext context) {
context.details.statusScheme = ["nightly", "milestone", "rc", "release"]
if (context.details.status == "integration") {
context.details.status = "nightly"
}
}
}
dependencies {
components {
all(CustomStatusRule)
}
implementation("org.apache.commons:commons-lang3:latest.rc")
}
与默认方案相比,该规则插入了一个新状态 rc
,并将 integration
替换为 nightly
。 状态为 integration
的现有模块映射到 nightly
。