If you write project(path: ':lib', configuration: 'reports') and resolution fails, what is likely wrong on the producer side and how do you make a configuration targetable this way?
answer
- two role flags: canBeResolved / canBeConsumed
- consumable target needs canBeConsumed = true
- outgoing config -> canBeResolved = false
- attach via artifacts { add(...) } or outgoing.artifact
- bucket/resolvable-only -> not targetable
basics
~10 sThe named configuration on :lib probably isn't consumable. Mark it isCanBeConsumed = true (and usually isCanBeResolved = false) and attach artifacts to it, then it can be targeted.
solid answer
~40 sConsuming a named configuration cross-project requires the producer to actually *expose* one. Every Gradle configuration has two role flags: `canBeConsumed` (other projects may depend on it) and `canBeResolved` (it can resolve a dependency graph itself). To be a valid target of `project(configuration: 'reports')`, the producer's `reports` configuration must have `isCanBeConsumed = true`. It typically also sets `isCanBeResolved = false`, because a pure *outgoing* configuration shouldn't double as a resolution sink. Then you attach the outputs via `artifacts { add("reports", someTask) }` or `outgoing.artifact(...)`. If the configuration is only a bucket (both false) or only resolvable, Gradle rejects the cross-project request — often with a message that the configuration is not meant to be consumed. So the failure is usually a producer-side role/visibility problem, not consumer syntax.
code
kotlin · 11 lines// in :lib (the producer)
val reports by configurations.creating {
isCanBeConsumed = true
isCanBeResolved = false
}
artifacts { add("reports", tasks.named("genReports")) }
// in :app (the consumer)
dependencies {
implementation(project(path = ":lib", configuration = "reports"))
}go deeper
Know that the producer configuration must be 'consumable' (canBeConsumed = true) and have artifacts attached.
Explain the two role flags, the typical consumable setup (canBeConsumed true, canBeResolved false), and how to attach artifacts.
Diagnose failures by reasoning about roles, explain why dual-role configs are discouraged, and relate to how java plugin's apiElements/runtimeElements are defined.
Set conventions for exposing custom outgoing configurations, prefer attribute/capability modeling for reuse, and govern against ad-hoc consumable configs proliferating across modules.
## Configuration roles, briefly Every Gradle `Configuration` carries two booleans that define its **role**: - `canBeResolved` — it can be *resolved* into a concrete dependency graph + files (a consumer/resolution sink). - `canBeConsumed` — it can be *exposed* to other projects as a dependency target (a producer/outgoing surface). Three common combinations: | Role | canBeResolved | canBeConsumed | example | |------|---------------|---------------|---------| | Bucket (declarable) | false | false | `implementation`, `api` | | Resolvable | true | false | `compileClasspath`, `runtimeClasspath` | | Consumable | false | true | `apiElements`, `runtimeElements` | ## Why your cross-project target fails `project(path = ":lib", configuration = "reports")` asks `:lib` to hand over the artifacts of *its* `reports` configuration. Gradle only allows this if `reports` is **consumable** (`canBeConsumed = true`). If `reports` is a bucket or resolvable-only, the request is invalid and you get an error to the effect that the target configuration is not intended to be consumed. ## Making a configuration targetable ```kotlin val reports by configurations.creating { isCanBeConsumed = true // other projects may depend on it isCanBeResolved = false // it is purely outgoing } val genReports = tasks.register<Zip>("genReports") { /* ... */ } artifacts { add("reports", genReports) } ``` Now a sibling can do `implementation(project(path = ":lib", configuration = "reports"))` and receive the zip. ## Attributes are optional here Because you named the configuration explicitly, Gradle does **not** run attribute matching to pick it. You can still add attributes (`attributes { attribute(Usage.USAGE_ATTRIBUTE, ...) }`) for documentation or so the same configuration also participates in attribute-driven resolution, but they aren't required for the explicit-name path. ## Common producer-side mistakes - Forgetting `isCanBeConsumed = true` (left at the default false). - Adding artifacts to a *bucket* configuration like `implementation` and trying to consume that. - Marking it both resolvable and consumable — discouraged; one configuration shouldn't be both ends.
- Why shouldn't a single configuration be both resolvable and consumable?It conflates the producer (outgoing) and consumer (resolution) roles, leading to ambiguous semantics and accidental dependency leakage; Gradle warns against and is deprecating this dual role.
- Do you need attributes on the producer configuration for project(configuration:) to work?No — explicitly naming the configuration skips attribute matching. Attributes are optional here and mostly useful if the same configuration also participates in attribute-driven resolution.
saying these in an interview costs you the question
- Blaming consumer syntax when the real issue is a non-consumable producer configuration.
- Attaching artifacts to a bucket like implementation and expecting it to be consumable.