从存储库中提取的每个模块都有与之关联的元数据,例如其组,名称,版本以及它提供的工件和依赖项所提供的不同变体. 有时,此元数据不完整或不正确. 为了从构建脚本中处理这种不完整的元数据,Gradle提供了一个API来编写组件元数据规则 . 这些规则在下载模块的元数据之后但在依赖关系解析中使用之前生效.

Basics of writing a component metadata rule

组件元数据规则应用于构建脚本的依赖关系块( DependencyHandler )的组件( ComponentMetadataHandler )部分中. 可以用两种不同的方式定义规则:

  1. 直接在组件部分中应用它们时作为操作

  2. 作为实现ComponentMetadataRule接口的隔离类

While defining rules inline as action can be convenient for experimentation, it is generally recommended to define rules as separate classes. Rules that are written as isolated classes can be annotated with @CacheableRule to cache the results of their application such that they do not need to be re-executed each time dependencies are resolved.

示例1.可配置组件元数据规则的示例
build.gradle
class TargetJvmVersionRule implements ComponentMetadataRule {
    final Integer jvmVersion
    @Inject TargetJvmVersionRule(Integer jvmVersion) {
        this.jvmVersion = jvmVersion
    }

    @Inject 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")
}
build.gradle.kts
open class TargetJvmVersionRule @Inject constructor(val jvmVersion: Int) : ComponentMetadataRule {
    @Inject open fun getObjects(): ObjectFactory = throw UnsupportedOperationException()

    override fun execute(context: ComponentMetadataContext) {
        context.details.withVariant("compile") {
            attributes {
                attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, jvmVersion)
                attribute(Usage.USAGE_ATTRIBUTE, getObjects().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")
}

从上面的示例中可以看出,组件元数据规则是通过实现ComponentMetadataRule来定义的,该组件具有单个execute方法,该方法接收ComponentMetadataContext的实例作为参数. 在此示例中,还通过ActionConfiguration进一步配置了规则. 通过在ComponentMetadataRule的实现中使构造函数接受已配置的参数和需要注入的服务,可以支持此功能.

Gradle强制隔离ComponentMetadataRule实例. 这意味着所有参数必须是可Serializable或可以隔离的已知Gradle类型.

另外,可以将Gradle服务注入到ComponentMetadataRule . 因此,一旦有了构造函数,就必须使用@javax.inject.Inject对其进行注释. 通常需要使用ObjectFactory服务来创建强类型值对象的实例,例如用于设置Attribute的值. RepositoryResourceAccessor是一项有助于使用自定义元数据对组件元数据规则进行高级使用的服务.

组件元数据规则可以应用于所有模块withModule(groupAndName, rule) all(rule) -或选定的模块withModule(groupAndName, rule) . 通常,专门编写规则以丰富一个特定模块的元数据,因此withModule API应该是首选.

Which parts of metadata can be modified?

组件元数据规则API面向Gradle Module元数据和构建脚本中的依赖项 API支持的功能. 编写规则与在构建脚本中定义依赖项和工件之间的主要区别在于,组件元数据规则遵循Gradle Module元数据的结构,直接对变体进行操作. 相反,在构建脚本中,您经常一次影响多个变量的形状(例如,将api依赖项添加到Java库的api运行时变量中,由jar任务生成的工件也添加到这两个变量中) .

变体可以通过以下方法进行修改:

  • allVariants :修改组件的所有变体

  • withVariant(name) :修改由其名称标识的单个变体

  • addVariant(name)addVariant(name, base)从头开始或通过复制现有变量的详细信息(基础)向组件添加新的变量

可以调整每个变体的以下详细信息:

  • 所述属性识别所述变体- attributes {}

  • 该变体提供的功能 withCapabilities { }

  • 变体的依赖项,包括丰富的版本 withDependencies {}

  • 变体的依赖关系约束 ,包括丰富版本 withDependencyConstraints {}

  • 组成变体实际内容的已发布文件的位置withFiles { }

整个组件的一些属性也可以更改:

  • The 组件级属性, currently the only meaningful attribute there is org.gradle.status

  • 在版本选择期间影响org.gradle.status属性解释的状态方案

  • 通过虚拟平台进行版本对齐belongsTo属性

根据模块元数据的格式,它以不同的方式映射到元数据的以变量为中心的表示形式:

  • 如果模块具有Gradle模块元数据,则规则所基于的数据结构与您在模块的.module文件中找到的数据结构非常相似.

  • 如果仅使用.pom元数据发布该模块, .pom导出许多固定的变体,如POM文件到变体映射部分中所述.

  • 如果仅使用ivy.xml文件发布了模块,则可以访问文件中定义的Ivy配置 ,而不是变体. 它们的依赖性,依赖性约束和文件可以修改. 另外,如果需要,可以使用addVariant(name, baseVariantOrConfiguration) { } API从Ivy配置派生变体(例如,可以使用此方法定义Java库插件的编译运行时变体 ).

When to use Component Metadata Rules?

通常,如果您考虑使用组件元数据规则来调整某个模块的元数据,则应首先检查该模块是使用Gradle模块元数据( .module文件)还是仅使用传统元数据( .pomivy.xml )发布的.

如果使用Gradle Module Metadata发布了模块,则尽管有时仍然存在某些明显错误的情况,但元数据可能已完成. 对于这些模块,只有在明确确定元数据本身存在问题的情况下,才应使用组件元数据规则. 如果您对依赖项解决结果有疑问,则应首先检查是否可以通过声明带有丰富版本的依赖项约束来解决问题. 特别是,如果您正在开发要发布的库,则应记住,与组件元数据规则相反,依赖关系约束是作为您自己的库的元数据的一部分发布的. 因此,在具有依赖关系约束的情况下,您可以自动与使用者共享依赖关系解决方案的解决方案,而组件元数据规则仅应用于自己的内部版本.

如果模块是使用传统的元数据发布的(仅.pomivy.xml ,没有.module文件),则元数据很可能不完整,因为这些格式不支持诸如变体或依赖项约束之类的功能. 仍然,从概念上讲,这样的模块可以包含不同的变体,或者可能具有它们被忽略(或错误地定义为依赖项)的依赖项约束. 在接下来的部分中,我们将探讨许多现有的oss模块,这些模块具有不完整的元数据以及添加缺失的元数据信息的规则.

根据经验,您应该考虑所编写的规则是否也适用于构建环境. 也就是说,如果将规则应用到任何其他使用受其影响的模块的构建中,该规则是否仍会产生正确且有用的结果?

Fixing wrong dependency details

让我们以Maven Central上Jaxen XPath Engine的发布为例. 版本1.1.3的pom在编译范围内声明了一些依赖关系,这些依赖关系实际上并不是编译所必需的. 这些已在1.1.4 pom中删除. 假设由于某种原因需要使用1.1.3,我们可以使用以下规则来修复元数据:

示例2.删除Jaxen元数据的未使用依赖项的规则
build.gradle
class JaxenDependenciesRule implements ComponentMetadataRule {
    void execute(ComponentMetadataContext context) {
        context.details.allVariants {
            withDependencies {
                removeAll { it.group in ["dom4j", "jdom", "xerces",  "maven-plugins", "xml-apis", "xom"] }
            }
        }
    }
}
build.gradle.kts
open 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") }
            }
        }
    }
}

withDependencies块中,您可以访问依赖关系的完整列表,并且可以使用Java集合界面上可用的所有方法来检查和修改该列表. 此外,还有add(notation, configureAction)方法接受通常的表示法,类似于在构建脚本中声明依赖项 . 可以以相同的方式在withDependencyConstraints块中检查和修改依赖性约束.

如果仔细研究Jaxen 1.1.4 pom,我们会发现dom4jjdomxerces依赖项仍然存在,但标记为optional . podle中的可选依赖项不会由Gradle或Maven自动处理. 原因是它们指示Jaxen库提供了一些可选的功能变体 ,这些变体需要这些依赖项中的一个或多个,但是缺少这些功能是什么以及哪个依赖项属于哪个的信息. 此类信息无法在pom文件中表示,而是通过变体和功能在Gradle模块元数据中表示. 因此,我们也可以在规则中添加此信息.

例子3.将可选功能添加到Jaxen元数据的规则
build.gradle
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")
            }
        }
    }
}
build.gradle.kts
open 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")
            }
        }
    }
}

在这里,我们首先使用addVariant(name, baseVariant)方法创建一个附加变体,通过定义新功能jaxen-dom4j来表示Jaxen的可选dom4j集成功能,我们将其识别为特征变体 . 这类似于在构建脚本中定义可选功能变体 . 然后,我们使用add方法之一来添加依赖项,以定义此可选功能所需的依赖项.

然后,在构建脚本中,我们可以将依赖项添加到可选功能中 ,Gradle将使用丰富的元数据来发现正确的传递性依赖项.

例子4.为Jaxen元数据应用和利用规则
build.gradle
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") }
    }
}
build.gradle.kts
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") }
    }
}

Making variants published as classified jars explicit

在上一个示例中,所有变体("主要变体"和可选功能)都打包在一个jar文件中,但通常会将某些变体发布为单独的文件. 特别是,当变体互斥时,即它们不是功能变体,而是提供替代选择的不同变体. 所有基于pom的库都已经存在的一个示例是运行时编译变体,其中Gradle只能根据手头的任务选择一个. 在Java生态系统中经常发现的此类替代方法中的另一个是针对不同Java版本的jar.

例如,我们看一下在Maven Central上发布的异步编程库Quasar的0.7.9版本. 如果检查目录列表, quasar-core-0.7.9-jdk8.jar发现除了quasar-core-0.7.9.jar之外,还发布了quasar-core-0.7.9-jdk8.jar quasar-core-0.7.9.jar . 在Maven存储库中,使用分类器 (此处为jdk8 )发布其他jar是很常见的做法. 尽管Maven和Gradle都允许您通过分类器引用此类jar,但它们在元数据中根本没有提及. 因此,不存在这些罐子存在的信息,以及此类罐子所代表的变体之间是否存在其他差异(如不同的依赖项).

在Gradle模块元数据中,将显示此变体信息,对于已经发布的Quasar库,我们可以使用以下规则添加它:

示例5.将Jdk 8变体添加到类星体元数据的规则
build.gradle
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)
                }
            }
        }
    }
}
build.gradle.kts
open 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)
                }
            }
        }
    }
}

在这种情况下,很明显,分类器代表目标Java版本,这是已知的Java生态系统属性 . 因为我们还需要Java 8的编译运行时 ,所以我们创建了两个新的变体,但将现有的编译运行时变体用作base . 这样,所有其他Java生态系统属性已经正确设置,并且所有依赖项都被继承. 然后,将两个变体的TARGET_JVM_VERSION_ATTRIBUTE都设置为8 ,使用removeAllFiles()从新变体中删除任何现有文件,并使用addFile()添加jdk8 jar文件. 需要removeAllFiles() ,因为从相应的基本变量中复制了对主jar quasar-core-0.7.5.jar的引用.

我们还将针对Java 7 attribute(TARGET_JVM_VERSION_ATTRIBUTE, 7)的信息,丰富了现有的编译运行时变体.

现在,我们可以为构建脚本中的所有依赖于编译类路径的依赖项请求Java 8版本,并且Gradle将自动为每个库选择最合适的变体. 在Quasar的情况下,这将是jdk8Compile变体,它暴露了quasar-core-0.7.9-jdk8.jar .

例子6.为类星元数据应用和利用规则
build.gradle
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")
}
build.gradle.kts
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")
}

Making variants encoded in versions explicit

为同一个库发布多个替代项的另一种解决方案是使用流行的Guava库完成的版本控制模式. 在这里,通过将分类器(而不是jar工件)附加到版本,每个新版本都会发布两次. 以Guava 28为例,我们可以在Maven Central上找到28.0-jre (Java 8)和28.0-android (Java 6)版本. 仅在使用pom元数据时使用此模式的优势在于,可以通过版本发现这两种变体. 缺点是没有信息表明不同版本的后缀在语义上意味着什么. 因此,在发生冲突的情况下,Gradle在比较版本字符串时只会选择最高版本.

将其转换为适当的变体会比较棘手,因为Gradle首先选择模块的版本,然后选择最合适的变体. 因此,不直接支持将变体编码为版本的概念. 但是,由于两个变体始终一起发布,因此我们可以假设文件实际上位于同一存储库中. 由于它们是按照Maven存储库约定发布的,因此如果知道模块名称和版本,我们就知道每个文件的位置. 我们可以编写以下规则:

Example 7. Rule to add Jdk 6 and Jdk 8 variants to Guava metadata
build.gradle
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")
                    }
                }
            }
        }
    }
}
build.gradle.kts
open 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")
                    }
                }
            }
        }
    }
}

Similar to the previous example, we add runtime and compile variants for both Java versions. In the withFile block however, we now also specify a relative path for the corresponding jar file which allows Gradle to find the file no matter if it has selected a -jre or -android version. The path is always relative to the location of the metadata (in this case pom) file of the selection module version. So with this rules, both Guava 28 "versions" carry both the jdk6 and jdk8 variants. So it does not matter to which one Gradle resolves. The variant, and with it the correct jar file, is determined based on the requested TARGET_JVM_VERSION_ATTRIBUTE value.

例子8.为番石榴元数据应用和利用规则
build.gradle
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+")
}
build.gradle.kts
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+")
}

Adding variants for native jars

带有分类器的Jar还用于将库中存在多个替代方案(例如本机代码)的部分与主要工件分离. 例如,这是由轻量级Java游戏库(LWGJ)完成的,该库将一些特定于平台的jar发布到Maven Central,在运行时,除了主jar之外,始终都需要一个. 由于没有通过元数据关联多个工件的概念,因此无法在pom元数据中传达此信息. 在Gradle模块元数据中,每个变体可以具有任意多个文件,我们可以通过编写以下规则来利用它:

例子9.将本地运行时变体添加到LWGJ元数据的规则
build.gradle
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 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")
                }
            }
        }
    }
}
build.gradle.kts
open 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")
    )

    @Inject open fun getObjects(): ObjectFactory = throw UnsupportedOperationException()

    override fun execute(context: ComponentMetadataContext) {
        context.details.withVariant("runtime") {
            attributes {
                attributes.attribute(OperatingSystemFamily.OPERATING_SYSTEM_ATTRIBUTE, getObjects().named("none"))
                attributes.attribute(MachineArchitecture.ARCHITECTURE_ATTRIBUTE, getObjects().named("none"))
            }
        }
        nativeVariants.forEach { variantDefinition ->
            context.details.addVariant("${variantDefinition.classifier}-runtime", "runtime") {
                attributes {
                    attributes.attribute(OperatingSystemFamily.OPERATING_SYSTEM_ATTRIBUTE, getObjects().named(variantDefinition.os))
                    attributes.attribute(MachineArchitecture.ARCHITECTURE_ATTRIBUTE, getObjects().named(variantDefinition.arch))
                }
                withFiles {
                    addFile("${context.details.id.name}-${context.details.id.version}-${variantDefinition.classifier}.jar")
                }
            }
        }
    }
}

此规则与上面的Quasar库示例非常相似. 仅这次,我们添加了五个不同的运行时变体,而对于编译变体则无需更改. 运行时变体全部基于现有的运行时变体,并且我们不更改任何现有信息. 所有Java生态系统属性,依赖项和主jar文件都属于每个运行时变体的一部分. 我们仅设置其他属性OPERATING_SYSTEM_ATTRIBUTEARCHITECTURE_ATTRIBUTE ,这些属性定义为Gradle 本机支持的一部分 . 然后,我们添加相应的本机jar文件,以便每个运行时变体现在都包含两个文件:主jar和本机jar.

在构建脚本中,我们现在可以请求特定的变体,并且如果需要更多信息来做出决定,则Gradle会因选择错误而失败.

例子10.为LWGJ元数据应用和利用规则
build.gradle
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")
}
build.gradle.kts
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")
}
Gradle无法选择变体,因为需要选择机器架构
> Could not resolve all files for configuration ':runtimeClasspath'.
   > Could not resolve org.lwjgl:lwjgl:3.2.3.
     Required by:
         project :
      > Cannot choose between the following variants of org.lwjgl:lwjgl:3.2.3:
          - natives-windows-runtime
          - natives-windows-x86-runtime

Making different flavors of a library available through capabilities

由于很难将可选功能变量建模为带有pom元数据的单独jar,因此库有时会组成具有不同功能集的不同jar. 也就是说,不是从不同的功能变体中组合库的风格,而是选择一种预先组合的变体(在一个jar中提供所有内容). 这样的库之一就是在Maven Central上发布的著名的依赖项注入框架Guice,它提供了完整的风格(主jar)和简化的变体,而没有面向方面的编程支持( guice-4.2.2-no_aop.jar ). pom元数据中未提及带有分类器的第二个变体. 遵循以下规则,我们将基于该文件创建编译和运行时变体,并通过名为com.google.inject:guice-no_aop的功能使其可选.

例子11.将no_aop特征变量添加到Guice元数据的规则
build.gradle
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" }
                }
            }
        }
    }
}
build.gradle.kts
open 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" }
                }
            }
        }
    }
}

新的变体还依赖于标准化的aop接口库aopalliance:aopalliance ,因为这些变体显然不需aopalliance:aopalliance . 同样,这是无法在pom元数据中表达的信息. 现在,我们可以选择guice-no_aop变体,并将获得正确的jar文件正确的依赖关系.

例子12.为Guice元数据应用和利用规则
build.gradle
dependencies {
    components {
        withModule("com.google.inject:guice", GuiceRule)
    }
    implementation("com.google.inject:guice:4.2.2") {
        capabilities { requireCapability("com.google.inject:guice-no_aop") }
    }
}
build.gradle.kts
dependencies {
    components {
        withModule<GuiceRule>("com.google.inject:guice")
    }
    implementation("com.google.inject:guice:4.2.2") {
        capabilities { requireCapability("com.google.inject:guice-no_aop") }
    }
}

Adding missing capabilities to detect conflicts

功能的另一种用法是表示两个不同的模块(例如log4jlog4j-over-slf4j )提供同一事物的替代实现. 通过声明两者均提供相同的功能,Gradle在依赖图中仅接受其中之一. 此示例以及如何用组件元数据规则解决该示例,在功能建模部分中进行了详细说明.

Making Ivy modules variant-aware

具有常春藤元数据的模块默认情况下不具有变体. 但是,由于addVariant(name, baseVariantOrConfiguration)接受任何已发布为基础的Ivy配置,因此可以将Ivy配置映射到变量. 例如,这可用于定义运行时和编译变体. 相应规则的示例可以在此处找到. 常春藤配置的常春藤详细信息(例如,依赖关系和文件)也可以使用withVariant(configurationName) API进行修改. 但是,修改Ivy配置上的属性或功能无效.

对于非常特定于Ivy的用例,组件元数据规则API还提供对仅在Ivy元数据中找到的其他详细信息的访问. 这些可以通过IvyModuleDescriptor接口获得,并且可以使用ComponentMetadataContext上的getDescriptor(IvyModuleDescriptor)进行访问. (请注意,对于其他类型的元数据,此方法返回null.)

例子13.常春藤组件元数据规则
build.gradle
class IvyComponentRule implements ComponentMetadataRule {
    void execute(ComponentMetadataContext context) {
        val descriptor = context.getDescriptor(IvyModuleDescriptor)
        if (descriptor != null && descriptor.branch == "testing") {
            context.details.status = "rc"
        }
    }
}
build.gradle.kts
open class IvyComponentRule : ComponentMetadataRule {
    override fun execute(context: ComponentMetadataContext) {
        val descriptor = context.getDescriptor(IvyModuleDescriptor::class)
        if (descriptor != null && descriptor.branch == "testing") {
            context.details.status = "rc"
        }
    }
}

Modifying metadata on the component level for alignment

尽管以上所有示例都对组件的变体进行了修改,但是也可以对组件本身的元数据进行有限的修改. 此信息可能会影响相关性解析期间模块的版本选择过程,该过程在选择组件的一个或多个变体之前执行.

组件上可用的第一个API是belongsTo()用于创建虚拟平台,以对齐没有Gradle Module Metadata的多个模块的版本. 在对齐未随Gradle发行的模块版本的部分中对此进行了详细说明.

Modifying metadata on the component level for version selection based on status

Gradle和Gradle模块元数据还允许在整个组件上设置属性,而不是单个变量. 这些属性中的每一个都具有特殊的语义,因为它们影响版本选择,而版本选择是变量选择之前完成的. 尽管变体选择可以处理任何自定义属性 ,但是版本选择仅考虑实现了特定语义的属性. 目前,这里唯一有意义的属性是org.gradle.status . 因此,建议仅在组件级别修改此属性(如果有). setStatus(value)可以使用专用的API setStatus(value) . 要使用withAllVariants { attributes {} }修改组件的所有变体的另一个属性,应withAllVariants { attributes {} } .

解析最新版本选择器时,将考虑模块的状态. 具体来说, latest.someStatus将解析为状态为someStatus或更成熟的状态的最高模块版本. 例如, latest.integration将选择最高模块版本,而不考虑其状态(因为integration是最不成熟的状态,如下所述),而latest.release将选择状态为release的最高模块版本.

通过setStatusScheme(valueList) API更改模块的状态方案,可以影响状态的解释. 这个概念模拟了模块在不同出版物中随着时间推移而转变的不同成熟度. 默认状态方案(从最低状态到最成熟状态排列)为integrationmilestonerelease . 必须将org.gradle.status属性设置为组件状态方案中的值之一. 因此,每个组件始终具有从元数据确定的状态,如下所示:

  • Gradle模块元数据:为组件上的org.gradle.status属性发布的值

  • 常春藤元数据:在ivy.xml中定义的status ,如果缺少则默认为integration

  • Pom元数据: integration具有SNAPSHOT版本的模块,为所有其他版本release

下面的示例演示了基于在适用于所有模块的组件元数据规则中声明的自定义状态方案的latest选择器:

例子14.定制状态方案
build.gradle
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")
}
build.gradle.kts
open 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")
}

与默认方案相比,该规则会插入新的状态rc并用nightly替换integration . 具有状态integration现有模块被映射为nightly .