variant_sources_precedence.md
382 lines
| 9.1 KiB
| text/x-minidsrc
|
MarkdownLexer
|
|
r41 | # `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 | ||||
|
|
r60 | Four selectors are available: | ||
|
|
r41 | |||
|
|
r60 | - `configureEach(...)` | ||
|
|
r41 | - `variant(...)` | ||
| - `layer(...)` | ||||
| - `unit(...)` | ||||
| They all target the same set of compile units, but at different levels of specificity. | ||||
|
|
r60 | ### `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. | ||||
| --- | ||||
|
|
r41 | ### `variant(...)` | ||
| `variant(...)` applies configuration to all compile units that belong to the given variant. | ||||
| Example: | ||||
| ```groovy | ||||
| variantSources { | ||||
| variant("browser") { | ||||
|
|
r60 | sourceSet { | ||
| declareOutputs("js", "dts") | ||||
| } | ||||
|
|
r41 | } | ||
| } | ||||
| ``` | ||||
| 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") { | ||||
|
|
r60 | sourceSet { | ||
| sets.create("ts") { | ||||
| srcDir("src/main/ts") | ||||
| } | ||||
|
|
r41 | } | ||
| } | ||||
| } | ||||
| ``` | ||||
| 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") { | ||||
|
|
r60 | sourceSet { | ||
| sets.create("resources") { | ||||
| srcDir("src/browserMain/resources") | ||||
| } | ||||
|
|
r41 | } | ||
| } | ||||
| } | ||||
| ``` | ||||
| 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 | ||||
|
|
r60 | configureEach < variant < layer < unit | ||
|
|
r41 | ``` | ||
| This means: | ||||
|
|
r60 | 1. `configureEach(...)` actions are applied first | ||
| 2. `variant(...)` actions are applied next | ||||
| 3. `layer(...)` actions are applied next | ||||
| 4. `unit(...)` actions are applied last | ||||
|
|
r41 | |||
| 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. | ||||
|
|
r43 | ### 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 | ||||
|
|
r60 | - selector rules here mean `configureEach(...)`, `variant(...)`, `layer(...)`, | ||
| and `unit(...)` | ||||
|
|
r43 | - once chosen, the policy cannot be changed later | ||
| - the policy is single-valued; it is not intended to be switched during further | ||||
| configuration | ||||
|
|
r44 | - the enforcement point is the first selector registration itself; finalization | ||
| of `variants` alone does not freeze this policy | ||||
|
|
r43 | |||
| 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 | ||||
|
|
r60 | actual registration order, not in reconstructed | ||
| `configureEach < variant < layer < unit` order | ||||
|
|
r43 | |||
|
|
r41 | --- | ||
|
|
r44 | ## 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: | ||||
|
|
r60 | - policy selection must happen before `variantSources.whenAvailable(...)` | ||
|
|
r44 | becomes observable | ||
| - compile-unit names are projected and validated before queued | ||||
|
|
r60 | `whenAvailable(...)` callbacks are replayed | ||
| - changing naming policy from inside a `whenAvailable(...)` callback is too late | ||||
|
|
r44 | |||
| --- | ||||
|
|
r41 | ## Example | ||
| ```groovy | ||||
| variantSources { | ||||
|
|
r60 | configureEach { | ||
| sourceSet { | ||||
| declareOutputs("js", "dts") | ||||
| } | ||||
| } | ||||
|
|
r41 | variant("browser") { | ||
|
|
r60 | sourceSet { | ||
| registerOutput("js", layout.projectDirectory.file("inputs/browser.js")) | ||||
| } | ||||
|
|
r41 | } | ||
| layer("main") { | ||||
|
|
r60 | sourceSet { | ||
| sets.create("ts") { | ||||
| srcDir("src/main/ts") | ||||
| } | ||||
|
|
r41 | } | ||
| } | ||||
| unit("browser", "main") { | ||||
|
|
r60 | sourceSet { | ||
| sets.create("resources") { | ||||
| srcDir("src/browserMain/resources") | ||||
| } | ||||
|
|
r41 | } | ||
| } | ||||
| } | ||||
| ``` | ||||
| For compile unit `(browser, main)` the effective configuration is built in this order: | ||||
| 1. `variant("browser")` | ||||
| 2. `layer("main")` | ||||
| 3. `unit("browser", "main")` | ||||
|
|
r60 | The global `configureEach(...)` selector is applied before the listed | ||
| variant/layer/unit selectors for every compile unit. | ||||
|
|
r41 | 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. | ||||
|
|
r43 | Internally, selector-based configuration is accumulated and later applied by the | ||
| source-set materializer when a `GenericSourceSet` is created for a compile unit. | ||||
|
|
r41 | |||
| This guarantees that: | ||||
|
|
r43 | - selector precedence is stable before materialization | ||
|
|
r41 | - registration order is preserved | ||
|
|
r43 | - configuration of already materialized targets is governed by the selected | ||
| late-configuration policy | ||||
|
|
r41 | - 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 | ||||
|
|
r60 | configureEach < variant < layer < unit | ||
|
|
r41 | ``` | ||
| - registration order is preserved within the same selector level | ||||
|
|
r43 | - already materialized targets are handled by `lateConfigurationPolicy(...)` | ||
| - the late-configuration policy must be selected before the first selector rule | ||||
| and cannot be changed later | ||||
|
|
r44 | - compile-unit naming is governed by `namingPolicy(...)` | ||
| - by default, name collisions fail fast during finalized context creation | ||||
|
|
r41 | - `variants` defines what exists | ||
| - `variantSources` defines how those compile units are materialized as source sets | ||||
