What are the consumer and producer sides of variant selection, and how do resolvable vs consumable configurations and their attributes participate?
answer
- consumable = producer variant
- resolvable = consumer request
- bucket = declarable (implementation/api)
- never both consumable+resolvable
- match request attrs vs variant attrs
basics
~10 sThe producer exposes variants through consumable configurations carrying attributes. The consumer asks for artifacts through a resolvable configuration carrying requested attributes. Variant selection matches the consumer's attributes against the producer's consumable variants.
solid answer
~40 sA configuration has two boolean roles: `isCanBeConsumed` (it's a **variant** other projects can pick — the producer side, e.g. `apiElements`, `runtimeElements`) and `isCanBeResolved` (it's a **request** that resolves to a set of artifacts — the consumer side, e.g. `compileClasspath`, `runtimeClasspath`). A well-formed configuration is one or the other, not both (the third role, plain dependency *bucket* like `implementation`, is neither — it's just declarable). Each consumable configuration carries the **producer attributes** that describe its variant; each resolvable configuration carries the **consumer's requested attributes**. During resolution Gradle takes the resolvable config's requested attributes and runs variant selection against every target component's consumable variants, using the attributesSchema's compatibility/disambiguation rules to pick exactly one variant per edge. This consumer-requests/producer-declares split is the backbone of variant-aware resolution within a single multi-project build, not just for external libraries.
code
kotlin · 12 linesval shared = Attribute.of("com.acme.kind", String::class.java)
val exposeReport by configurations.consumable("exposeReport") {
attributes { attribute(shared, "report") }
outgoing.artifact(reportFile)
}
val collectReports by configurations.resolvable("collectReports") {
attributes { attribute(shared, "report") }
}
dependencies { collectReports(project(":producer")) }go deeper
Likely beyond scope; at most know consumer asks and producer offers.
Name the three roles and that compileClasspath is resolvable while apiElements is consumable.
Explain the consumer-requests/producer-declares pairing, the attribute role split, and how it powers intra-build project dependencies.
Design custom consumable/resolvable pairs with attributes for cross-project artifact sharing and codify role/attribute conventions across a large build.
## Three roles of a configuration Every Gradle `Configuration` plays at most one resolution role: - **Declarable / bucket** (`isCanBeConsumed = false`, `isCanBeResolved = false`): where you *declare* dependencies, e.g. `implementation`, `api`. You don't resolve or publish these directly. - **Consumable** (`isCanBeConsumed = true`, `isCanBeResolved = false`): a **producer variant** other projects/builds can select, e.g. `apiElements`, `runtimeElements`. It carries the attributes describing what it offers and the artifacts it exposes. - **Resolvable** (`isCanBeResolved = true`, `isCanBeConsumed = false`): a **consumer request** that gets resolved into a concrete artifact set, e.g. `compileClasspath`, `runtimeClasspath`. It carries the requested attributes and typically `extendsFrom` the declarable buckets. Mixing consumable and resolvable on one configuration is deprecated/illegal because the role determines how attributes are interpreted. ## How they pair up in selection 1. A resolvable configuration collects its **requested attributes** (e.g. `Usage=java-api`, `TargetJvmVersion=17`). 2. For each dependency, Gradle finds the target component's **consumable variants** and their producer attributes. 3. Variant selection runs through the **attributesSchema**: compatibility filters, disambiguation tie-breaks, exactly one variant wins per edge. 4. The chosen variant's artifacts join the resolved classpath; its dependencies continue the graph. ## Within a multi-project build The same mechanism powers `project(":lib")` dependencies: `:lib`'s `apiElements`/`runtimeElements` are consumable variants; the consumer's `compileClasspath` (resolvable) selects among them by attribute. That's why the API of a project module reaches the compile classpath while runtime-only deps don't — different Usage attribute. ## Authoring custom configurations When building plugins or sharing custom artifacts between projects you create matched pairs: ```kotlin val shared = Attribute.of("com.acme.kind", String::class.java) // Producer side val exposeReport by configurations.consumable("exposeReport") { attributes { attribute(shared, "report") } outgoing.artifact(reportFile) } // Consumer side val collectReports by configurations.resolvable("collectReports") { attributes { attribute(shared, "report") } } dependencies { collectReports(project(":producer")) } ``` Getting the roles and attributes right is what makes such cross-project artifact sharing resolve unambiguously.
- Why can't a single configuration be both resolvable and consumable?The role determines whether its attributes are a *request* or a *declaration*; mixing them makes selection ambiguous, so Gradle forbids it (deprecated/illegal) and you split into a matched consumable/resolvable pair.
- How does this model apply to a project(':lib') dependency rather than an external one?`:lib` exposes consumable variants (apiElements, runtimeElements); the consumer's resolvable classpath selects among them by attribute, so the same variant-selection engine works intra-build.
saying these in an interview costs you the question
- Calling implementation/api 'resolvable' — they are declarable buckets.
- Resolving against a consumable configuration or publishing a resolvable one (wrong role).