构建依赖图后,Gradle 执行构件解析,将解析后的图映射到一组将在构建期间下载的构件。
构件
构件是在构建过程中生成或消耗的文件。构件通常是编译后的库、JAR 文件、AAR 文件、DLLs 或 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
,通过指定属性 org.gradle.category:production
并强制 Gradle 重新选择一个新的变体来选择我们的自定义变体 apiProductionElements
。
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