diff --git a/variant_artifacts.md b/variant_artifacts.md new file mode 100644 --- /dev/null +++ b/variant_artifacts.md @@ -0,0 +1,694 @@ +# Variants and Variant Artifacts + +## Overview + +This document describes the artifact model built on top of `variants` and +`variantSources`. + +The goal is to define: + +- a stable artifact-facing model for outgoing contracts; +- a DSL for declaring slots and their inputs; +- a resolver bridge between `variantArtifacts` and `variantSources`; +- extension points for deduplication and similar policies; +- a model that remains live during configuration and does not require an + artificial freeze of slot content. + +The design follows the same split already used elsewhere: + +- `variants` defines the closed domain topology; +- `variantSources` defines source materialization semantics over that topology; +- `variantArtifacts` defines outgoing artifact contracts over that topology. + +--- + +## Core idea + +`variantArtifacts` is not the owner of the variant model and not the owner of +source-set materialization. + +Its purpose is narrower: + +- decide which variants participate in outgoing publication; +- define artifact slots for those outgoing variants; +- declare how each slot gathers its inputs. + +This makes `variantArtifacts` an outgoing-contract layer, not a compilation or +source-materialization layer. + +--- + +## Model boundaries + +### What belongs to `variants` + +- identities: `Variant`, `Role`, `Layer`; +- normalized topology relation `(variant, role, layer)`; +- finalization of the core domain model; +- `VariantsView` and derived topology views. + +### What belongs to `variantSources` + +- compile units and role projections derived from finalized `variants`; +- source-set naming policy; +- source-set materialization; +- lazy access to `GenericSourceSet` providers and their named outputs. + +### What belongs to `variantArtifacts` + +- selection of outgoing variants; +- slot identities inside an outgoing variant; +- slot assembly declarations; +- root outgoing configurations for outgoing variants; +- slot-level assembly bodies; +- publication-facing hooks. + +### What does not belong to `variantArtifacts` + +- ownership of variant existence; +- ownership of source-set naming; +- direct mutation of source materialization internals; +- compiler- or toolchain-specific logic; +- eager flattening of source inputs into files during DSL declaration. + +--- + +## Outgoing subset + +Not every declared `Variant` must become outgoing. + +`variantArtifacts` defines an outgoing subset of variants. + +For each outgoing variant there is one root outgoing aggregate: + +- one root consumable `Configuration`; +- one or more artifact slots; +- one primary slot; +- optional secondary slots. + +This is why `OutgoingConfiguration` is a real model object and not merely a +publication event payload. + +It represents a live build-facing aggregate: + +- it has its own attributes; +- it is visible to other build logic as soon as registered; +- it contains lazy handles rather than eagerly materialized state; +- it is distinct from slot assembly state. + +--- + +## Identity-first split + +The artifact model should preserve the same identity-first principle used for +the rest of the project. + +### Identity objects + +- `Variant` +- `Slot` +- `ArtifactSlot = (variant, slot)` + +These objects are: + +- cheap; +- replayable; +- suitable for discovery and selection. + +### Stateful objects + +- `OutgoingConfiguration` +- `ArtifactAssembly` + +These objects are: + +- build-facing; +- allowed to contain Gradle lazy handles; +- obtained through dedicated APIs rather than embedded into identity objects. + +### Rule of thumb + +- slot identity is cheap and replayable; +- inside one `OutgoingConfiguration`, `Slot` is the natural local identity; +- `ArtifactSlot` is useful when slot identity must be referenced outside the + parent outgoing configuration; +- slot body is stateful and resolved separately; +- outgoing configuration is a live aggregate, not a mere snapshot. + +--- + +## Artifact model + +Conceptually: + +```java +interface VariantArtifactsContext { + VariantsView getVariants(); + void all(Action action); + Optional findArtifacts(Variant variant); + OutgoingConfiguration requireArtifacts(Variant variant); + ArtifactAssemblies getAssemblies(); +} +``` + +```java +interface OutgoingConfiguration { + Variant getVariant(); + NamedDomainObjectProvider getOutgoingConfiguration(); + NamedDomainObjectContainer getSlots(); + Property getPrimarySlot(); +} +``` + +```java +interface ArtifactAssemblies { + ArtifactAssembly resolveSlot(ArtifactSlot slot); +} +``` + +This intentionally uses a Gradle-style local slot container rather than a +separate `ArtifactSlotsView`. + +The reason is simple: + +- inside one `OutgoingConfiguration`, slot identity is local and naturally + expressed as `Slot`; +- a dedicated `ArtifactSlotsView` would suggest a detached readonly projection + without clearly owning mutation/configuration semantics; +- `NamedDomainObjectContainer` better matches the live configuration model + and keeps the parent aggregate as the owner of slot structure. + +`ArtifactSlot` remains useful, but only as a fully qualified identity outside +the parent aggregate: + +- resolver APIs; +- global payloads; +- cross-variant references. + +Important distinction: + +- `OutgoingConfiguration` describes outgoing publication structure; +- `ArtifactAssembly` describes how one slot artifact is assembled. + +An `ArtifactAssembly` does not create the root outgoing configuration. It serves +one already declared slot inside that configuration. + +### Primary slot ownership + +Primary status belongs to the parent outgoing configuration, not to the slot +itself. + +This is why the preferred container-level contract is: + +```java +Property getPrimarySlot(); +``` + +and not a slot-local boolean or a derived `ArtifactSlot` reference. + +Reasons: + +- the primary role is assigned by the parent aggregate; +- within one `OutgoingConfiguration`, the variant identity is already known, so + `Slot` is sufficient and avoids redundant `(variant, slot)` duplication; +- `Property` matches the rest of the Gradle-facing live model better than a + custom `findPrimarySlot()` style API; +- `Property` gives useful write/configure/finalize semantics without inventing a + separate special lifecycle abstraction. + +Expected usage: + +- DSL or adapters may assign the primary slot through `set(...)`; +- adapters that want to provide a default may use `convention(...)`; +- the property may remain unset while the model is still incomplete; +- at materialization time the property may be finalized; +- if more than one slot exists and `primarySlot` is still unset at the + materialization point, that is a model error. + +The model should still enforce that the selected primary slot belongs to the +same `OutgoingConfiguration`. + +### Proposed public shape + +With these constraints, the preferred public structure is: + +```java +interface VariantArtifactsContext { + VariantsView getVariants(); + void all(Action action); + Optional findArtifacts(Variant variant); + OutgoingConfiguration requireArtifacts(Variant variant); + ArtifactAssemblies getAssemblies(); +} + +interface OutgoingConfiguration { + Variant getVariant(); + NamedDomainObjectProvider getOutgoingConfiguration(); + NamedDomainObjectContainer getSlots(); + Property getPrimarySlot(); +} + +interface ArtifactAssemblies { + ArtifactAssembly resolveSlot(ArtifactSlot slot); +} + +record ArtifactSlot(Variant variant, Slot slot) {} +``` + +This gives: + +- one global registry of outgoing variants; +- one local slot container per outgoing variant; +- one fully qualified slot identity for resolver and cross-aggregate use; +- one explicit service for slot assembly materialization. + +### Minimal internal shape + +The preferred minimal internal structure is: + +```java +final class VariantArtifactsRegistry implements VariantArtifactsContext { + OutgoingConfiguration outgoingConfiguration(Variant variant); + ArtifactAssemblyRules slotRules(ArtifactSlot slot); + ArtifactAssemblies assemblies(); +} + +interface ArtifactAssemblyRules { + void from(Object input); + void fromVariant(Action action); + void fromRole(String roleName, Action action); + void fromLayer(String layerName, Action action); +} +``` + +Responsibility split: + +- `VariantArtifactsRegistry` owns the whole artifact model; +- `OutgoingConfiguration` owns only the structural variant-local aggregate; +- `ArtifactAssemblyRules` owns the content declaration of one slot; +- `ArtifactAssemblies` materializes `ArtifactAssembly` from + `ArtifactSlot -> ArtifactAssemblyRules`. + +This keeps `OutgoingConfiguration` focused on structure and avoids overloading it +with slot-content APIs such as `rules(slot)`. + +### DSL binding + +The DSL should be connected through the registry, not by making +`OutgoingConfiguration` responsible for content rules. + +Conceptually: + +```java +variantArtifacts.variant("browser", spec -> { + spec.slot("runtime", assembly -> { + assembly.fromRole("production", out -> out.output("js")); + }); +}); +``` + +Operationally this means: + +1. registry creates or returns `OutgoingConfiguration` for the variant; +2. slot declaration creates or returns `Slot` inside that outgoing configuration; +3. registry forms `ArtifactSlot(variant, slot)`; +4. registry resolves `slotRules(artifactSlot)`; +5. bound `ArtifactAssemblySpec` writes into those rules. + +So the DSL writes: + +- structure into `OutgoingConfiguration`; +- slot content into registry-owned `ArtifactAssemblyRules`. + +This is the intended bridge point between the public DSL and the internal +resolver/materialization model. + +--- + +## Live model and monotonic structure + +`variantArtifacts` should be treated as a live configuration model during the +whole configuration phase. + +This means: + +- slot inputs remain live; +- `from(...)`, `fromVariant(...)`, `fromRole(...)`, `fromLayer(...)` may keep + contributing inputs until task execution; +- `ArtifactAssembly` may expose live `FileCollection`, `Provider`, and task + wiring; +- external task outputs remain outside the control of this model and must be + accepted as live inputs. + +The model should therefore avoid a mandatory freeze phase for slot content. + +Instead, it should follow a monotonic rule: + +- outgoing variant existence may grow; +- slot existence may grow; +- slot content may grow; +- publication-visible identity should not be retroactively redefined. + +In practice this means: + +- slot names are stable once declared; +- primary slot designation is structural; +- slot input content remains live. + +This also means that the model does not need a dedicated freeze phase for slot +content merely because the root outgoing configuration was registered earlier. + +Early registration of the root `Configuration` and live evolution of slot input +content are compatible concerns. + +--- + +## DSL principles + +The DSL should remain declarative and symbolic. + +It should describe: + +- which variant is outgoing; +- which slots exist; +- which slot is primary; +- which selectors contribute inputs to each slot. + +It should not directly expose: + +- `GenericSourceSet`; +- `FileCollection`; +- concrete resolved files from `variantSources`; +- internal resolver state. + +### DSL shape + +Conceptually: + +```groovy +variantArtifacts { + variant("browser") { + primarySlot("runtime") { + fromRole("production") { + output("js") + output("resources") + } + } + + slot("types") { + fromVariant { + output("dts") + } + } + + slot("sources") { + fromLayer("main") { + output("sources") + } + } + + slot("bundleMetadata") { + from(someTask) + from(layout.buildDirectory.file("generated/meta.json")) + } + } +} +``` + +### Meaning of contribution forms + +- `from(Object)` adds a direct input independent from `variantSources`; +- `fromVariant { output(...) }` selects named outputs from all compile units of + the current variant; +- `fromRole(role) { output(...) }` selects named outputs from compile units that + belong to the given role projection; +- `fromLayer(layer) { output(...) }` selects named outputs from the compile unit + of the current variant and the given layer, if such unit exists. + +The DSL stores declarations, not resolved file collections. + +--- + +## Contribution model + +Internally the DSL should compile to slot contributions. + +Conceptually: + +- `DirectContribution` +- `VariantOutputContribution` +- `RoleOutputContribution` +- `LayerOutputContribution` + +These contributions should remain symbolic for as long as possible. + +They should not resolve source sets or files at declaration time. + +Each contribution is expected to provide: + +- its selection scope; +- the requested output names; +- enough symbolic identity for later validation and resolver policies. + +--- + +## Resolver bridge between `variantSources` and `variantArtifacts` + +This is the central integration point. + +`variantArtifacts` should not access mutable internals of `variantSources`. + +Instead, it should resolve slot inputs through the public finalized +`VariantSourcesContext`. + +### Bridge responsibilities + +The bridge: + +- takes slot contribution declarations; +- expands them against finalized variant topology; +- maps logical selectors to compile units and role projections; +- obtains source sets lazily through `VariantSourcesContext`; +- resolves named outputs from those source sets; +- builds the live input model for an `ArtifactAssembly`. + +### Bridge input + +- current outgoing variant identity; +- slot contribution declarations; +- `VariantSourcesContext`. + +### Bridge output + +- a live collection of logical slot inputs; +- later adapted to `FileCollection` or other assembly-facing input models. + +### Resolution semantics + +For one outgoing variant: + +- `fromVariant { output(x) }` + - expands to all `CompileUnit` of that variant; +- `fromRole(role) { output(x) }` + - expands to `RoleProjection(variant, role)` and then to its compile units; +- `fromLayer(layer) { output(x) }` + - expands to one compile unit `(variant, layer)` when it exists; +- `from(Object)` + - bypasses `variantSources` completely. + +After compile units are known, the bridge asks +`ctx.getSourceSets().getSourceSet(unit)` for each selected unit and resolves the +requested named output. + +This keeps `variantArtifacts` independent from source-set naming internals and +other materialization details. + +--- + +## Validation + +Validation should be structural and symbolic. + +It should validate: + +- outgoing variant refers to an existing `Variant`; +- referenced `Role` exists in that variant projection space; +- referenced `Layer` exists in that variant compile-unit space; +- primary slot is defined when needed; +- primary slot refers to a slot declared in the same outgoing configuration. + +Validation should not require eager materialization of source sets or eager +resolution of files. + +--- + +## Deduplication and policy extension points + +Deduplication is important, but it should not be baked into the DSL itself. + +The correct place for it is the resolver bridge, after symbolic contributions +have been expanded to logical inputs but before they are finally adapted to +assembly-facing file collections. + +### Why not in the DSL + +At declaration time it is still unknown whether selectors overlap: + +- `fromVariant` +- `fromRole` +- `fromLayer` + +may all describe the same logical source output. + +### Why not rely only on `FileCollection` + +`FileCollection` may still provide useful physical deduplication, but it is too +late and too file-oriented to serve as the only semantic mechanism. + +The artifact model should first deduplicate logical inputs, then let Gradle +perform any additional physical deduplication. + +### Default expectation + +The default resolver should support: + +- deduplication of topology-aware inputs by logical identity; +- no implicit deduplication of direct `from(Object)` inputs. + +Logical identity should be based on domain meaning, for example: + +- `(CompileUnit, outputName)` + +and not on projected source-set names. + +This is important because source-set naming policy belongs to `variantSources` +and must not silently redefine artifact semantics. + +### Extension points + +The model should provide explicit internal extension points for: + +- deduplication policy; +- logical input identity; +- adaptation of resolved logical inputs to assembly-facing objects. + +Conceptually: + +```java +interface SlotInputDedupPolicy { ... } +interface LogicalSlotInputIdentity { ... } +interface SlotInputAdapter { ... } +``` + +The default implementation may remain simple, but these seams should exist from +the start. + +--- + +## Publication hooks + +Publication hooks remain useful, but they should observe the live structural +model rather than define it. + +Examples: + +- `whenOutgoingVariant(...)` +- `whenOutgoingSlot(...)` + +These hooks are adapter-facing customization points over already declared +outgoing structure: + +- root configuration attributes; +- slot artifact attributes; +- assembly task tweaks. + +The recommended way to connect publication-facing `Spec` objects to the +structural model is by backlink, not by moving slot rules into publication +types. + +Conceptually: + +```java +interface OutgoingConfigurationSpec { + OutgoingConfiguration getOutgoingArtifacts(); + Variant getVariant(); + Configuration getConfiguration(); +} + +interface OutgoingArtifactSlotSpec { + ArtifactSlot getArtifactSlot(); + ArtifactAssembly getAssembly(); + boolean isPrimary(); +} +``` + +In this arrangement: + +- `OutgoingConfigurationSpec` remains a publication-facing facade; +- `OutgoingConfigurationSpec` may expose the structural aggregate when an + adapter needs it; +- slot rules still belong to `VariantArtifactsRegistry`; +- publication specs do not become owners of declaration or resolver state. + +They should not become the primary structural API for the artifact model. + +This is why a separate phase-oriented `OutgoingPublicationsContext` is not +required. + +The live `OutgoingConfiguration` aggregate is sufficient. + +--- + +## Design principles + +### 1. Keep topology ownership in `variants` + +`variantArtifacts` selects from the topology model. It does not own it. + +### 2. Keep source ownership in `variantSources` + +`variantArtifacts` consumes source materialization through a resolver bridge. It +does not own source-set semantics. + +### 3. Keep the DSL symbolic + +The DSL declares intent and selection rules, not materialized files. + +### 4. Keep slot content live + +Do not introduce an artificial finalize phase for slot content unless a real +semantic need appears. + +### 5. Fix only structural identity + +Slot name, primary designation, and outgoing shape are structural. Slot inputs +remain live. + +### 6. Resolve through dedicated bridges + +Cross-model integration belongs in a resolver service, not in DSL classes. + +### 7. Add policy seams early + +Deduplication and similar concerns should have extension points from the start, +even if the initial implementation is conservative. + +--- + +## Summary + +`variantArtifacts` should be modeled as a live outgoing-contract layer over +`variants`, with source input resolution delegated to a dedicated bridge over +`variantSources`. + +The resulting shape is: + +- `variants` owns topology; +- `variantSources` owns source materialization; +- `variantArtifacts` owns outgoing contract structure; +- a resolver bridge connects symbolic slot declarations to live source-derived + inputs; +- deduplication and similar concerns are policies of that bridge, not of the + DSL itself; +- slot content stays live during configuration; +- only publication-visible structure is treated as stable identity. diff --git a/variants_variant_sources.md b/variant_sources.md rename from variants_variant_sources.md rename to variant_sources.md diff --git a/variants/src/main/java/org/implab/gradle/internal/ReplayableQueue.java b/variants/src/main/java/org/implab/gradle/internal/ReplayableQueue.java new file mode 100644 --- /dev/null +++ b/variants/src/main/java/org/implab/gradle/internal/ReplayableQueue.java @@ -0,0 +1,24 @@ +package org.implab.gradle.internal; + +import java.util.LinkedList; +import java.util.List; +import java.util.function.Consumer; + +public class ReplayableQueue { + private final List> consumers = new LinkedList<>(); + private final List values = new LinkedList<>(); + + public void add(T value) { + consumers.forEach(consumer -> consumer.accept(value)); + values.add(value); + } + + List values() { + return List.copyOf(values); + } + + public void forEach(Consumer consumer) { + values.forEach(consumer); + consumers.add(consumer); + } +} \ No newline at end of file diff --git a/variants/src/main/java/org/implab/gradle/variants/VariantArtifactsPlugin.java b/variants/src/main/java/org/implab/gradle/variants/VariantArtifactsPlugin.java --- a/variants/src/main/java/org/implab/gradle/variants/VariantArtifactsPlugin.java +++ b/variants/src/main/java/org/implab/gradle/variants/VariantArtifactsPlugin.java @@ -1,9 +1,16 @@ package org.implab.gradle.variants; +import org.gradle.api.Action; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.implab.gradle.common.core.lang.Deferred; +import org.implab.gradle.variants.artifacts.OutgoingArtifactSlotSpec; +import org.implab.gradle.variants.artifacts.OutgoingConfigurationSpec; import org.implab.gradle.variants.artifacts.VariantArtifactsContext; +import org.implab.gradle.variants.artifacts.VariantArtifactsExtension; +import org.implab.gradle.variants.artifacts.VariantArtifactsSpec; +import org.implab.gradle.variants.artifacts.internal.VariantArtifactsRegistry; +import org.implab.gradle.variants.core.Variant; import org.implab.gradle.variants.core.VariantsExtension; public abstract class VariantArtifactsPlugin implements Plugin { @@ -11,18 +18,46 @@ public abstract class VariantArtifactsPl @Override public void apply(Project target) { var extensions = target.getExtensions(); + var objects = target.getObjects(); // Apply the main VariantsPlugin to ensure the core variant model is available. target.getPlugins().apply(VariantsPlugin.class); // Access the VariantsExtension to configure variant sources. var variantsExtension = extensions.getByType(VariantsExtension.class); - var deferred = new Deferred(); + var deferred = new Deferred(); variantsExtension.whenFinalized(variants -> { - + deferred.resolve(new VariantArtifactsRegistry(variants)); }); + var variantArtifacts = new VariantArtifactsExtension() { + + @Override + public void variant(String variantName, Action action) { + deferred.whenResolved(registry -> registry.configureVariant( + objects.named(Variant.class, variantName), action)); + } + + @Override + public void whenFinalized(Action action) { + deferred.whenResolved(registry -> action.execute(registry.variantsContext())); + } + + @Override + public void whenOutgoingVariant(Action action) { + deferred.whenResolved(registry -> registry.configureOutgoing(action)); + + } + + @Override + public void whenOutgoingSlot(Action action) { + deferred.whenResolved(registry -> registry.configureOutgoingSlot(action)); + } + + }; + + extensions.add(VariantArtifactsExtension.class, "variantArtifacts", variantArtifacts); } diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/ArtifactAssemblyRules.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/ArtifactAssemblyRules.java new file mode 100644 --- /dev/null +++ b/variants/src/main/java/org/implab/gradle/variants/artifacts/ArtifactAssemblyRules.java @@ -0,0 +1,19 @@ +package org.implab.gradle.variants.artifacts; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.implab.gradle.variants.core.Layer; +import org.implab.gradle.variants.core.Role; + +@NonNullByDefault +public interface ArtifactAssemblyRules { + void addDirectInput(Object input); + + void addVariantOutputs(Set outputs); + + void addRoleOutputs(Role role, Set outputs); + + void addLayerOutputs(Layer layer, Set outputs); + +} diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/ArtifactSlotsView.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/ArtifactSlotsView.java deleted file mode 100644 --- a/variants/src/main/java/org/implab/gradle/variants/artifacts/ArtifactSlotsView.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.implab.gradle.variants.artifacts; - -import java.util.Optional; -import java.util.Set; - -import org.implab.gradle.variants.core.Variant; - -/** - * Finalized view of artifact slot identities. - * - *

This view exposes only cheap slot identities. Assemblies and publication state are resolved - * separately. - */ -public interface ArtifactSlotsView { - /** - * Returns all declared slot identities. - * - * @return all slots known to the finalized model - */ - Set getSlots(); - - /** - * Returns all slots declared for the given variant. - * - * @param variant variant identity - * @return slots declared for the variant - */ - Set getSlotsForVariant(Variant variant); - - /** - * Finds a slot by typed identities. - * - * @param variant variant identity - * @param slot slot identity inside the variant - * @return matching slot when present - */ - Optional findSlot(Variant variant, Slot slot); - - /** - * Requires a slot by typed identities. - * - * @param variant variant identity - * @param slot slot identity inside the variant - * @return matching slot - */ - ArtifactSlot requireSlot(Variant variant, Slot slot); - -} diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/OutgoingArtifactSlotSpec.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/OutgoingArtifactSlotSpec.java --- a/variants/src/main/java/org/implab/gradle/variants/artifacts/OutgoingArtifactSlotSpec.java +++ b/variants/src/main/java/org/implab/gradle/variants/artifacts/OutgoingArtifactSlotSpec.java @@ -10,7 +10,7 @@ import org.implab.gradle.common.core.lan * Materialized outgoing publication state of a single slot. * *

This type is a DSL facade to represent already created publication-facing state. Slot-specific - * publication tweaks should be applied here rather than through {@link OutgoingVariantSpec}, which + * publication tweaks should be applied here rather than through {@link OutgoingConfigurationSpec}, which * is limited to the root outgoing configuration of the variant. */ public interface OutgoingArtifactSlotSpec { diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/VariantArtifactsConfiguration.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/OutgoingConfiguration.java rename from variants/src/main/java/org/implab/gradle/variants/artifacts/VariantArtifactsConfiguration.java rename to variants/src/main/java/org/implab/gradle/variants/artifacts/OutgoingConfiguration.java --- a/variants/src/main/java/org/implab/gradle/variants/artifacts/VariantArtifactsConfiguration.java +++ b/variants/src/main/java/org/implab/gradle/variants/artifacts/OutgoingConfiguration.java @@ -3,10 +3,11 @@ package org.implab.gradle.variants.artif import org.gradle.api.NamedDomainObjectContainer; import org.gradle.api.NamedDomainObjectProvider; import org.gradle.api.artifacts.Configuration; +import org.gradle.api.provider.Property; import org.implab.gradle.variants.core.Variant; -/** Описывает конфигурация варианта исходящей конфигурации */ -public interface VariantArtifactsConfiguration { +/** Описывает исходящую конфигурацию варианта */ +public interface OutgoingConfiguration { /** * Исходный вариант для которого строится Outgoing конфигурация */ @@ -25,4 +26,13 @@ public interface VariantArtifactsConfigu * @see {@link ArtifactSlot} */ NamedDomainObjectContainer getSlots(); + + /** + * Основной набор артефактов (primary variant) для исходящей конфигурации + * + *

+ * Если в свойстве {@link #getSlots()} есть только один слой, то по конвенции он + * считается также основным. + */ + Property getPrimarySlot(); } diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/OutgoingVariantSpec.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/OutgoingConfigurationSpec.java rename from variants/src/main/java/org/implab/gradle/variants/artifacts/OutgoingVariantSpec.java rename to variants/src/main/java/org/implab/gradle/variants/artifacts/OutgoingConfigurationSpec.java --- a/variants/src/main/java/org/implab/gradle/variants/artifacts/OutgoingVariantSpec.java +++ b/variants/src/main/java/org/implab/gradle/variants/artifacts/OutgoingConfigurationSpec.java @@ -13,7 +13,7 @@ import groovy.lang.Closure; *

This is a variant-level publication hook. Slot-specific publication state is exposed separately via * {@link OutgoingArtifactSlotSpec}. */ -public interface OutgoingVariantSpec { +public interface OutgoingConfigurationSpec { /** * Returns the variant whose outgoing configuration is represented here. * diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/VariantArtifactsContext.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/VariantArtifactsContext.java --- a/variants/src/main/java/org/implab/gradle/variants/artifacts/VariantArtifactsContext.java +++ b/variants/src/main/java/org/implab/gradle/variants/artifacts/VariantArtifactsContext.java @@ -18,10 +18,17 @@ public interface VariantArtifactsContext */ VariantsView getVariants(); - void all(Action action); + ArtifactAssemblies getAssemblies(); - Optional findArtifacts(Variant variant); + /** + * Replayable hook для всех объявленных конфигураций + */ + void all(Action action); - VariantArtifactsConfiguration requireArtifacts(Variant variant); + Optional findArtifacts(Variant variant); + + OutgoingConfiguration requireArtifacts(Variant variant); + + ArtifactAssemblyRules slotRules(ArtifactSlot slot); } diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/VariantArtifactsExtension.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/VariantArtifactsExtension.java --- a/variants/src/main/java/org/implab/gradle/variants/artifacts/VariantArtifactsExtension.java +++ b/variants/src/main/java/org/implab/gradle/variants/artifacts/VariantArtifactsExtension.java @@ -40,7 +40,7 @@ public interface VariantArtifactsExtensi * * @param action variant-level outgoing configuration callback */ - void whenOutgoingVariant(Action action); + void whenOutgoingVariant(Action action); default void whenOutgoingVariant(Closure closure) { whenOutgoingVariant(Closures.action(closure)); diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/VariantArtifactsSpec.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/VariantArtifactsSpec.java --- a/variants/src/main/java/org/implab/gradle/variants/artifacts/VariantArtifactsSpec.java +++ b/variants/src/main/java/org/implab/gradle/variants/artifacts/VariantArtifactsSpec.java @@ -19,10 +19,10 @@ public interface VariantArtifactsSpec { * @param action slot declaration * @return slot identity */ - Slot slot(String name, Action action); + void slot(String name, Action action); - default Slot slot(String name, Closure closure) { - return slot(name, Closures.action(closure)); + default void slot(String name, Closure closure) { + slot(name, Closures.action(closure)); } /** @@ -32,9 +32,9 @@ public interface VariantArtifactsSpec { * @param action slot declaration * @return slot identity */ - Slot primarySlot(String name, Action action); + void primarySlot(String name, Action action); - default Slot primarySlot(String name, Closure closure) { - return primarySlot(name, Closures.action(closure)); + default void primarySlot(String name, Closure closure) { + primarySlot(name, Closures.action(closure)); } } diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/ArtifactInputsResolver.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/ArtifactInputsResolver.java new file mode 100644 --- /dev/null +++ b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/ArtifactInputsResolver.java @@ -0,0 +1,10 @@ +package org.implab.gradle.variants.artifacts.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.gradle.api.Action; +import org.implab.gradle.variants.artifacts.ArtifactSlot; + +@NonNullByDefault +interface ArtifactInputsResolver { + void observeInputs(ArtifactSlot slot, Action action); +} \ No newline at end of file diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/BoundArtifactAssemblySpec.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/BoundArtifactAssemblySpec.java new file mode 100644 --- /dev/null +++ b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/BoundArtifactAssemblySpec.java @@ -0,0 +1,52 @@ +package org.implab.gradle.variants.artifacts.internal; + +import java.util.Set; + +import org.gradle.api.Action; +import org.implab.gradle.variants.artifacts.ArtifactAssemblyRules; +import org.implab.gradle.variants.artifacts.ArtifactAssemblySpec; +import org.implab.gradle.variants.core.Variant; +import org.implab.gradle.variants.artifacts.OutputSelectionSpec; + +final class BoundArtifactAssemblySpec implements ArtifactAssemblySpec { + private final VariantArtifactsRegistry registry; + private final Variant variant; + private final ArtifactAssemblyRules rules; + + BoundArtifactAssemblySpec( + VariantArtifactsRegistry registry, + Variant variant, + ArtifactAssemblyRules rules) { + this.registry = registry; + this.variant = variant; + this.rules = rules; + } + + @Override + public void from(Object artifact) { + rules.addDirectInput(artifact); + } + + @Override + public void fromVariant(Action action) { + rules.addVariantOutputs(outputs(action)); + } + + @Override + public void fromRole(String roleName, Action action) { + var role = registry.requireRole(variant, roleName); + rules.addRoleOutputs(role, outputs(action)); + } + + @Override + public void fromLayer(String layerName, Action action) { + var layer = registry.requireLayer(variant, layerName); + rules.addLayerOutputs(layer, outputs(action)); + } + + private static Set outputs(Action action) { + var spec = new DefaultOutputSelectionSpec(); + action.execute(spec); + return spec.outputs(); + } +} diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/BoundVariantArtifactsSpec.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/BoundVariantArtifactsSpec.java new file mode 100644 --- /dev/null +++ b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/BoundVariantArtifactsSpec.java @@ -0,0 +1,39 @@ +package org.implab.gradle.variants.artifacts.internal; + +import org.gradle.api.Action; +import org.implab.gradle.variants.artifacts.VariantArtifactsSpec; +import org.implab.gradle.variants.artifacts.ArtifactAssemblySpec; +import org.implab.gradle.variants.artifacts.ArtifactSlot; + +/** + * DSL фасад для описания исходящей конфигурации варианта + */ +final class BoundVariantArtifactsSpec implements VariantArtifactsSpec { + private final VariantArtifactsRegistry registry; + private final DefaultOutgoingConfiguration outgoing; + + BoundVariantArtifactsSpec(VariantArtifactsRegistry registry, DefaultOutgoingConfiguration outgoing) { + this.registry = registry; + this.outgoing = outgoing; + } + + @Override + public void slot(String name, Action action) { + var slot = outgoing.getSlots().maybeCreate(name); + var artifactSlot = new ArtifactSlot(outgoing.getVariant(), slot); + var rules = registry.slotRules(artifactSlot); + + action.execute(new BoundArtifactAssemblySpec(registry, outgoing.getVariant(), rules)); + } + + @Override + public void primarySlot(String name, Action action) { + var slot = outgoing.getSlots().maybeCreate(name); + outgoing.getPrimarySlot().set(slot); + + var artifactSlot = new ArtifactSlot(outgoing.getVariant(), slot); + var rules = registry.slotRules(artifactSlot); + + action.execute(new BoundArtifactAssemblySpec(registry, outgoing.getVariant(), rules)); + } +} diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/CompileUnitOutputKey.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/CompileUnitOutputKey.java new file mode 100644 --- /dev/null +++ b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/CompileUnitOutputKey.java @@ -0,0 +1,8 @@ +package org.implab.gradle.variants.artifacts.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.implab.gradle.variants.sources.CompileUnit; + +@NonNullByDefault +record CompileUnitOutputKey(CompileUnit unit, String outputName) implements SlotInputKey { +} diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/DefaultArtifactAssemblies.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/DefaultArtifactAssemblies.java new file mode 100644 --- /dev/null +++ b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/DefaultArtifactAssemblies.java @@ -0,0 +1,51 @@ +package org.implab.gradle.variants.artifacts.internal; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.gradle.api.file.ProjectLayout; +import org.gradle.api.model.ObjectFactory; +import org.gradle.api.tasks.TaskContainer; +import org.implab.gradle.variants.artifacts.ArtifactAssemblies; +import org.implab.gradle.variants.artifacts.ArtifactAssembly; +import org.implab.gradle.variants.artifacts.ArtifactSlot; +import org.implab.gradle.variants.sources.VariantSourcesContext; + +@NonNullByDefault +final class DefaultArtifactAssemblies implements ArtifactAssemblies { + private final ArtifactInputsResolver inputs; + private final ObjectFactory objects; + + private final VariantArtifactsRegistry registry; + private final VariantSourcesContext sources; + private final TaskContainer tasks; + private final ProjectLayout layout; + + private final Map assemblies = new LinkedHashMap<>(); + + DefaultArtifactAssemblies( + VariantArtifactsRegistry registry, + VariantSourcesContext sources, + TaskContainer tasks, + ProjectLayout layout) { + this.registry = registry; + this.sources = sources; + this.tasks = tasks; + this.layout = layout; + } + + @Override + public ArtifactAssembly resolveSlot(ArtifactSlot slot) { + return assemblies.computeIfAbsent(slot, this::createAssembly); + } + + private ArtifactAssembly createAssembly(ArtifactSlot slot) { + var files = objects.fileCollection(); + + inputs.observeInputs(slot, input -> files.from(input.input())); + + // register task + wire live inputs + // return DefaultArtifactAssembly(...) + } +} diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/DefaultArtifactAssemblyRules.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/DefaultArtifactAssemblyRules.java new file mode 100644 --- /dev/null +++ b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/DefaultArtifactAssemblyRules.java @@ -0,0 +1,42 @@ +package org.implab.gradle.variants.artifacts.internal; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.gradle.api.Action; +import org.implab.gradle.variants.artifacts.ArtifactAssemblyRules; +import org.implab.gradle.variants.core.Layer; +import org.implab.gradle.variants.core.Role; + +@NonNullByDefault +final class DefaultArtifactAssemblyRules implements ArtifactAssemblyRules { + private final List contributions = new ArrayList<>(); + + @Override + public void addDirectInput(Object input) { + contributions.add(new DirectContribution(Objects.requireNonNull(input, "input"))); + } + + @Override + public void addVariantOutputs(Set outputs) { + contributions.add(new VariantOutputsContribution(Set.copyOf(outputs))); + } + + @Override + public void addRoleOutputs(Role role, Set outputs) { + contributions.add(new RoleOutputsContribution(role, Set.copyOf(outputs))); + } + + @Override + public void addLayerOutputs(Layer layer, Set outputs) { + contributions.add(new LayerOutputsContribution(layer, Set.copyOf(outputs))); + } + + @Override + public void all(Action action) { + contributions.forEach(action::execute); + } +} diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/DefaultArtifactInputsResolver.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/DefaultArtifactInputsResolver.java new file mode 100644 --- /dev/null +++ b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/DefaultArtifactInputsResolver.java @@ -0,0 +1,116 @@ +package org.implab.gradle.variants.artifacts.internal; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.gradle.api.Action; +import org.implab.gradle.internal.ReplayableQueue; +import org.implab.gradle.variants.artifacts.ArtifactSlot; +import org.implab.gradle.variants.sources.CompileUnit; +import org.implab.gradle.variants.sources.VariantSourcesContext; + +@NonNullByDefault +final class DefaultArtifactInputsResolver implements ArtifactInputsResolver { + private final VariantArtifactsRegistry registry; + private final VariantSourcesContext sources; + private final SlotInputDedupPolicy dedupPolicy; + + private final Map> resolved = new LinkedHashMap<>(); + + DefaultArtifactInputsResolver( + VariantArtifactsRegistry registry, + VariantSourcesContext sources, + SlotInputDedupPolicy dedupPolicy) { + this.registry = registry; + this.sources = sources; + this.dedupPolicy = dedupPolicy; + } + + /** + * ставит replayble-hook на registry.slotRules(slot).all(action) + */ + @Override + public void observeInputs(ArtifactSlot slot, Action action) { + resolvedInputs(slot).forEach(action::execute); + } + + private ReplayableQueue resolvedInputs(ArtifactSlot slot) { + return resolved.computeIfAbsent(slot, this::bindSlot); + } + + private ReplayableQueue bindSlot(ArtifactSlot slot) { + var queue = new ReplayableQueue(); + var filter = dedupPolicy.newFilter(slot); + + // TODO: жесть какая-то нужно переписать + registry.slotRules(slot).all(contribution -> { + var processor = new ContributionProcessor(slot, input -> { + if (filter.accept(input)) { + queue.add(input); + } + }); + contribution.accept(processor); + }); + + return queue; + } + + private void emitUnitOutputs( + ArtifactSlot slot, + Set units, + Set outputs, + Consumer sink) { + for (var unit : units) { + var sourceSet = sources.getSourceSets().getSourceSet(unit); + for (var output : outputs) { + sink.accept(new ResolvedSlotInput( + slot, + new CompileUnitOutputKey(unit, output), + sourceSet.map(ss -> ss.output(output)))); + } + } + } + + + + class ContributionProcessor implements SlotContributionVisitor { + private final ArtifactSlot slot; + private final Consumer sink; + + ContributionProcessor(ArtifactSlot slot, Consumer sink) { + this.slot = slot; + this.sink = sink; + } + + @Override + public void visit(DirectContribution contribution) { + sink.accept(new ResolvedSlotInput( + slot, + SlotInputKey.newUniqueKey("Direct slot for " + slot), + contribution.input())); + } + + @Override + public void visit(VariantOutputsContribution contribution) { + var units = sources.getCompileUnits().getUnitsForVariant(slot.variant()); + emitUnitOutputs(slot, units, contribution.outputs(), sink); + } + + @Override + public void visit(RoleOutputsContribution contribution) { + var projection = sources.getRoleProjections().getProjection(slot.variant(), contribution.role()); + var units = sources.getRoleProjections().getUnits(projection); + emitUnitOutputs(slot, units, contribution.outputs(), sink); + } + + @Override + public void visit(LayerOutputsContribution contribution) { + var unit = sources.getCompileUnits().requireUnit(slot.variant(), contribution.layer()); + emitUnitOutputs(slot, Set.of(unit), contribution.outputs(), sink); + } + + } +} diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/DefaultOutgoingConfiguration.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/DefaultOutgoingConfiguration.java new file mode 100644 --- /dev/null +++ b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/DefaultOutgoingConfiguration.java @@ -0,0 +1,62 @@ +package org.implab.gradle.variants.artifacts.internal; + +import org.gradle.api.NamedDomainObjectContainer; +import org.gradle.api.NamedDomainObjectProvider; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.model.ObjectFactory; +import org.gradle.api.provider.Property; +import org.gradle.api.provider.ProviderFactory; +import org.gradle.api.provider.Provider; +import org.implab.gradle.variants.artifacts.OutgoingConfiguration; +import org.implab.gradle.variants.artifacts.Slot; +import org.implab.gradle.variants.core.Variant; + +class DefaultOutgoingConfiguration implements OutgoingConfiguration { + + private final Variant variant; + + private final NamedDomainObjectProvider configurationProvider; + + private final NamedDomainObjectContainer slots; + + private final Property primarySlot; + + public DefaultOutgoingConfiguration( + Variant variant, + NamedDomainObjectProvider configurationProvider, + ObjectFactory objectFactory, + ProviderFactory providerFactory) { + this.variant = variant; + this.configurationProvider = configurationProvider; + this.slots = objectFactory.domainObjectContainer(Slot.class); + this.primarySlot = objectFactory.property(Slot.class) + .convention(providerFactory + .provider(this::primarySlotConvention) + .flatMap(x -> x)); + } + + @Override + public Property getPrimarySlot() { + return primarySlot; + } + + @Override + public Variant getVariant() { + return variant; + } + + @Override + public NamedDomainObjectProvider getOutgoingConfiguration() { + return configurationProvider; + } + + @Override + public NamedDomainObjectContainer getSlots() { + return slots; + } + + private Provider primarySlotConvention() { + return slots.size() == 1 ? slots.named(slots.getNames().first()) : null; + } + +} diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/DefaultVariantArtifactsContext.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/DefaultVariantArtifactsContext.java deleted file mode 100644 --- a/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/DefaultVariantArtifactsContext.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.implab.gradle.variants.artifacts.internal; - -import java.util.Optional; - -import org.gradle.api.Action; -import org.implab.gradle.variants.artifacts.VariantArtifactsConfiguration; -import org.implab.gradle.variants.artifacts.VariantArtifactsContext; -import org.implab.gradle.variants.core.Variant; -import org.implab.gradle.variants.core.VariantsView; - -public class DefaultVariantArtifactsContext implements VariantArtifactsContext { - - @Override - public VariantsView getVariants() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getVariants'"); - } - - @Override - public void all(Action action) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'all'"); - } - - @Override - public Optional findArtifacts(Variant variant) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'findArtifacts'"); - } - - @Override - public VariantArtifactsConfiguration requireArtifacts(Variant variant) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'requireArtifacts'"); - } - -} diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/DirectContribution.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/DirectContribution.java new file mode 100644 --- /dev/null +++ b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/DirectContribution.java @@ -0,0 +1,11 @@ +package org.implab.gradle.variants.artifacts.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +@NonNullByDefault +record DirectContribution(Object input) implements SlotContribution { + @Override + public void accept(SlotContributionVisitor visitor) { + visitor.visit(this); + } +} diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/LayerOutputsContribution.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/LayerOutputsContribution.java new file mode 100644 --- /dev/null +++ b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/LayerOutputsContribution.java @@ -0,0 +1,14 @@ +package org.implab.gradle.variants.artifacts.internal; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.implab.gradle.variants.core.Layer; + +@NonNullByDefault +record LayerOutputsContribution(Layer layer, Set outputs) implements SlotContribution { + @Override + public void accept(SlotContributionVisitor visitor) { + visitor.visit(this); + } +} diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/ResolvedSlotInput.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/ResolvedSlotInput.java new file mode 100644 --- /dev/null +++ b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/ResolvedSlotInput.java @@ -0,0 +1,11 @@ +package org.implab.gradle.variants.artifacts.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.implab.gradle.variants.artifacts.ArtifactSlot; + +@NonNullByDefault +record ResolvedSlotInput( + ArtifactSlot slot, + SlotInputKey key, + Object input) { +} diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/RoleOutputsContribution.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/RoleOutputsContribution.java new file mode 100644 --- /dev/null +++ b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/RoleOutputsContribution.java @@ -0,0 +1,14 @@ +package org.implab.gradle.variants.artifacts.internal; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.implab.gradle.variants.core.Role; + +@NonNullByDefault +record RoleOutputsContribution(Role role, Set outputs) implements SlotContribution { + @Override + public void accept(SlotContributionVisitor visitor) { + visitor.visit(this); + } +} diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/SlotContribution.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/SlotContribution.java new file mode 100644 --- /dev/null +++ b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/SlotContribution.java @@ -0,0 +1,9 @@ +package org.implab.gradle.variants.artifacts.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +@NonNullByDefault +public interface SlotContribution { + + void accept(SlotContributionVisitor visitor); +} diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/SlotContributionVisitor.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/SlotContributionVisitor.java new file mode 100644 --- /dev/null +++ b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/SlotContributionVisitor.java @@ -0,0 +1,11 @@ +package org.implab.gradle.variants.artifacts.internal; + +public interface SlotContributionVisitor { + void visit(DirectContribution contribution); + + void visit(VariantOutputsContribution contribution); + + void visit(RoleOutputsContribution contribution); + + void visit(LayerOutputsContribution contribution); +} diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/SlotInputDedupPolicy.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/SlotInputDedupPolicy.java new file mode 100644 --- /dev/null +++ b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/SlotInputDedupPolicy.java @@ -0,0 +1,9 @@ +package org.implab.gradle.variants.artifacts.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.implab.gradle.variants.artifacts.ArtifactSlot; + +@NonNullByDefault +interface SlotInputDedupPolicy { + SlotInputFilter newFilter(ArtifactSlot slot); +} diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/SlotInputFilter.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/SlotInputFilter.java new file mode 100644 --- /dev/null +++ b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/SlotInputFilter.java @@ -0,0 +1,8 @@ +package org.implab.gradle.variants.artifacts.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +@NonNullByDefault +interface SlotInputFilter { + boolean accept(ResolvedSlotInput input); +} diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/SlotInputKey.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/SlotInputKey.java new file mode 100644 --- /dev/null +++ b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/SlotInputKey.java @@ -0,0 +1,20 @@ +package org.implab.gradle.variants.artifacts.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +@NonNullByDefault +interface SlotInputKey { + + static SlotInputKey newUniqueKey(String hint) { + return new SlotInputKey() { + @Override + public String toString() { + return hint; + } + }; + } + + static SlotInputKey newUniqueKey() { + return newUniqueKey("unnamed"); + } +} diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/VariantArtifactsRegistry.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/VariantArtifactsRegistry.java --- a/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/VariantArtifactsRegistry.java +++ b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/VariantArtifactsRegistry.java @@ -1,11 +1,105 @@ package org.implab.gradle.variants.artifacts.internal; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Optional; + +import org.eclipse.jdt.annotation.NonNullByDefault; import org.gradle.api.Action; +import org.gradle.api.InvalidUserDataException; +import org.gradle.api.file.ProjectLayout; +import org.gradle.api.model.ObjectFactory; +import org.gradle.api.tasks.TaskContainer; +import org.implab.gradle.variants.artifacts.ArtifactAssemblies; +import org.implab.gradle.variants.artifacts.ArtifactAssemblyRules; +import org.implab.gradle.variants.artifacts.ArtifactSlot; +import org.implab.gradle.variants.artifacts.Slot; +import org.implab.gradle.variants.artifacts.OutgoingConfiguration; +import org.implab.gradle.variants.artifacts.VariantArtifactsContext; import org.implab.gradle.variants.artifacts.VariantArtifactsSpec; import org.implab.gradle.variants.core.Variant; +import org.implab.gradle.variants.core.VariantsView; +import org.implab.gradle.variants.sources.VariantSourcesContext; -public class VariantArtifactsRegistry { - public void configureVariant(Variant variant, Action action) { +@NonNullByDefault +public final class VariantArtifactsRegistry { + private final VariantsView variants; + private final ArtifactAssemblies assemblies; + + private final Map outgoingByVariant = new LinkedHashMap<>(); + private final Map rulesBySlot = new LinkedHashMap<>(); + + private final VariantArtifactsContext context = new ContextView(); + + + VariantArtifactsRegistry( + ObjectFactory objects, + VariantsView variants, + VariantSourcesContext sources, + TaskContainer tasks, + ProjectLayout layout) { + this.variants = variants; + this.assemblies = new DefaultArtifactAssemblies(this, sources, tasks, layout); + } + + public VariantArtifactsContext context() { + return context; + } + + + ArtifactAssemblyRules slotRules(ArtifactSlot slot) { + assertDeclaredSlot(slot); + return rulesBySlot.computeIfAbsent(slot, key -> new DefaultArtifactAssemblyRules()); + } + + void configureVariant(Variant variant, Action action) { + var outgoing = outgoingByVariant.computeIfAbsent(variant, this::newOutgoingConfiguration); + action.execute(new BoundVariantArtifactsSpec(this, outgoing)); + } + + private DefaultOutgoingConfiguration newOutgoingConfiguration(Variant variant) { + // register Elements eagerly + } + private void assertDeclaredSlot(ArtifactSlot slot) { + var outgoing = context.requireArtifacts(slot.variant()); + if (outgoing.getSlots().findByName(slot.slot().getName()) == null) { + throw new InvalidUserDataException( + "Slot '" + slot.slot().getName() + "' isn't declared for variant '" + slot.variant().getName() + "'"); + } + } + + private final class ContextView implements VariantArtifactsContext { + @Override + public VariantsView getVariants() { + return variants; + } + + @Override + public ArtifactAssemblies getAssemblies() { + return assemblies; + } + + @Override + public void all(Action action) { + outgoingByVariant.values().forEach(action::execute); + } + + @Override + public Optional findArtifacts(Variant variant) { + return Optional.ofNullable(outgoingByVariant.get(variant)); + } + + @Override + public OutgoingConfiguration requireArtifacts(Variant variant) { + return findArtifacts(variant) + .orElseThrow(() -> new InvalidUserDataException( + "Outgoing configuration for variant '" + variant.getName() + "' isn't declared")); + } + + @Override + public ArtifactAssemblyRules slotRules(ArtifactSlot slot) { + return VariantArtifactsRegistry.this.slotRules(slot); + } } } diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/VariantOutputsContribution.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/VariantOutputsContribution.java new file mode 100644 --- /dev/null +++ b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/VariantOutputsContribution.java @@ -0,0 +1,13 @@ +package org.implab.gradle.variants.artifacts.internal; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +@NonNullByDefault +record VariantOutputsContribution(Set outputs) implements SlotContribution { + @Override + public void accept(SlotContributionVisitor visitor) { + visitor.visit(this); + } +} diff --git a/variants/src/main/java/org/implab/gradle/variants/core/VariantsView.java b/variants/src/main/java/org/implab/gradle/variants/core/VariantsView.java --- a/variants/src/main/java/org/implab/gradle/variants/core/VariantsView.java +++ b/variants/src/main/java/org/implab/gradle/variants/core/VariantsView.java @@ -60,6 +60,13 @@ public class VariantsView { return roles; } + public void assertRole(Role role) { + Objects.requireNonNull(role, "The role can't be null"); + + if (!roles.contains(role)) + throw new InvalidUserDataException("The specified role '" + role.getName() + "' isn't declared"); + } + /** * Returns all declared variants included in this view. */