在构建依赖图之后,Gradle 可以在已解析的图上执行构件解析

Gradle API 可用于影响构件选择的过程 — 将图映射到一组构件。

然后,Gradle 可以将构件选择的结果公开为 ArtifactCollection。更常见的是,结果以 FileCollection 的形式公开,这是一个扁平的文件列表。

构件选择

构件选择在依赖图上逐节点操作。图中的每个节点可能公开多组构件,但只能选择其中一组。例如,Java 插件的 runtimeElements 变体公开了一个 jarclassesresources 构件集。这三个构件集代表相同的可分发物,但形式不同。

对于图中每个节点(变体),Gradle 对该节点公开的每组构件执行属性匹配,以确定最佳构件集。 如果没有构件集与请求的属性匹配,Gradle 将尝试构建构件转换链以满足请求。

有关属性匹配过程的更多详细信息,请参阅属性匹配部分。

隐式构件选择

默认情况下,用于构件选择的属性与图解析期间用于变体选择的属性相同。 这些属性由 Configuration#getAttributes() 属性指定。

要使用这些默认属性执行构件选择(以及隐式地,图解析),请使用 FileCollectionArtifactCollection API。

也可以从配置的 ResolvedConfigurationLenientConfigurationResolvedArtifactResolvedDependency API 访问文件。 但是,这些 API 处于维护模式,不建议在新开发中使用。 这些 API 使用默认属性执行构件选择。

解析文件

要解析文件,我们首先定义一个接受 ConfigurableFileCollection 作为输入的 task

build.gradle.kts
abstract class ResolveFiles : DefaultTask() {

    @get:InputFiles
    abstract val files: ConfigurableFileCollection

    @TaskAction
    fun print() {
        files.forEach {
            println(it.name)
        }
    }
}
build.gradle
abstract class ResolveFiles extends DefaultTask {

    @InputFiles
    abstract ConfigurableFileCollection getFiles()

    @TaskAction
    void print() {
        files.each {
            println(it.name)
        }
    }
}

然后,我们可以将可解析配置的文件连接到 task 的输入。 Configuration 直接实现了 FileCollection,可以直接连接。 或者,通过 Configuration#getIncoming() 进行连接是一种更显式的方法

build.gradle.kts
tasks.register<ResolveFiles>("resolveConfiguration") {
    files.from(configurations.runtimeClasspath)
}
tasks.register<ResolveFiles>("resolveIncomingFiles") {
    files.from(configurations.runtimeClasspath.map { it.incoming.files })
}
build.gradle
tasks.register("resolveConfiguration", ResolveFiles) {
    files.from(configurations.runtimeClasspath)
}
tasks.register("resolveIncomingFiles", ResolveFiles) {
    files.from(configurations.runtimeClasspath.incoming.files)
}

运行这两个 task,我们可以看到输出是相同的

> Task :resolveConfiguration
junit-platform-commons-1.11.0.jar
junit-jupiter-api-5.11.0.jar
opentest4j-1.3.0.jar

> Task :resolveIncomingFiles
junit-platform-commons-1.11.0.jar
junit-jupiter-api-5.11.0.jar
opentest4j-1.3.0.jar

解析构件

我们可以消费构件,其中包含文件和元数据,而不是直接从隐式构件选择过程中消费文件。

此过程稍微复杂一些,因为为了保持配置缓存兼容性,我们需要将 ResolvedArtifactResult 的字段拆分为两个 task 输入

build.gradle.kts
data class ArtifactDetails(
    val id: ComponentArtifactIdentifier,
    val variant: ResolvedVariantResult
)

abstract class ResolveArtifacts : DefaultTask() {

    @get:Input
    abstract val details: ListProperty<ArtifactDetails>

    @get:InputFiles
    abstract val files: ListProperty<File>

    fun from(artifacts: Provider<Set<ResolvedArtifactResult>>) {
        details.set(artifacts.map {
            it.map { artifact -> ArtifactDetails(artifact.id, artifact.variant) }
        })
        files.set(artifacts.map {
            it.map { artifact -> artifact.file }
        })
    }

    @TaskAction
    fun print() {
        assert(details.get().size == files.get().size)
        details.get().zip(files.get()).forEach { (details, file) ->
            println("${details.variant.displayName}:${file.name}")
        }
    }
}
build.gradle
class ArtifactDetails {
    ComponentArtifactIdentifier id
    ResolvedVariantResult variant

    ArtifactDetails(ComponentArtifactIdentifier id, ResolvedVariantResult variant) {
        this.id = id
        this.variant = variant
    }
}

abstract class ResolveArtifacts extends DefaultTask {

    @Input
    abstract ListProperty<ArtifactDetails> getDetails()

    @InputFiles
    abstract ListProperty<File> getFiles()

    void from(Provider<Set<ResolvedArtifactResult>> artifacts) {
        details.set(artifacts.map {
            it.collect { artifact -> new ArtifactDetails(artifact.id, artifact.variant) }
        })
        files.set(artifacts.map {
            it.collect { artifact -> artifact.file }
        })
    }

    @TaskAction
    void print() {
        List<ArtifactDetails> allDetails = details.get()
        List<File> allFiles = files.get()

        assert allDetails.size() == allFiles.size()
        for (int i = 0; i < allDetails.size(); i++) {
            def details = allDetails.get(i)
            def file = allFiles.get(i)
            println("${details.variant.displayName}:${file.name}")
        }
    }
}

此 task 的初始化方式与文件解析 task 类似

build.gradle.kts
tasks.register<ResolveArtifacts>("resolveIncomingArtifacts") {
    from(configurations.runtimeClasspath.flatMap { it.incoming.artifacts.resolvedArtifacts })
}
build.gradle
tasks.register("resolveIncomingArtifacts", ResolveArtifacts) {
    from(configurations.runtimeClasspath.incoming.artifacts.resolvedArtifacts)
}

运行此 task,我们可以看到文件元数据包含在输出中

org.junit.platform:junit-platform-commons:1.11.0 variant runtimeElements:junit-platform-commons-1.11.0.jar
org.junit.jupiter:junit-jupiter-api:5.11.0 variant runtimeElements:junit-jupiter-api-5.11.0.jar
org.opentest4j:opentest4j:1.3.0 variant runtimeElements:opentest4j-1.3.0.jar

自定义构件选择

ArtifactView 在已解析的依赖图(即 ResolutionResult)之上运行,但允许您应用不同的属性

当您调用配置的 getFiles() 时,Gradle 会根据图解析期间使用的属性选择构件。 但是,ArtifactView 更加灵活。 它允许您使用自定义属性从图中解析构件。

ArtifactView 允许您

  1. 使用不同的属性查询构件:

    • 假设图解析了依赖项的 runtime 变体。 您可以使用 ArtifactView 从其 api 变体中提取构件,即使它们最初不是已解析图的一部分。

  2. 提取特定类型的构件:

    • 您可以通过指定类似 artifactType 的属性,仅请求 .jar 文件或特定构件类型(例如,sources、Javadoc)。

  3. 避免副作用:

    • 使用 ArtifactView 允许您提取构件,而无需更改底层依赖解析逻辑或配置状态。

在以下示例中,生产者项目创建一个具有以下变体及其属性的库

下一步: 了解构件视图 >>