# `variantSources`: selectors and precedence `variantSources` configures source-set materialization over the compile-unit space. A compile unit is defined as: - `(variant, layer)` This means: - `variant` defines compilation semantics - `layer` defines compilation partitioning The `variantSources` DSL does not introduce a separate source model. Instead, it provides configuration selectors over the existing compile-unit space. ## Selectors Four selectors are available: - `configureEach(...)` - `variant(...)` - `layer(...)` - `unit(...)` They all target the same set of compile units, but at different levels of specificity. ### `configureEach(...)` `configureEach(...)` applies configuration to every materialized compile-unit source set. Example: ```groovy variantSources { configureEach { sourceSet { declareOutputs("js") } } } ``` Use this selector for global source-set conventions. --- ### `variant(...)` `variant(...)` applies configuration to all compile units that belong to the given variant. Example: ```groovy variantSources { variant("browser") { sourceSet { 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: ```groovy variantSources { layer("main") { sourceSet { sets.create("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: ```groovy variantSources { unit("browser", "main") { sourceSet { sets.create("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: ```text configureEach < variant < layer < unit ``` This means: 1. `configureEach(...)` actions are applied first 2. `variant(...)` actions are applied next 3. `layer(...)` actions are applied next 4. `unit(...)` 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 `fail` mode, such late configuration is rejected - in `warn` and `allow` modes, the late action is applied as an imperative follow-up step - in `warn` and `allow` modes, 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: ```groovy variantSources { lateConfigurationPolicy { failOnLateConfiguration() } } ``` Available modes: - `failOnLateConfiguration()` rejects such rules - `warnOnLateConfiguration()` allows them and emits a warning - `allowLateConfiguration()` allows them silently Policy rules: - the policy must be chosen before the first selector rule is added - selector rules here mean `configureEach(...)`, `variant(...)`, `layer(...)`, and `unit(...)` - once chosen, the policy cannot be changed later - the policy is single-valued; it is not intended to be switched during further configuration - the enforcement point is the first selector registration itself; finalization of `variants` alone does not freeze this policy Operationally: - `fail` preserves the strict precedence contract by rejecting late mutation of already materialized targets - `warn` and `allow` keep compatibility with imperative late mutation - in `warn` and `allow`, already materialized targets observe the late action in actual registration order, not in reconstructed `configureEach < variant < layer < unit` order --- ## Compile-Unit Naming Policy `variantSources` also exposes a policy for projecting compile units to symbolic source-set names: ```groovy variantSources { namingPolicy { failOnNameCollision() } } ``` Base projected name: - `sourceSetName = variantName + capitalize(layerName)` Example: - `(browser, main)` -> `browserMain` - `(browser, rjs)` -> `browserRjs` Available modes: - `failOnNameCollision()` rejects finalized compile-unit models that project the same base name for different compile units - `resolveNameCollision()` resolves such collisions deterministically ### `resolveNameCollision()` semantics Conflicting compile units are ordered canonically by: ```text (variant.name, layer.name) ``` Name assignment in a conflicting group is: - the first compile unit keeps the base name - the second gets suffix `2` - the third gets suffix `3` - and so on Example: - `(foo, variantBar)` and `(fooVariant, bar)` both project to `fooVariantBar` - after canonical ordering: - `(foo, variantBar)` -> `fooVariantBar` - `(fooVariant, bar)` -> `fooVariantBar2` ### Fixation Point Naming policy is fixed when the finalized `VariantSourcesContext` is created. Operationally this means: - policy selection must happen before `variantSources.whenAvailable(...)` becomes observable - compile-unit names are projected and validated before queued `whenAvailable(...)` callbacks are replayed - changing naming policy from inside a `whenAvailable(...)` callback is too late --- ## Example ```groovy variantSources { configureEach { sourceSet { declareOutputs("js", "dts") } } variant("browser") { sourceSet { registerOutput("js", layout.projectDirectory.file("inputs/browser.js")) } } layer("main") { sourceSet { sets.create("ts") { srcDir("src/main/ts") } } } unit("browser", "main") { sourceSet { sets.create("resources") { srcDir("src/browserMain/resources") } } } } ``` For compile unit `(browser, main)` the effective configuration is built in this order: 1. `variant("browser")` 2. `layer("main")` 3. `unit("browser", "main")` The global `configureEach(...)` selector is applied before the listed variant/layer/unit selectors for every compile unit. For compile unit `(browser, rjs)` the effective configuration is built in this order: 1. `variant("browser")` 2. `layer("rjs")` if present 3. `unit("browser", "rjs")` if present For compile unit `(nodejs, main)` the effective configuration is built in this order: 1. `variant("nodejs")` if present 2. `layer("main")` 3. `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: - `variants` is the source of truth for compile-unit existence - `variantSources` is 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(...)`, and `unit(...)` are selectors over that space - precedence is: ```text configureEach < 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 - compile-unit naming is governed by `namingPolicy(...)` - by default, name collisions fail fast during finalized context creation - `variants` defines what exists - `variantSources` defines how those compile units are materialized as source sets