variantSources: selectors and precedence
variantSources configures source-set materialization over the compile-unit space.
A compile unit is defined as:
(variant, layer)
This means:
variantdefines compilation semanticslayerdefines compilation partitioning
The variantSources DSL does not introduce a separate source model.
Instead, it provides configuration selectors over the existing compile-unit space.
Selectors
Three selectors are available:
variant(...)layer(...)unit(...)
They all target the same set of compile units, but at different levels of specificity.
variant(...)
variant(...) applies configuration to all compile units that belong to the given variant.
Example:
variantSources { variant("browser") { declareOutputs("js", "dts") } }
This affects all compile units of browser, for example:
(browser, main)(browser, rjs)(browser, test)
Use this selector for variant-wide conventions.
layer(...)
layer(...) applies configuration to all compile units that use the given layer.
Example:
variantSources { layer("main") { set("ts") { srcDir("src/main/ts") } } }
This affects all compile units with layer main, for example:
(browser, main)(nodejs, main)(electron, main)
Use this selector for cross-variant layer conventions.
unit(...)
unit(...) applies configuration to one exact compile unit.
Example:
variantSources { unit("browser", "main") { set("resources") { srcDir("src/browserMain/resources") } } }
This affects only:
(browser, main)
Use this selector for the most specific adjustments.
Precedence
For each compile unit, source-set configuration is applied in the following order:
variant < layer < unit
This means:
variant(...)actions are applied firstlayer(...)actions are applied nextunit(...)actions are applied last
Each next level is allowed to refine or override the previous one.
Within the same level
Within the same selector level, actions are applied in registration order.
For example, if two plugins both configure layer("main"), their actions are applied in the same order in which they were registered.
Scope of this guarantee
This precedence describes the normal materialization order used by the source-set materializer.
It is stable for source sets that are configured before they are materialized.
If a selector rule is added after a target source set has already been materialized, the behavior depends on the selected late-configuration policy.
- in
failmode, such late configuration is rejected - in
warnandallowmodes, the late action is applied as an imperative follow-up step - in
warnandallowmodes, selector precedence is not reconstructed retroactively for already materialized targets
Late Configuration Policy
variantSources exposes a policy switch for selector rules that target already
materialized source sets:
variantSources { lateConfigurationPolicy { failOnLateConfiguration() } }
Available modes:
failOnLateConfiguration()rejects such ruleswarnOnLateConfiguration()allows them and emits a warningallowLateConfiguration()allows them silently
Policy rules:
- the policy must be chosen before the first selector rule is added
- selector rules here mean
variant(...),layer(...), andunit(...) - once chosen, the policy cannot be changed later
- the policy is single-valued; it is not intended to be switched during further configuration
Operationally:
failpreserves the strict precedence contract by rejecting late mutation of already materialized targetswarnandallowkeep compatibility with imperative late mutation- in
warnandallow, already materialized targets observe the late action in actual registration order, not in reconstructedvariant < layer < unitorder
Example
variantSources { variant("browser") { declareOutputs("js", "dts") } layer("main") { set("ts") { srcDir("src/main/ts") } } unit("browser", "main") { set("resources") { srcDir("src/browserMain/resources") } } }
For compile unit (browser, main) the effective configuration is built in this order:
variant("browser")layer("main")unit("browser", "main")
For compile unit (browser, rjs) the effective configuration is built in this order:
variant("browser")layer("rjs")if presentunit("browser", "rjs")if present
For compile unit (nodejs, main) the effective configuration is built in this order:
variant("nodejs")if presentlayer("main")unit("nodejs", "main")if present
Model boundary
These selectors do not define compile units.
Compile units are derived from finalized variants.
variantSources only configures how source sets are materialized for those units.
This means:
variantsis the source of truth for compile-unit existencevariantSourcesis the source of truth for compile-unit source-set configuration
Operational semantics
The variantSources API is exposed through a finalized context.
Conceptually, configuration is registered against finalized model objects, while DSL sugar may still use names for convenience.
Internally, selector-based configuration is accumulated and later applied by the
source-set materializer when a GenericSourceSet is created for a compile unit.
This guarantees that:
- selector precedence is stable before materialization
- registration order is preserved
- configuration of already materialized targets is governed by the selected late-configuration policy
- adapters do not need to depend on raw Gradle lifecycle timing
Summary
- compile unit space is
(variant, layer) variant(...),layer(...), andunit(...)are selectors over that space- precedence is:
variant < layer < unit
- registration order is preserved within the same selector level
- already materialized targets are handled by
lateConfigurationPolicy(...) - the late-configuration policy must be selected before the first selector rule and cannot be changed later
variantsdefines what existsvariantSourcesdefines how those compile units are materialized as source sets
