How do you actually trigger a registered artifact transform during resolution using an artifactView?
answer
- incoming.artifactView { attributes.attribute(...) }
- request to-attributes to trigger
- view is local, doesn't mutate config
- .artifacts / .artifactFiles = transformed
- lenient(true), componentFilter
basics
~10 sResolve the configuration with an artifactView that requests the transform's to attribute, e.g. configurations.runtimeClasspath.get().incoming.artifactView { attributes.attribute(artifactType, "classes") }.artifacts. Requesting that attribute makes Gradle insert the transform.
solid answer
~40 sRegistration alone does nothing — you trigger a transform by *asking* for the target attributes. The idiomatic way is `incoming.artifactView { ... }`, where you set the attribute(s) that match a transform's `to`: ```kotlin val classes = configurations.runtimeClasspath.get().incoming .artifactView { attributes.attribute(artifactType, "classes") } .artifacts.artifactFiles ``` Gradle resolves the graph normally, then for each artifact whose current attributes don't match what the view requested, it finds a registered transform chain from the artifact's attributes (`from`) to the requested ones (`to`) and inserts it. The view's `.artifacts` (or `.files`) are the *transformed* outputs. Using `artifactView` instead of changing the configuration's own attributes keeps the request local — you get a transformed view without mutating the configuration for everyone else. You can also pass `lenient(true)` to tolerate failures.
code
kotlin · 13 linesval artifactType = Attribute.of("artifactType", String::class.java)
val transformed = configurations.runtimeClasspath.get().incoming
.artifactView {
attributes.attribute(artifactType, "classes")
lenient(true)
}
.artifacts
.artifactFiles
tasks.register("useClasses") {
inputs.files(transformed)
}go deeper
Know that you request the target attribute via incoming.artifactView { attributes.attribute(...) } and read .artifacts/.files.
Explain that the view is local and read-only, doesn't mutate the configuration, and that requesting the to attribute is what inserts the transform.
Discuss lenient/componentFilter/withVariantReselection and wiring .artifactFiles lazily into task inputs for cacheability.
Reason about exposing transformed views as a stable internal contract and avoiding global attribute mutation that affects unrelated consumers.
## The trigger problem Registering a transform makes a rule *available*; it does not run anything. Something must **request** the `to` attributes for Gradle to insert the transform. The cleanest, most local way to do that is an **artifact view**. ## artifactView Every resolvable configuration exposes `incoming` (a `ResolvableDependencies`). On it, `artifactView { }` builds an `ArtifactView` — an alternative *view* of the resolution result with its own requested attributes: ```kotlin val artifactType = Attribute.of("artifactType", String::class.java) val classDirs = configurations.runtimeClasspath.get().incoming .artifactView { attributes.attribute(artifactType, "classes") } .artifacts // ArtifactCollection of the transformed artifacts .artifactFiles // FileCollection you can wire into a task input ``` Inside the view's `attributes { }` you set exactly the attributes you want the resulting artifacts to have. Gradle then: 1. Resolves the dependency graph using the configuration's normal attributes. 2. For each selected artifact, compares its variant attributes to the view's requested attributes. 3. Where they differ, it searches registered transforms for a chain whose `from` matches the artifact and whose `to` matches the request, and **inserts** it. 4. The view's `.artifacts`/`.files` expose the transformed outputs. ## Why artifactView (not configuration attributes) You *could* set `attributes` directly on the configuration, but that changes the variant selection for every consumer of that configuration. An `artifactView` is **local and read-only** to that one query — ideal when only one task needs the transformed shape (e.g. a task that needs `classes` dirs while everything else uses `jar`s). ## Useful view options - `lenient(true)` — don't fail the whole view if some artifacts can't be resolved/transformed. - `componentFilter { }` — restrict which components are included. - `withVariantReselection()` — allow re-selecting a different variant of a component (needed for some cross-variant transforms). ## Wiring into tasks Because `.artifactFiles` is a lazy `FileCollection`, you wire it straight into a task input (`inputs.files(...)` or a `ConfigurableFileCollection` property). The transform executes only when that input is actually realized, and the output is build-cache-eligible.
- Why prefer an artifactView over setting attributes on the configuration itself?An artifactView is a local, read-only query — it requests transformed artifacts without mutating the configuration's variant selection for every other consumer. Setting attributes on the configuration changes selection globally.
- What does lenient(true) do on an artifactView?It makes the view tolerate resolution/transform failures for individual artifacts rather than failing the entire view, so the remaining artifacts still resolve.
saying these in an interview costs you the question
- Thinking you must invoke the transform by name — you request the `to` attributes, Gradle picks the transform.
- Setting attributes on the configuration when only one task needs the transformed shape.
- Forgetting that the view's `.artifacts`/`.files` are the transformed outputs, not the originals.