diff --git a/variant_sources_precedence.md b/variant_sources_precedence.md --- a/variant_sources_precedence.md +++ b/variant_sources_precedence.md @@ -164,6 +164,8 @@ Policy rules: - 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: @@ -176,6 +178,70 @@ Operationally: --- +## 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 @@ -265,5 +331,7 @@ variant < layer < unit - 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 diff --git a/variants/src/main/java/org/implab/gradle/variants/sources/SourceSetMaterializer.java b/variants/src/main/java/org/implab/gradle/variants/sources/SourceSetMaterializer.java --- a/variants/src/main/java/org/implab/gradle/variants/sources/SourceSetMaterializer.java +++ b/variants/src/main/java/org/implab/gradle/variants/sources/SourceSetMaterializer.java @@ -4,14 +4,18 @@ import org.gradle.api.NamedDomainObjectP import org.implab.gradle.common.sources.GenericSourceSet; /** - * Materializes symbolic source set names into actual GenericSourceSet + * Materializes symbolic source set names into actual {@link GenericSourceSet} * instances. + * + *

Symbolic names are assigned from the finalized compile-unit model using + * the selected + * {@link VariantSourcesExtension#namingPolicy(org.gradle.api.Action)}. */ public interface SourceSetMaterializer { /** * Returns a lazy provider for the source set corresponding to the compile unit. * - * The provider is stable and cached per compile unit. + *

The provider is stable and cached per compile unit. */ NamedDomainObjectProvider getSourceSet(CompileUnit unit); -} \ No newline at end of file +} diff --git a/variants/src/main/java/org/implab/gradle/variants/sources/VariantSourcesContext.java b/variants/src/main/java/org/implab/gradle/variants/sources/VariantSourcesContext.java --- a/variants/src/main/java/org/implab/gradle/variants/sources/VariantSourcesContext.java +++ b/variants/src/main/java/org/implab/gradle/variants/sources/VariantSourcesContext.java @@ -9,7 +9,9 @@ import org.implab.gradle.variants.core.V /** * Registry of symbolic source set names produced by sources projection. * - * Identity in this registry is the GenericSourceSet name. + *

Identity in this registry is the {@link GenericSourceSet} name assigned + * by the finalized + * {@link VariantSourcesExtension#namingPolicy(org.gradle.api.Action)}. */ public interface VariantSourcesContext { diff --git a/variants/src/main/java/org/implab/gradle/variants/sources/VariantSourcesExtension.java b/variants/src/main/java/org/implab/gradle/variants/sources/VariantSourcesExtension.java --- a/variants/src/main/java/org/implab/gradle/variants/sources/VariantSourcesExtension.java +++ b/variants/src/main/java/org/implab/gradle/variants/sources/VariantSourcesExtension.java @@ -21,6 +21,9 @@ public interface VariantSourcesExtension *

  • once selected, it cannot be changed later;
  • *
  • the policy controls both diagnostics and late-application semantics.
  • * + * + *

    If not selected explicitly, the default is + * {@link LateConfigurationPolicySpec#failOnLateConfiguration()}. */ void lateConfigurationPolicy(Action action); @@ -28,24 +31,63 @@ public interface VariantSourcesExtension lateConfigurationPolicy(Closures.action(closure)); } + /** + * Selects how compile-unit name collisions are handled when the finalized + * source context is created. + * + *

    This policy is single-valued: + *

    + * + *

    If not selected explicitly, the default is + * {@link NamingPolicySpec#failOnNameCollision()}. + */ void namingPolicy(Action action); default void namingPolicy(Closure closure) { namingPolicy(Closures.action(closure)); } + /** + * Registers a selector rule for all compile units of the given layer. + * + *

    Registering the first selector rule fixes the selected + * {@link #lateConfigurationPolicy(Action)} for the remaining extension + * lifecycle. + */ void layer(String layerName, Action action); default void layer(String layerName, Closure closure) { layer(layerName, Closures.action(closure)); } + /** + * Registers a selector rule for all compile units of the given variant. + * + *

    Registering the first selector rule fixes the selected + * {@link #lateConfigurationPolicy(Action)} for the remaining extension + * lifecycle. + */ void variant(String variantName, Action action); default void variant(String variantName, Closure closure) { variant(variantName, Closures.action(closure)); } + /** + * Registers a selector rule for one exact compile unit. + * + *

    Registering the first selector rule fixes the selected + * {@link #lateConfigurationPolicy(Action)} for the remaining extension + * lifecycle. + */ void unit(String variantName, String layerName, Action action); /** @@ -56,6 +98,10 @@ public interface VariantSourcesExtension *

  • if called before variants finalization, action is queued *
  • if called after variants finalization, action is invoked immediately * + * + *

    By the time this callback becomes observable, compile-unit naming + * policy has already been fixed and symbolic source-set names for finalized + * compile units are determined. */ void whenFinalized(Action action); @@ -98,8 +144,21 @@ public interface VariantSourcesExtension } interface NamingPolicySpec { + /** + * Rejects finalized compile-unit models that project the same source-set + * name for different compile units. + */ void failOnNameCollision(); + /** + * Resolves name collisions deterministically for the finalized + * compile-unit model. + * + *

    Conflicting compile units are ordered canonically by + * {@code (variant.name, layer.name)}. The first unit keeps the base + * projected name, and each next unit receives a numeric suffix + * ({@code 2}, {@code 3}, ...). + */ void resolveNameCollision(); } } diff --git a/variants_variant_sources.md b/variants_variant_sources.md --- a/variants_variant_sources.md +++ b/variants_variant_sources.md @@ -453,7 +453,7 @@ Reasons: * the DSL is internal to source materialization * the source of truth for unit existence is already finalized in `VariantsView` -* `GenericSourceSetMaterializer` returns `NamedDomainObjectProvider` +* `SourceSetMaterializer` returns `NamedDomainObjectProvider` * adapters may need to refine source-related behavior after `variants` is finalized Therefore: @@ -468,6 +468,11 @@ It reflects the difference between: * a closed domain model * an open infrastructure/materialization model +This openness is still constrained by explicit policy fixation points: + +* late-configuration policy is fixed when the first selector rule is registered +* naming policy is fixed when the finalized `VariantSourcesContext` is created + --- ## Late configuration policy @@ -508,6 +513,8 @@ property: * selector rules here mean `variant(...)`, `layer(...)`, and `unit(...)` * once chosen, it cannot be changed later * it controls runtime behavior, not just a stored value +* the enforcement point is the first selector registration itself, not variants + finalization in isolation For source sets configured before materialization, selector precedence remains: @@ -523,6 +530,82 @@ For already materialized source sets in --- +## Compile-unit naming policy + +Source-set naming is treated as a separate policy concern from selector +registration. + +Conceptually, `variantSources` exposes: + +```groovy +variantSources { + namingPolicy { + failOnNameCollision() + } +} +``` + +The base projected name of a compile unit is: + +```text +variantName + capitalize(layerName) +``` + +Examples: + +* `(browser, main)` -> `browserMain` +* `(browser, rjs)` -> `browserRjs` + +Available modes are: + +* `failOnNameCollision()` - reject finalized compile-unit models that project + the same source-set name for different compile units +* `resolveNameCollision()` - resolve such conflicts deterministically + +### `resolveNameCollision()` semantics + +Conflicting compile units are ordered canonically by: + +```text +(variant.name, layer.name) +``` + +Within one conflicting group: + +* the first compile unit keeps the base name +* the second gets suffix `2` +* the third gets suffix `3` +* and so on + +For example, if: + +* `(foo, variantBar)` projects to `fooVariantBar` +* `(fooVariant, bar)` also projects to `fooVariantBar` + +then canonical ordering yields: + +* `(foo, variantBar)` -> `fooVariantBar` +* `(fooVariant, bar)` -> `fooVariantBar2` + +### Fixation point + +Naming policy is fixed when the finalized `VariantSourcesContext` is created. + +Operationally this means: + +* naming policy must be selected 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 + +This differs intentionally from late-configuration policy: + +* late-configuration policy is fixed by the first selector rule +* naming policy is fixed by finalized-context creation + +--- + ## `VariantSourcesContext` `variantSources.whenFinalized(...)` remains useful, but not because `variantSources` itself is frozen. @@ -533,7 +616,12 @@ This context contains: * `CompileUnitsView` * `RoleProjectionsView` -* `GenericSourceSetMaterializer` +* `SourceSetMaterializer` + +By the time the context becomes observable: + +* compile-unit naming policy is already fixed +* symbolic source-set names for finalized compile units are already determined Conceptually: @@ -541,7 +629,7 @@ Conceptually: interface VariantSourcesContext { CompileUnitsView getCompileUnits(); RoleProjectionsView getRoleProjections(); - GenericSourceSetMaterializer getSourceSets(); + SourceSetMaterializer getSourceSets(); } ``` @@ -559,15 +647,17 @@ variantSources.whenFinalized(ctx -> { --- -## `GenericSourceSetMaterializer` +## `SourceSetMaterializer` ### Purpose -`GenericSourceSetMaterializer` is the official source of truth for materialized source sets. +`SourceSetMaterializer` is the official source of truth for materialized source sets. It is responsible for: * lazy creation of `GenericSourceSet` +* projecting finalized compile units to symbolic source-set names +* validating or resolving name collisions according to naming policy * applying `layerRule` * connecting a compile unit to a source set provider * exposing source sets to adapters @@ -579,14 +669,14 @@ Adapters should not apply layer rules th ### Conceptual API ```java -interface GenericSourceSetMaterializer { +interface SourceSetMaterializer { NamedDomainObjectProvider getSourceSet(CompileUnit unit); } ``` --- -## Why `GenericSourceSetMaterializer` should own `layerRule` application +## Why `SourceSetMaterializer` should own `layerRule` application If adapters applied `layerRule` directly, responsibility would leak across multiple layers: @@ -599,7 +689,7 @@ This would make the model harder to reas Instead: * `layerRule` is DSL/spec-level -* `GenericSourceSetMaterializer` is execution/materialization-level +* `SourceSetMaterializer` is execution/materialization-level * adapters are consumption-level This gives a much cleaner separation. @@ -737,6 +827,11 @@ guarantee. Materialized `GenericSourceSet` objects should remain behind a lazy API. +### 7. Make name-collision behavior explicit + +Compile-unit naming must be governed by an explicit policy, not by incidental +materialization order. + --- ## Summary @@ -758,6 +853,7 @@ An open, source-materialization layer: * layer source rules * compile-unit source set materialization +* compile-unit naming policy * adapter-facing `GenericSourceSet` providers ### Derived views