使用 Gradle 在项目之间共享制品的方法
Gradle 提供了一种使用 variant-aware 共享 在项目之间灵活且可维护地共享制品(artifacts)的机制。这使得消费项目可以根据定义的 attributes(属性)选择适当的制品,从而确保兼容性和正确性。
为什么使用 Variant-Aware 共享?
与简单的项目依赖项不同,variant-aware 共享 提供了
-
更好的封装性,仅暴露预期的制品。
-
细粒度的控制,用于制品选择。
-
支持同一制品的多种变体(例如,debug 构建 vs. release 构建)。
步骤 1:配置生产者项目
首先,你应该通过运行 outgoingVariants
任务来检查生产者项目的现有变体
$ ./gradlew :producer:outgoingVariants --variant runtimeElements
> Task :producer:outgoingVariants
--------------------------------------------------
Variant runtimeElements
--------------------------------------------------
Runtime elements for the 'main' feature.
Capabilities
- variant-sharing-example:producer:unspecified (default capability)
Attributes
- org.gradle.category = library
- org.gradle.dependency.bundling = external
- org.gradle.jvm.version = 17
- org.gradle.libraryelements = jar
- org.gradle.usage = java-runtime
Artifacts
- build/libs/producer.jar (artifactType = jar)
这告诉我们,这个 Java Library 插件会生成带有 5 个属性的变体
-
org.gradle.category
- 此变体表示一个库 -
org.gradle.dependency.bundling
- 此变体的依赖项以 jar 形式存在(例如,它们没有被重新打包到 jar 文件中) -
org.gradle.jvm.version
- 此库支持的最低 Java 版本是 Java 11 -
org.gradle.libraryelements
- 此变体包含 jar 文件中的所有元素(类和资源) -
org.gradle.usage
- 此变体是一个 Java 运行时变体,因此适用于 Java 编译器,也适用于运行时
生产者项目定义了一个制品(例如,一个 instrumented JAR),其他项目可以基于属性来消费它。如果希望在执行测试时使用 instrumented 类来替代现有变体,我们需要为这个变体附加类似的属性。实际上,我们关心的属性是 org.gradle.libraryelements
,它解释了变体包含什么。
首先,以下任务定义了一个带有 instrumented 分类器(例如,producer-instrumented.jar
)的自定义 JAR 制品
plugins {
id("java-library")
}
val instrumentedJar by tasks.registering(Jar::class) {
archiveClassifier.set("instrumented")
from(sourceSets.main.get().output)
// Additional instrumentation processing could go here
}
plugins {
id("java-library")
}
def instrumentedJar = tasks.register("instrumentedJar", Jar) {
archiveClassifier.set("instrumented")
from(sourceSets.main.output)
// Additional instrumentation processing could go here
}
其次,创建一个配置,该配置
-
为消费提供一个制品变体。
-
此变体是一个
runtime-library
制品,其元素类型为instrumented-jar
。
这允许其他项目(消费者)根据属性发现和请求此特定制品
configurations {
create("instrumentedJars") {
isCanBeConsumed = true
isCanBeResolved = false
attributes {
attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.LIBRARY))
attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_RUNTIME))
attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named("instrumented-jar"))
}
}
}
artifacts {
add("instrumentedJars", instrumentedJar)
}
configurations {
create("instrumentedJars") {
canBeConsumed = true
canBeResolved = false
attributes {
attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category, Category.LIBRARY))
attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, Usage.JAVA_RUNTIME))
attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements, "instrumented-jar"))
}
}
}
artifacts {
add("instrumentedJars", instrumentedJar)
}
此配置确保只暴露正确的制品,并防止意外依赖内部任务。
步骤 2:配置消费者项目
消费者项目通过定义匹配的属性来请求 instrumented JAR。
首先,消费者显式请求一个运行时用途的制品,其变体标记为 instrumented-jar
plugins {
id("application")
}
configurations {
create("instrumentedRuntime") {
isCanBeConsumed = false
isCanBeResolved = true
attributes {
attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_RUNTIME))
attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named("instrumented-jar"))
}
}
}
plugins {
id("application")
}
configurations {
create("instrumentedRuntime") {
canBeConsumed = false
canBeResolved = true
attributes {
attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, Usage.JAVA_RUNTIME))
attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements, "instrumented-jar"))
}
}
}
然后,添加一个对生产者的依赖项
dependencies {
add("instrumentedRuntime", project(":producer"))
}
dependencies {
add("instrumentedRuntime", project(":producer"))
}
最后,我们在任务中使用制品变体。此任务使用从生产者解析到的 instrumented JAR 来运行应用程序
tasks.register<JavaExec>("runWithInstrumentation") {
classpath = configurations["instrumentedRuntime"]
mainClass.set("com.example.Main")
}
tasks.register("runWithInstrumentation",JavaExec) {
classpath = configurations["instrumentedRuntime"]
mainClass.set("com.example.Main")
}
此设置确保消费者解析到正确的变体,而无需了解生产者的实现细节。
检查一切是否正常运行的一个好方法是在消费者侧运行 resolvableConfigurations
任务
$ ./gradlew consumer:resolvableConfigurations --configuration instrumentedRuntime
> Task :consumer:resolvableConfigurations
--------------------------------------------------
Configuration instrumentedRuntime
--------------------------------------------------
Attributes
- org.gradle.libraryelements = instrumented-jar
- org.gradle.usage = java-runtime
步骤 3:设置默认值
为了确保 Gradle 在解析没有 instrumented 变体的依赖项时不会失败,我们需要定义一个回退。没有这个回退,Gradle 会对不提供 instrumented 类的依赖项缺少变体而报错。回退明确告诉 Gradle,当 instrumented 变体不可用时,可以使用常规 JAR 是可以接受的。
这是通过使用一个 兼容性规则 来完成的
abstract class InstrumentedJarsRule: AttributeCompatibilityRule<LibraryElements> {
override fun execute(details: CompatibilityCheckDetails<LibraryElements>) = details.run {
if (consumerValue?.name == "instrumented-jar" && producerValue?.name == "jar") {
compatible()
}
}
}
abstract class InstrumentedJarsRule implements AttributeCompatibilityRule<LibraryElements> {
@Override
void execute(CompatibilityCheckDetails<LibraryElements> details) {
if (details.consumerValue.name == 'instrumented-jar' && details.producerValue.name == 'jar') {
details.compatible()
}
}
}
我们在属性 schema 上声明它
dependencies {
attributesSchema {
attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE) {
compatibilityRules.add(InstrumentedJarsRule::class.java)
}
}
}
dependencies {
attributesSchema {
attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE) {
compatibilityRules.add(InstrumentedJarsRule)
}
}
}
步骤 4:疑难解答
如果消费者未能解析制品,请检查以下内容:
-
消费者中的属性与生产者中的属性是否匹配。
-
生产者项目是否正确声明了制品。
-
是否没有具有不同属性的冲突配置。
总结
Variant-aware 共享实现了项目之间清晰灵活的制品共享。它避免了硬编码的任务依赖项,并提高了构建的可维护性。