##// END OF EJS Templates
Set the project version to 0.1.0, add publication descriptions/license metadata, and keep module-level docs as compatibility pointers to the root documentation.
Set the project version to 0.1.0, add publication descriptions/license metadata, and keep module-level docs as compatibility pointers to the root documentation.

File last commit:

r60:e376d0cab00e default
r60:e376d0cab00e default
Show More
variant_sources_precedence.md
382 lines | 9.1 KiB | text/x-minidsrc | MarkdownLexer
/ variant_sources_precedence.md

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:

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:

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:

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:

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:

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:

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:

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:

(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

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