在构建依赖图之后,Gradle 执行构件解析,将解析后的图映射到构建期间将下载的一组构件。
构件
构件是在构建过程中生成或使用的文件。构件通常是编译后的库、JAR 文件、AAR 文件、DLL 或 ZIP 等文件。
让我们看看 org.jetbrains.kotlin:kotlin-stdlib:1.8.10 的元数据,它展示了几个变体和构件
{
"variants": [
{
"name": "apiElements",
"attributes": {
"org.gradle.category": "library",
"org.gradle.dependency.bundling": "external",
"org.gradle.jvm.version": "8",
"org.gradle.libraryelements": "jar",
"org.gradle.usage": "java-api"
},
"files": [
{
"name": "kotlin-stdlib-1.8.10-public.jar"
},
{
"name": "kotlin-stdlib-1.8.10-private.jar"
}
]
},
{
"name": "runtimeElements",
"attributes": {
"org.gradle.category": "library",
"org.gradle.dependency.bundling": "external",
"org.gradle.jvm.version": "8",
"org.gradle.libraryelements": "jar",
"org.gradle.usage": "java-runtime"
},
"files": [
{
"name": "kotlin-stdlib-1.8.10.jar"
}
]
},
{
"name": "jdk7ApiElements",
"attributes": {
"org.gradle.usage": "java-api",
"org.gradle.jvm.version": "7"
},
"files": [
{
"name": "kotlin-stdlib-jdk7-1.8.10.jar"
}
]
},
{
"name": "jdk8ApiElements",
"attributes": {
"org.gradle.usage": "java-api",
"org.gradle.jvm.version": "8"
},
"files": [
{
"name": "kotlin-stdlib-jdk8-1.8.10.jar"
}
]
}
]
}
我们可以看到元数据中提供了许多构件
| 变体 | 构件 | 用途 |
|---|---|---|
|
|
标准 Kotlin 运行时(默认)。 |
|
|
为 Java 7 兼容性提供额外的 API。 |
|
|
为 Java 8 兼容性提供额外的 API。 |
|
|
Kotlin Multiplatform 的共享标准库。 |
通常,一旦选择了变体,其关联的构件就可以自动解析。但是,仍有特定原因导致构件选择发生在变体选择*之后*。
例如,如果 org.jetbrains.kotlin:kotlin-stdlib:1.8.10 的元数据看起来像这样
{
"variants": [
{
"name": "runtimeElements",
"attributes": {
"org.gradle.category": "library",
"org.gradle.dependency.bundling": "external",
"org.gradle.jvm.version": "8",
"org.gradle.libraryelements": "jar",
"org.gradle.usage": "java-runtime"
},
"files": [
{
"name": "kotlin-stdlib-1.8.10.jar"
}
]
},
{
"name": "runtimeElements",
"attributes": {
"org.gradle.category": "library",
"org.gradle.dependency.bundling": "external",
"org.gradle.jvm.version": "8",
"org.gradle.libraryelements": "classes",
"org.gradle.usage": "java-runtime"
},
"files": [
{
"name": "build/classes/java/main/"
}
]
},
{
"name": "runtimeElements",
"attributes": {
"org.gradle.category": "library",
"org.gradle.dependency.bundling": "external",
"org.gradle.jvm.version": "8",
"org.gradle.libraryelements": "resources",
"org.gradle.usage": "java-runtime"
},
"files": [
{
"name": "build/resources/main/"
}
]
}
]
}
Gradle 会如何知道要下载哪个文件?
构件集
构件集是属于模块的单个变体的一组构件。
单个变体可以包含多个构件集,每个构件集都有不同的用途。
让我们看看 org.jetbrains.kotlin:kotlin-stdlib:1.8.10 的示例
| 变体 | 集合 | 集合中的构件 |
|---|---|---|
|
1 |
jar → 打包的库 JAR ( |
2 |
tar → 打包的库 TAR ( |
|
|
1 |
jar → 打包的库 JAR ( |
2 |
classes → 编译后的 .class 文件 ( |
|
3 |
resources → 相关的资源文件 ( |
|
|
1 |
jar → 打包的库 JAR ( |
|
1 |
jar → 打包的库 JAR ( |
org.jetbrains.kotlin:kotlin-stdlib:1.8.10 的 apiElements 变体提供了两个构件集——jar 和 tar——每个都以不同的形式表示相同的可分发文件。org.jetbrains.kotlin:kotlin-stdlib:1.8.10 的 runtimeElements 变体提供了三个构件集——jar、classes 和 resources——每个都以不同的形式表示相同的可分发文件。
Gradle 现在必须遵循特定过程来确定最合适的构建构件集。
可用API
Gradle API 可用于影响构件选择过程。
Gradle 然后可以将构件选择的结果暴露为 ArtifactCollection。更常见的是,结果暴露为 FileCollection,它是一个文件扁平列表。
隐式构件选择
默认情况下,用于构件选择的属性与图解析期间用于变体选择的属性相同。这些属性由Configuration.getAttributes() 属性指定。
要使用这些默认属性执行构件选择(并隐式执行图解析),请使用 FileCollection 和 ArtifactCollection API。
文件也可以从配置的 ResolvedConfiguration、LenientConfiguration、ResolvedArtifact 和 ResolvedDependency API 访问。但是,这些 API 处于维护模式,不鼓励在新开发中使用。这些 API 使用默认属性执行构件选择。 |
解析文件
要解析文件,我们首先定义一个接受 ConfigurableFileCollection 作为输入的任务
abstract class ResolveFiles : DefaultTask() {
@get:InputFiles
abstract val files: ConfigurableFileCollection
@TaskAction
fun print() {
files.forEach {
println(it.name)
}
}
}
abstract class ResolveFiles extends DefaultTask {
@InputFiles
abstract ConfigurableFileCollection getFiles()
@TaskAction
void print() {
files.each {
println(it.name)
}
}
}
然后,我们可以将可解析配置的文件连接到任务的输入。Configuration 直接实现了 FileCollection,可以直接连接
tasks.register<ResolveFiles>("resolveConfiguration") {
files.from(configurations.runtimeClasspath)
}
tasks.register("resolveConfiguration", ResolveFiles) {
files.from(configurations.runtimeClasspath)
}
运行 resolveConfiguration 任务产生
> Task :resolveConfiguration junit-platform-commons-1.11.0.jar junit-jupiter-api-5.11.0.jar opentest4j-1.3.0.jar
解析构件
我们可以不直接从隐式构件选择过程中消费文件,而是消费包含文件和元数据的构件。
此过程稍微复杂一些,因为为了保持配置缓存兼容性,我们需要将 ResolvedArtifactResult 的字段拆分为两个任务输入
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}")
}
}
}
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}")
}
}
}
此任务的初始化类似于文件解析任务
tasks.register<ResolveArtifacts>("resolveIncomingArtifacts") {
from(configurations.runtimeClasspath.flatMap { it.incoming.artifacts.resolvedArtifacts })
}
tasks.register("resolveIncomingArtifacts", ResolveArtifacts) {
from(configurations.runtimeClasspath.incoming.artifacts.resolvedArtifacts)
}
运行此任务,我们可以看到文件元数据包含在输出中
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 API 在解析后的图之上操作,由 ../javadoc/org/gradle/api/artifacts/result/ResolutionResult.html[ResolutionResult] 定义。
API 提供了灵活的方式来访问解析后的构件
-
FileCollection- 文件扁平列表,这是处理解析后的构件最常用的方式。 -
ArtifactCollection- 提供对解析后的构件的元数据和文件的访问,允许更高级的构件处理。
当您调用配置的 getFiles() 时,Gradle 根据图解析期间使用的属性选择构件。但是,ArtifactView API 更灵活。它允许您使用自定义属性从图中解析构件。
构件视图
ArtifactView 允许您
-
使用不同的属性查询构件:
-
假设图解析了依赖项的
runtime变体。您可以使用ArtifactView从其api变体中提取构件,即使它们最初不属于解析后的图。
-
-
提取特定类型的构件:
-
您可以通过指定
artifactType等属性来仅请求.jar文件或特定构件类型(例如,源文件、Javadoc)。
-
-
避免副作用:
-
使用
ArtifactView允许您提取构件,而无需更改底层依赖项解析逻辑或配置状态。
-
在以下示例中,一个生产者项目创建了一个具有典型 Java 库变体(runtimeElements、apiElements)的库。我们还创建了一个名为 apiProductionElements 的自定义变体,其构件为 production.jar,属性为 org.gradle.category:production
plugins {
`java-library`
}
repositories {
mavenCentral()
}
dependencies {
// Define some dependencies here
}
// Define a task that produces a custom artifact
tasks.register<Jar>("createProductionArtifact") {
archiveBaseName.set("production")
from(sourceSets["main"].output)
destinationDirectory.set(file("build/libs"))
}
configurations {
// Define a custom configuration and extend from apiElements
create("apiProductionElements") {
extendsFrom(configurations.apiElements.get())
outgoing.artifacts.clear()
attributes {
attribute(Category.CATEGORY_ATTRIBUTE, objects.named("production"))
}
artifacts {
add("apiProductionElements", tasks.named("createProductionArtifact"))
}
}
}
plugins {
id 'java-library'
}
repositories {
mavenCentral()
}
dependencies {
// Define some dependencies here
}
// Define a task that produces a custom artifact
tasks.register('createProductionArtifact', Jar) {
archiveBaseName.set('production')
from(sourceSets.main.output)
destinationDirectory.set(file('build/libs'))
}
configurations {
// Define a custom configuration and extend from apiElements
apiProductionElements {
extendsFrom(configurations.apiElements)
attributes {
attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category, 'production'))
}
artifacts {
add('apiProductionElements', tasks.named('createProductionArtifact'))
}
}
}
我们可以使用一个名为 checkProducerAttributes 的自定义任务查看此库的可用变体及其相应的构件和属性。这是一个简略的输出,显示了此库的相关变体,以及它们相应的构件和属性
Configuration: apiElements Attributes: - org.gradle.category -> library - org.gradle.usage -> java-api - org.gradle.libraryelements -> jar - org.gradle.dependency.bundling -> external Artifacts: - producer.jar Configuration: apiProductionElements Attributes: - org.gradle.category -> production Artifacts: - production.jar Configuration: mainSourceElements Attributes: - org.gradle.dependency.bundling -> external - org.gradle.category -> verification - org.gradle.verificationtype -> main-sources Artifacts: - /src/main/resources - /src/main/java Configuration: runtimeElements Attributes: - org.gradle.category -> library - org.gradle.usage -> java-runtime - org.gradle.libraryelements -> jar - org.gradle.dependency.bundling -> external Artifacts: - producer.jar
Java 应用程序是此 Java 库的消费者
plugins {
application
}
repositories {
mavenCentral()
}
// Declare the dependency on the producer project
dependencies {
implementation(project(":producer")) // This references another subproject in the same build
}
plugins {
id 'application'
}
repositories {
mavenCentral()
}
// Declare the dependency on the producer project
dependencies {
implementation project(':producer')
}
默认情况下,应用程序作为消费者,在运行时将消费预期的变体。我们可以使用另一个名为 checkResolvedVariant 的自定义任务来验证这一点,该任务打印以下内容
RuntimeClasspath Configuration:
- Component: project :producer
- Variant: runtimeElements
- org.gradle.category -> library
- org.gradle.usage -> java-runtime
- org.gradle.libraryelements -> jar
- org.gradle.dependency.bundling -> external
- org.gradle.jvm.version -> 11
- Artifact: producer.jar
正如预期的那样,对于 runtimeClasspath,应用程序消费库的 runtimeElements 变体,该变体作为构件 producer.jar 可用。它使用属性 org.gradle.category:library 和 org.gradle.usage:java-runtime 来选择此变体。
现在,让我们创建一个 ArtifactView 来选择库提供的其他构件之一。我们通过使用带有属性 org.gradle.category:classes 的 ArtifactView 来实现这一点,这样我们就可以获得源文件而不是 jar 文件
tasks.register("artifactWithAttributeAndView") {
val configuration = configurations.runtimeClasspath
println("ArtifactView with attribute 'libraryelements = classes' for ${configuration.name}:")
val artifactView = configuration.get().incoming.artifactView {
attributes {
attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements::class, "classes"))
}
}
println("- Attributes:")
artifactView.attributes.keySet().forEach { attribute ->
val value = artifactView.attributes.getAttribute(attribute)
println(" - ${attribute.name} = ${value}")
}
artifactView.artifacts.artifactFiles.files.forEach { file ->
println("- Artifact: ${file.name}")
}
}
tasks.register("artifactWithAttributeAndVariantReselectionView") {
val configuration = configurations.runtimeClasspath
println("ArtifactView with attribute 'category = production' for ${configuration.name}:")
val artifactView = configuration.get().incoming.artifactView {
withVariantReselection()
attributes {
attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category::class, "production"))
}
}
println("- Attributes:")
artifactView.attributes.keySet().forEach { attribute ->
val value = artifactView.attributes.getAttribute(attribute)
println(" - ${attribute.name} = ${value}")
}
artifactView.artifacts.artifactFiles.files.forEach { file ->
println("- Artifact: ${file.name}")
}
}
tasks.register('artifactWithAttributeAndView') {
def configuration = configurations.runtimeClasspath
println "ArtifactView with attribute 'libraryelements = classes' for ${configuration.name}:"
def artifactView = configuration.incoming.artifactView {
attributes {
attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements, 'classes'))
}
}
println "- Attributes:"
artifactView.attributes.keySet().each { attribute ->
def value = artifactView.attributes.getAttribute(attribute)
println " - ${attribute.name} = ${value}"
}
artifactView.artifacts.artifactFiles.files.each { file ->
println "- Artifact: ${file.name}"
}
}
tasks.register('artifactWithAttributeAndVariantReselectionView') {
def configuration = configurations.runtimeClasspath
println "ArtifactView with attribute 'category = production' for ${configuration.name}:"
def artifactView = configuration.incoming.artifactView {
withVariantReselection()
attributes {
attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category,'production'))
}
}
println "- Attributes:"
artifactView.attributes.keySet().each { attribute ->
def value = artifactView.attributes.getAttribute(attribute)
println " - ${attribute.name} = ${value}"
}
artifactView.artifacts.artifactFiles.files.each { file ->
println "- Artifact: ${file.name}"
}
}
我们运行任务 artifactWithAttributeAndView 以查看我们获得主构件
ArtifactView with attribute 'libraryelements = classes' for runtimeClasspath: - Attributes: - org.gradle.libraryelements = classes - org.gradle.category = library - org.gradle.usage = java-runtime - org.gradle.dependency.bundling = external - org.gradle.jvm.environment = standard-jvm - Artifact: main
现在,让我们创建一个 ArtifactView 来选择我们的自定义变体 apiProductionElements,通过指定属性 org.gradle.category:production 并强制 Gradle 重新选择一个新的变体
tasks.register("artifactWithAttributeAndVariantReselectionView") {
val configuration = configurations.runtimeClasspath
println("ArtifactView with attribute 'category = production' for ${configuration.name}:")
val artifactView = configuration.get().incoming.artifactView {
withVariantReselection()
attributes {
attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category::class, "production"))
}
}
println("- Attributes:")
artifactView.attributes.keySet().forEach { attribute ->
val value = artifactView.attributes.getAttribute(attribute)
println(" - ${attribute.name} = ${value}")
}
artifactView.artifacts.artifactFiles.files.forEach { file ->
println("- Artifact: ${file.name}")
}
}
tasks.register('artifactWithAttributeAndVariantReselectionView') {
def configuration = configurations.runtimeClasspath
println "ArtifactView with attribute 'category = production' for ${configuration.name}:"
def artifactView = configuration.incoming.artifactView {
withVariantReselection()
attributes {
attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category,'production'))
}
}
println "- Attributes:"
artifactView.attributes.keySet().each { attribute ->
def value = artifactView.attributes.getAttribute(attribute)
println " - ${attribute.name} = ${value}"
}
artifactView.artifacts.artifactFiles.files.each { file ->
println "- Artifact: ${file.name}"
}
}
正如预期的那样,选择了 apiProductionElements 变体以及 production.jar 构件
ArtifactView with attribute 'libraryelements = classes' for runtimeClasspath: - Attributes: - org.gradle.libraryelements = classes - org.gradle.category = library - org.gradle.usage = java-runtime - org.gradle.dependency.bundling = external - org.gradle.jvm.environment = standard-jvm - Artifact: main