How do you depend on a specific producer configuration of another subproject, e.g. `project(path: ':lib', configuration: 'myConfig')`, and when is that useful?
answer
- map notation path: + configuration:
- targets a named consumable config
- bypasses attribute matching
- producer: consumable + artifacts.add
- prefer attribute-based variants
basics
~10 sUse the map notation project(path: ':lib', configuration: 'instrumented') (Groovy) or project(":lib", configuration = "instrumented") (Kotlin) to consume a named outgoing configuration of the producer instead of its default variant.
solid answer
~40 sBy default `project(":lib")` selects the producer's standard library variant (apiElements/runtimeElements) via attribute matching. Sometimes a producer exposes an **additional consumable configuration** — say `instrumentedJars` or `testFixtures` — and you want exactly that artifact. The map notation `project(path: ":lib", configuration: "instrumented")` targets that named outgoing configuration directly, bypassing variant attribute matching. It's useful for cross-project producer/consumer wiring where a module publishes more than one kind of output. The modern, preferred approach, however, is **attribute-based variant selection**: have the producer declare consumable configurations with attributes and let the consumer request via attributes, so resolution stays variant-aware and composable. Explicit `configuration:` targeting is the older, more brittle mechanism — fine for simple internal wiring, less so once variants and metadata publication matter.
code
kotlin · 8 lines// producer (lib/build.gradle.kts)
val instrumentedJars by configurations.consumable("instrumentedJars")
artifacts { add(instrumentedJars.name, tasks.named("instrument")) }
// consumer
dependencies {
implementation(project(":lib", configuration = "instrumentedJars"))
}go deeper
Just know the default project(':lib') picks the standard library output.
Recognize the map notation exists to target a specific named producer configuration.
Explain consumable vs resolvable roles, the producer-side artifacts wiring, and why attribute-based variants are preferred.
Decide org conventions: standardize on attribute schemas/variants over named-configuration coupling for maintainable, publishable wiring.
## The default vs. targeted case `implementation(project(":lib"))` lets Gradle pick the right **variant** of `:lib` by matching *attributes* (Usage = java-api/java-runtime, LibraryElements = jar, etc.) between the consumer's resolvable configuration and the producer's consumable ones (`apiElements`, `runtimeElements`). When a producer deliberately exposes a **non-standard output**, you can target it explicitly: ```groovy // Groovy DSL dependencies { implementation project(path: ':lib', configuration: 'instrumentedJars') } ``` ```kotlin // Kotlin DSL dependencies { implementation(project(":lib", configuration = "instrumentedJars")) } ``` This bypasses attribute matching and binds straight to the named **consumable** configuration on the producer. ## Producer side For this to work the producer must declare an outgoing (consumable) configuration and attach an artifact: ```kotlin val instrumentedJars by configurations.consumable("instrumentedJars") artifacts { add("instrumentedJars", tasks.named("instrument")) } ``` A configuration that is **consumable** (`canBeConsumed = true`, `canBeResolved = false`) is meant to be referenced by other projects; a **resolvable** one is what the consumer uses to build a classpath. That role split — `configurations.resolvable(...)` vs `configurations.consumable(...)` — is the foundation of cross-project wiring. ## When explicit targeting is useful - A module produces several distinct artifact kinds (instrumented vs. plain, fat jar vs. thin jar) and you need one specific kind. - Legacy builds predating full variant-aware resolution. - Quick internal wiring where authoring full attribute schemas is overkill. ## Why prefer attributes instead Explicit `configuration:` targeting is **not variant-aware**: it ignores attributes, doesn't compose with metadata published to a repository, and can silently pick an artifact that doesn't match the consumer's needs (wrong Java version, missing runtime deps). The recommended modern pattern is to give the producer's configurations **attributes** and have the consumer request via attributes — Gradle then negotiates the best matching variant, and the same wiring survives publication. Reach for `configuration:` only for simple, in-build, internal cases.
- What's the modern alternative to targeting a configuration by name?Attribute-based variant selection: the producer attaches attributes to its consumable configurations and the consumer requests via attributes, so Gradle picks the matching variant and the wiring survives publication.
- What must be true of the producer configuration you target?It must be consumable (`canBeConsumed = true`, not resolvable) and have an artifact attached, otherwise nothing is exposed to consume.
- Why is explicit `configuration:` targeting considered brittle?It ignores attributes, so it can select an artifact incompatible with the consumer's needs, and it doesn't compose with published module metadata.
saying these in an interview costs you the question
- Targeting a resolvable (not consumable) configuration.
- Claiming `configuration:` participates in attribute-based variant matching — it bypasses it.
- Recommending it as the default over attribute-based selection.