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 个属性的变体

  1. org.gradle.category - 此变体表示一个库

  2. org.gradle.dependency.bundling - 此变体的依赖项以 jar 形式存在(例如,它们没有被重新打包到 jar 文件中)

  3. org.gradle.jvm.version - 此库支持的最低 Java 版本是 Java 11

  4. org.gradle.libraryelements - 此变体包含 jar 文件中的所有元素(类和资源)

  5. org.gradle.usage - 此变体是一个 Java 运行时变体,因此适用于 Java 编译器,也适用于运行时

生产者项目定义了一个制品(例如,一个 instrumented JAR),其他项目可以基于属性来消费它。如果希望在执行测试时使用 instrumented 类来替代现有变体,我们需要为这个变体附加类似的属性。实际上,我们关心的属性是 org.gradle.libraryelements,它解释了变体包含什么。

首先,以下任务定义了一个带有 instrumented 分类器(例如,producer-instrumented.jar)的自定义 JAR 制品

producer/build.gradle.kts
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
}
producer/build.gradle
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

这允许其他项目(消费者)根据属性发现和请求此特定制品

producer/build.gradle.kts
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)
}
producer/build.gradle
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

consumer/build.gradle.kts
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"))
        }
    }
}
consumer/build.gradle
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"))
        }
    }
}

然后,添加一个对生产者的依赖项

consumer/build.gradle.kts
dependencies {
    add("instrumentedRuntime", project(":producer"))
}
consumer/build.gradle
dependencies {
    add("instrumentedRuntime", project(":producer"))
}

最后,我们在任务中使用制品变体。此任务使用从生产者解析到的 instrumented JAR 来运行应用程序

consumer/build.gradle.kts
tasks.register<JavaExec>("runWithInstrumentation") {
    classpath = configurations["instrumentedRuntime"]
    mainClass.set("com.example.Main")
}
consumer/build.gradle
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 是可以接受的。

这是通过使用一个 兼容性规则 来完成的

consumer/build.gradle.kts
abstract class InstrumentedJarsRule: AttributeCompatibilityRule<LibraryElements> {
    override fun execute(details: CompatibilityCheckDetails<LibraryElements>) = details.run {
        if (consumerValue?.name == "instrumented-jar" && producerValue?.name == "jar") {
            compatible()
        }
    }
}
consumer/build.gradle
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 上声明它

consumer/build.gradle.kts
dependencies {
    attributesSchema {
        attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE) {
            compatibilityRules.add(InstrumentedJarsRule::class.java)
        }
    }
}
consumer/build.gradle
dependencies {
    attributesSchema {
        attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE) {
            compatibilityRules.add(InstrumentedJarsRule)
        }
    }
}

步骤 4:疑难解答

如果消费者未能解析制品,请检查以下内容:

  • 消费者中的属性与生产者中的属性是否匹配。

  • 生产者项目是否正确声明了制品。

  • 是否没有具有不同属性的冲突配置。

总结

Variant-aware 共享实现了项目之间清晰灵活的制品共享。它避免了硬编码的任务依赖项,并提高了构建的可维护性。