I am migrating a build from gradle8 to 9 and attempting to solve a raft of configuration cache related issues along the way. Many are straightforward but one of the trickier ones relates to use of the Configuration.resolvedConfiguration.resolvedArtifacts at task execution time. This is used to produce a manifest of all the dependencies of a particular project including an assortment of metadata (GAV, location of that GAV in a binary repository, the published sha256 , whether it’s a published dependency or a project dependency), it’s basically creating a manifest for an image (not literally an image but an image like thing) that gets published to our binary repository for use later on.
The problem is that ResolvedArtifact has a very clear and unambiguous relationship to the underlying entity & combines metadata and the content in one place. The new APIs separate these into ArtifactCollection (which is a bunch of files with a limited bit of metadata) and ResolutionResult / ResolvedComponentResult which doesn’t know about files, does have a wide range of metadata but has a not obvious (nor obviously documented) api when compared to ResolvedArtifact.
Are there any good examples that illustrate how to use these APIs in real world use cases?
To give a relatively simple example, in order to get the sha256 directly from the binary repository (jfrog artifactory in a maven format repository), I need to make an api call & that api call is formed from group, artifact, version, classifier (optionally) & extension. As far as I can see, I need to combine information from both resolution and artifact views to extract group, artifact, version and extension but I can’t see anywhere that even provides classifier explicitly (except maybe the filename itself?)
Afair they were split, so that a task could reason on the dependency resolution graph without the need to download the artifacts.
So if you need both, you probably need both and connect them.
For example like this to list the selected coordinates and the associated resolved file:
val foo by tasks.registering {
val bar = configurations.runtimeClasspath.flatMap { it.incoming.resolutionResult.rootComponent }
val baz = configurations.runtimeClasspath.map { it.incoming.artifacts }
doLast {
val artifacts = baz.get().artifacts
bar.get().dependencies.forEach { dependency ->
dependency as ResolvedDependencyResult
println("BAR: ${dependency.selected.id}")
val artifact = artifacts.single { it.id.componentIdentifier == dependency.selected.id }
println("BAZ: ${artifact.file}")
}
}
}
It seems to be an api that needs a lot of instanceof testing to check what type one is dealing with (not so obvious when these aren’t sealed types) and that this API has lost info compared to the legacy one
for example, consider the case of discovering the classifier, it seems the only practical choice is to manually parse the file name by getting the ResolvedArtifactResult, casting ComponentArtifactIdentifier to ModuleComponentArtifactIdentifier (if it’s of that type) and parsing getFileName (NB: there is some irony here in that internally DefaultModuleComponentArtifactIdentifier implements getFileName using an IvyArtifactName which does expose the classifier directly). However this is internal api so perhaps there’s no public way without actually downloading the file?
is it a deliberate choice to remove such information from public api or is this an unintended side effect? I had thought that such information might instead be exposed in the new API as an attribute, arguably extension is (artifactType=jar) but classifier is not.
overall this API seems quite substantially harder/more awkward to use/navigate
I have no idea what the Gradle folks chose and why, you have to ask them / complain to them in an issue if you care.
I guess the classifier is not provided in the api because classifiers do not have meaning. Or well, they sometimes have no meaning and sometimes multiple and there is no standard in any way about them.