# `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

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:

```groovy
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:

```groovy
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:

```groovy
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:

```text
variant < layer < unit
```

This means:

1. `variant(...)` actions are applied first
2. `layer(...)` actions are applied next
3. `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 `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 `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.whenFinalized(...)`
  becomes observable
- compile-unit names are projected and validated before queued
  `whenFinalized(...)` callbacks are replayed
- changing naming policy from inside a `whenFinalized(...)` callback is too late

---

## Example

```groovy
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:

1. `variant("browser")`
2. `layer("main")`
3. `unit("browser", "main")`

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
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
