Gradle APIs can be used to influence the process of artifact selection.
Gradle can then expose the results of artifact selection as an ArtifactCollection
. More commonly, the results are exposed as a FileCollection
, which is a flat list of files.
By default, the attributes used for artifact selection are the same as those used for variant selection during graph resolution. These attributes are specified by the Configuration.getAttributes()
property.
To perform artifact selection (and implicitly, graph resolution) using these default attributes, use the FileCollection
and ArtifactCollection
APIs.
ResolvedConfiguration
, LenientConfiguration
, ResolvedArtifact
and ResolvedDependency
APIs. However, these APIs are in maintenance mode and are discouraged for use in new development. These APIs perform artifact selection using the default attributes. Resolving files
To resolve files, we first define a task that accepts a ConfigurableFileCollection
as input:
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)
}
}
}
Then, we can wire up a resolvable configuration’s files to the task’s input. The Configuration
directly implements FileCollection
and can be wired directly:
build.gradle.kts
tasks.register<ResolveFiles>("resolveConfiguration") {
files.from(configurations.runtimeClasspath)
}
build.gradle
tasks.register("resolveConfiguration", ResolveFiles) {
files.from(configurations.runtimeClasspath)
}
Running the resolveConfiguration
task produces:
> Task :resolveConfiguration junit-platform-commons-1.11.0.jar junit-jupiter-api-5.11.0.jar opentest4j-1.3.0.jarResolving artifacts
Instead of consuming the files directly from the implicit artifact selection process, we can consume the artifacts, which contain both the files and the metadata.
This process is slightly more complicated, as in order to maintain Configuration Cache compatibility, we need to split the fields of ResolvedArtifactResult
into two task inputs:
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}")
}
}
}
This task is initialized similarly to the file resolution 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)
}
Running this task, we can see that file metadata is included in the output:
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.jarCustom Artifact Selection
The ArtifactView
API operates on top of the resolved graph, defined by a ../javadoc/org/gradle/api/artifacts/result/ResolutionResult.html[ResolutionResult
].
The API provides flexible ways to access the resolved artifacts:
FileCollection
- A flat list of files, which is the most commonly way to work with resolved artifacts.
ArtifactCollection
- Offers access to both the metadata and the files of resolved artifacts, allowing for more advanced artifact handling.
When you call a configuration’s getFiles()
, Gradle selects artifacts based on the attributes used during graph resolution. However, the ArtifactView
API is more flexible. It allows you to resolve artifacts from the graph with custom attributes.
An ArtifactView
allows you to:
Query artifacts with different attributes:
Suppose the graph resolved a dependency’s runtime
variant. You can use an ArtifactView
to extract artifacts from its api
variant instead, even if they weren’t originally part of the resolved graph.
Extract specific types of artifacts:
You can request only the .jar
files or a specific artifact type (e.g., sources, Javadoc) by specifying an attribute like artifactType
.
Avoid side effects:
Using an ArtifactView
allows you to extract artifacts without changing the underlying dependency resolution logic or configuration state.
In the following example, a producer project creates a library with typical Java library variants (runtimeElements
, apiElements
). We also create a custom variant called apiProductionElements
with the artifact production.jar
and attribute org.gradle.category:production
:
producer/build.gradle.kts
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"))
}
}
}
producer/build.gradle
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'))
}
}
}
We can view the available variants of this library as well as their corresponding artifacts and attributes with a custom task called checkProducerAttributes
. Here is an abridged output showing the relevant variants of this library, along with their corresponding artifacts and attributes:
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
A Java application is a consumer of this Java library:
consumer/build.gradle.kts
plugins {
application
}
repositories {
mavenCentral()
}
// Declare the dependency on the producer project
dependencies {
implementation(project(":producer")) // This references another subproject in the same build
}
consumer/build.gradle
plugins {
id 'application'
}
repositories {
mavenCentral()
}
// Declare the dependency on the producer project
dependencies {
implementation project(':producer')
}
By default, the application, as the consumer, will consume the expected variant when it is run. We can verify this with another custom task called checkResolvedVariant
that prints out the following:
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
As expected, for the runtimeClasspath
, the application consumes the runtimeElements
variant of the library which is available as the artifact producer.jar
. It uses the attributes org.gradle.category:library
and org.gradle.usage:java-runtime
to select this variant.
Now, let’s create an ArtifactView
to select one of the other artifacts provided by the library. We do this by using an ArtifactView
with the attribute org.gradle.category:classes
so that instead of the jar file, we get the sources:
consumer/build.gradle.kts
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}")
}
}
consumer/build.gradle
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}"
}
}
We run the task artifactWithAttributeAndView
to see that we get the main artifact instead:
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
Now, let’s create an ArtifactView
to select our custom variant apiProductionElements
by specifying the attribute org.gradle.category:production
and forcing Gradle to reselect a new variant:
consumer/build.gradle.kts
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}")
}
}
consumer/build.gradle
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}"
}
}
As expected, the apiProductionElements
variant is selected along with the production.jar
artifact:
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
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4