How do you depend on a *specific* named configuration of another project in the same build, rather than its default API/runtime, and what does the syntax look like?
answer
- project(path:, configuration:) two-arg form
- bypasses attribute matching
- producer config must be canBeConsumed
- Kotlin uses named args path =, configuration =
- testFixtures() is the attribute-driven shortcut
basics
~10 sUse the two-arg project notation: project(path: ':lib', configuration: 'someConfig'). The configuration name selects which producer configuration to consume instead of the default one.
solid answer
~40 sA plain `project(':lib')` dependency resolves against the producer's default outgoing variant (its API/runtime, chosen by attribute matching). To pull a *named* producer configuration instead, you use the explicit notation `project(path: ':lib', configuration: 'someConfig')` (Groovy) or `project(path = ":lib", configuration = "someConfig")` (Kotlin). This bypasses attribute-based variant selection and binds directly to the artifacts and dependencies that the named configuration exposes. It's the classic way to consume a non-default output of a sibling project — for example a `testFixtures`-style configuration, packaged client artifacts, or generated resources. Modern Gradle prefers attribute-driven selection (a single `project(':lib')` with attributes deciding the variant), but the explicit `configuration:` form is still valid and sometimes the simplest way to grab a specific output.
code
kotlin · 7 linesdependencies {
// default variant (attribute-driven)
implementation(project(":lib"))
// a specific named producer configuration
implementation(project(path = ":lib", configuration = "instrumentedJars"))
}go deeper
Recall that there is a two-arg form project(path:, configuration:) that picks a named configuration instead of the default.
Explain the syntax in both Groovy and Kotlin and that it bypasses attribute matching, and note the producer config must be consumable.
Contrast explicit configuration targeting with attribute-driven variant selection and explain when each is appropriate; mention testFixtures() as the higher-level helper.
Frame a policy: prefer attribute/capability-driven consumption for maintainability, reserve explicit configuration targeting for narrow interop cases, and discuss migration paths off brittle explicit wiring.
## The problem When you write a project dependency in a multi-project build: ```kotlin dependencies { implementation(project(":lib")) } ``` Gradle has to decide *which* set of artifacts and transitive dependencies from `:lib` you actually get. By default it performs **variant-aware resolution**: it looks at the consumer's request attributes (e.g. `org.gradle.usage = java-api` vs `java-runtime`) and matches them against the producer's *consumable* configurations to pick exactly one variant. You never name a configuration — the attributes do the work. ## Targeting a specific configuration Sometimes you want to bypass that and grab a *named* output explicitly. The two-argument project notation does this: ```kotlin dependencies { implementation(project(path = ":lib", configuration = "instrumentedJars")) } ``` This says: "resolve `:lib`, but take exactly the configuration named `instrumentedJars` — its artifacts and its dependencies." Attribute matching is **not** used to choose the variant in this case; you've named it directly. ## What the producer must do For this to work, `:lib` must expose a configuration that is **consumable** (`canBeConsumed = true`) and carries the artifacts you want: ```kotlin // in :lib val instrumentedJars by configurations.creating { isCanBeConsumed = true isCanBeResolved = false } artifacts { add("instrumentedJars", instrumentJarTask) } ``` A configuration that is only a *bucket* (both `canBeConsumed` and `canBeResolved` false) or only *resolvable* cannot be consumed this way. ## When to use it vs attributes - **Explicit `configuration:`** — quick, direct, no attribute plumbing. Downside: brittle, ignores variant rules, and won't compose with attribute-based selection elsewhere. - **Attribute-driven** (plain `project(":lib")` + matching attributes) — the modern, recommended path; it's what plugins like `java-test-fixtures` build on top of via the `testFixtures(...)` helper. ## Test fixtures shortcut The `java-test-fixtures` plugin generates a `testFixtures` capability and gives you a helper: ```kotlin dependencies { testImplementation(testFixtures(project(":lib"))) } ``` That's attribute/capability driven under the hood — you rarely need the raw `configuration = "testFixtures..."` form once the plugin is applied.
- What property must the producer configuration have for project(configuration:) to resolve against it?It must be consumable — isCanBeConsumed = true. A pure bucket or a resolvable-only configuration cannot be targeted this way.
- What is the Kotlin DSL equivalent of the Groovy project(path: ':lib', configuration: 'x')?project(path = ":lib", configuration = "x") — Kotlin uses named arguments with = instead of Groovy's map-literal colon syntax.
saying these in an interview costs you the question
- Saying project(':lib') always gives you 'all' the lib's outputs — it gives one attribute-selected variant.
- Claiming you can target any configuration regardless of its canBeConsumed flag.