在 Gradle 中,依赖解析通常从消费者生产者的角度来考虑。消费者声明依赖并执行依赖解析,而生产者通过暴露变体来满足这些依赖。

Gradle 的解析引擎遵循一种动态的依赖解析方法,称为变体感知解析,其中消费者使用属性定义需求,这些属性与生产者声明的属性相匹配。

变体感知解析允许 Gradle 自动从生产者那里选择正确的变体,而无需消费者显式指定要使用哪个变体。

例如,如果您正在使用不同的架构(如 arm64i386),Gradle 可以为每个架构选择库(myLib)的适当版本

  1. 生产者 myLib 暴露带有特定属性的变体 (arm64Elements, i386Elements)(例如,ArchType.ARM64, ArchType.I386)。

  2. 消费者 myApp 在其可解析配置 (runtimeClasspath) 中指定所需的属性(例如,ArchType.ARM64)。

  3. 如果消费者 myApp 需要 arm64 架构的依赖,Gradle 将自动从 myLib 生产者中选择 arm64Elements 变体,并使用其相应的工件

代码示例

考虑一个 Java 库,您在其中创建一个名为 instrumentedJars 的新变体,并希望确保它被选择用于测试

  1. 生产者项目:创建一个专门的 instrumentedJars 变体,并用特定属性标记。

  2. 消费者项目:配置为请求用于测试的 instrumented-jar 属性

让我们看一下生产者和消费者的构建文件。

生产者端

1. 创建 instrumented JAR:

我们的 Java 库有一个名为 instrumentedJar 的 task,它生成一个 JAR 文件。我们期望其他项目使用此 JAR 文件。

producer/build.gradle.kts
val instrumentedJar = tasks.register("instrumentedJar", Jar::class) {
    archiveClassifier = "instrumented"
}
producer/build.gradle
def instrumentedJar = tasks.register("instrumentedJar", Jar) {
    archiveClassifier = "instrumented"
}

2. 创建自定义传出配置:

我们希望在执行测试时使用 instrumented 类,因此我们需要在我们的变体上定义适当的属性。我们创建一个名为 instrumentedJars 的新配置。此配置

  • 可以被其他项目使用。

  • 无法被解析(即,它旨在用作输出,而不是输入)。

  • 具有特定的属性,包括设置为 "instrumented-jar" 的 LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE,这解释了变体包含的内容。

producer/build.gradle.kts
val instrumentedJars by configurations.creating {
    isCanBeConsumed = true
    isCanBeResolved = false
    attributes {
        attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.LIBRARY))
        attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_RUNTIME))
        attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.EXTERNAL))
        attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, JavaVersion.current().majorVersion.toInt())
        attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named("instrumented-jar"))
    }
}
producer/build.gradle
configurations {
    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(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling, Bundling.EXTERNAL))
            attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, JavaVersion.current().majorVersion.toInteger())
            attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements, 'instrumented-jar'))
        }
    }
}

3. 附加工件:

instrumentedJar task 的输出作为工件添加到 instrumentedJars 配置中。当此变体包含在依赖关系图中时,此工件将在工件解析期间被解析。

producer/build.gradle.kts
artifacts {
    add("instrumentedJars", instrumentedJar)
}
producer/build.gradle
artifacts {
    instrumentedJars(instrumentedJar)
}

我们在这里所做的是,我们添加了一个新的变体,它可以在运行时使用,但包含 instrumented 类而不是普通类。但是,现在这意味着对于运行时,消费者必须在两个变体之间进行选择

  1. runtimeElementsjava-library 插件提供的常规变体

  2. instrumentedJars,我们创建的变体

消费者端

1. 添加依赖:

首先,在消费者端,像任何其他项目一样,我们将 Java 库定义为依赖

consumer/build.gradle.kts
dependencies {
    testImplementation("junit:junit:4.13")
    testImplementation(project(":producer"))
}
consumer/build.gradle
dependencies {
    testImplementation 'junit:junit:4.13'
    testImplementation project(':producer')
}

此时,Gradle 仍将为您的依赖选择默认的 runtimeElements 变体。这是因为 testRuntimeClasspath 配置正在请求具有 jar 库元素属性工件,而生产者定义了具有不同属性instrumentedJars 变体

2. 调整请求的属性:

修改 testRuntimeClasspath 配置以请求依赖的 "instrumented-jar" 版本。这意味着当 Gradle 为此配置解析依赖时,它将优先选择标记为 "instrumented" 的 JAR 文件

consumer/build.gradle.kts
configurations {
    testRuntimeClasspath {
        attributes {
            attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements::class.java, "instrumented-jar"))
        }
    }
}
consumer/build.gradle
configurations {
    testRuntimeClasspath {
        attributes {
            attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements, 'instrumented-jar'))
        }
    }
}

通过遵循这些步骤,Gradle 将根据配置和属性智能地选择正确的变体,同时还会处理专用变体不可用的情况。