# HG changeset patch # User cin # Date 2026-04-19 20:58:53 # Node ID 9db7822cd26c6115c7a7cc8704b6fe1d65ed416c # Parent ca3982e55d9e4b24623cc549bc2dc2da878ca22c Rework variant artifacts materialization model Refactor VariantArtifactsPlugin around a live outgoing artifacts context and split artifact publication into explicit internal services: outgoing variant registry, assembly binding, materialization policy hooks, primary-slot convention, and slot assembly handling. Introduce variant artifact slots as identity-first public API and expose materialized assembly handles through ArtifactAssemblies. Add replayable configuration hooks for outgoing configurations, outgoing slots, outgoing variants, and registered assemblies. Create consumable outgoing configurations per variant, bind the primary slot to the root outgoing artifact set, and publish non-primary slots as Gradle outgoing configuration variants. Add deterministic injective task names for slot assembly tasks, use Sync for directory assembly, and configure the default assembly output location under build/variant-assemblies. Make primary-slot selection finalize-on-read and provide a single-slot convention that fails when no unique default can be inferred. Mark artifact internal implementation package as non-public API. diff --git a/AGENTS.md b/AGENTS.md --- a/AGENTS.md +++ b/AGENTS.md @@ -2,6 +2,8 @@ ## Проектные договоренности +- для управления исходным кодом используется Mercurial + ### Публичное API библиотек - Предпочтителен `non-null` подход. diff --git a/common/src/main/java/org/implab/gradle/common/core/lang/Strings.java b/common/src/main/java/org/implab/gradle/common/core/lang/Strings.java --- a/common/src/main/java/org/implab/gradle/common/core/lang/Strings.java +++ b/common/src/main/java/org/implab/gradle/common/core/lang/Strings.java @@ -1,7 +1,5 @@ package org.implab.gradle.common.core.lang; -import java.util.function.Consumer; -import java.util.function.Function; import java.util.regex.Pattern; import org.eclipse.jdt.annotation.NonNullByDefault; diff --git a/design_notes.md b/design_notes.md --- a/design_notes.md +++ b/design_notes.md @@ -17,6 +17,7 @@ - artifact: directory - customTask - artifact: FileSystemLocation + - when, all - replayable hooks для получения содержимого сборки ## extension @@ -30,16 +31,8 @@ - whenOutgoingConfiguration - whenOutgoingSlot -outgoing = outgoings.maybeCreate(variant) - -slot = outgoings.slots.maybeCreate(slotName) -assembly = assemblies.register(variantSlot, task, mapOutput) -outgoing.configure(configuration -> { - slots.all(slot -> { - assemblies.when(variant, slot) { assembly -> - configuration.variants.create(slot.name) { - artifact(assembly.artifact) - } - } - }); -}); \ No newline at end of file +- ArtifactAssemblyBridge + связывает содержимое ArtifactAssembly и исходящей конфигурации OutgoingVariant +- VariantArtifactsHandler + управляет состоянием ArtifactAssembly, используется для DSL сборки + - configureVariant конфигурирует VariantArtifactsSpec \ No newline at end of file diff --git a/variants/src/main/java/org/implab/gradle/internal/ReplayableQueue.java b/variants/src/main/java/org/implab/gradle/internal/ReplayableQueue.java --- a/variants/src/main/java/org/implab/gradle/internal/ReplayableQueue.java +++ b/variants/src/main/java/org/implab/gradle/internal/ReplayableQueue.java @@ -4,6 +4,9 @@ import java.util.LinkedList; import java.util.List; import java.util.function.Consumer; +import org.eclipse.jdt.annotation.NonNullByDefault; + +@NonNullByDefault public class ReplayableQueue { private final List> consumers = new LinkedList<>(); private final List values = new LinkedList<>(); 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 @@ -4,16 +4,21 @@ 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.ArtifactAssemblyRegistry; import org.implab.gradle.variants.artifacts.OutgoingConfigurationSpec; import org.implab.gradle.variants.artifacts.OutgoingVariantsContext; import org.implab.gradle.variants.artifacts.VariantArtifactsExtension; import org.implab.gradle.variants.artifacts.VariantArtifactsSpec; -import org.implab.gradle.variants.artifacts.internal.ArtifactAssemblyBridge; -import org.implab.gradle.variants.artifacts.internal.DefaultVariantArtifactSpec; +import org.implab.gradle.variants.artifacts.internal.ArtifactAssemblyBinder; +import org.implab.gradle.variants.artifacts.internal.ArtifactAssemblyHandler; +import org.implab.gradle.variants.artifacts.internal.ArtifactAssemblyRegistry; +import org.implab.gradle.variants.artifacts.internal.DefaultOutgoingVariantsContext; +import org.implab.gradle.variants.artifacts.internal.MaterializationPolicyHandler; import org.implab.gradle.variants.artifacts.internal.OutgoingRegistry; +import org.implab.gradle.variants.artifacts.internal.SingleSlotConvention; import org.implab.gradle.variants.core.Variant; import org.implab.gradle.variants.core.VariantsExtension; +import org.implab.gradle.variants.sources.VariantSourcesExtension; +import org.implab.gradle.variants.artifacts.OutgoingConfigurationSlotSpec; public abstract class VariantArtifactsPlugin implements Plugin { @@ -22,25 +27,53 @@ public abstract class VariantArtifactsPl var extensions = target.getExtensions(); var objects = target.getObjects(); var providers = target.getProviders(); + var plugins = target.getPlugins(); var configurations = target.getConfigurations(); var tasks = target.getTasks(); + var layout = target.getLayout(); // Apply the main VariantsPlugin to ensure the core variant model is available. - target.getPlugins().apply(VariantsPlugin.class); + plugins.apply(VariantsPlugin.class); + plugins.apply(VariantSourcesPlugin.class); // Access the VariantsExtension to configure variant sources. var variantsExtension = extensions.getByType(VariantsExtension.class); - - var outgoing = new OutgoingRegistry(configurations, objects, providers); - var assemblies = new ArtifactAssemblyRegistry(objects, tasks); - - // wire artifact assemblies to configuration variants - var assembliesBridge = new ArtifactAssemblyBridge(assemblies); + var sourcesExtension = extensions.getByType(VariantSourcesExtension.class); var deferred = new Deferred(); - deferred.whenResolved(context -> context.all(assembliesBridge)); + variantsExtension.whenFinalized(variants -> { + + var outgoing = new OutgoingRegistry(configurations, objects, variants.getVariants()); + var assemblies = new ArtifactAssemblyRegistry(); + + // wire artifact assemblies to configuration variants + var assembliesBridge = new ArtifactAssemblyBinder(assemblies); + var primarySlotConvention = new SingleSlotConvention(providers); + var materializationHandler = new MaterializationPolicyHandler(assemblies); + + // bind slot assemblies to outgoing variants + outgoing.configureEach(assembliesBridge::execute); + // apply primary slot convention when outgoing variant has a single slot + outgoing.configureEach(primarySlotConvention::execute); + // apply materialization policy hooks to outgoing variants + outgoing.configureEach(materializationHandler::execute); - variantsExtension.whenFinalized(variants -> { + sourcesExtension.whenFinalized(sources -> { + var assemblyHandler = new ArtifactAssemblyHandler( + objects, + tasks, + assemblies, + sources.getCompileUnits(), + sources.getRoleProjections(), + sources.getSourceSets()); + assemblyHandler.getAssembliesDirectory().set(layout.getBuildDirectory().dir("variant-assemblies")); + + deferred.resolve(new DefaultOutgoingVariantsContext( + assemblies, + outgoing, + assemblyHandler, + materializationHandler)); + }); }); @@ -49,24 +82,25 @@ public abstract class VariantArtifactsPl @Override public void variant(String variantName, Action action) { deferred.whenResolved(context -> { - new DefaultVariantArtifactSpec(); + var variant = objects.named(Variant.class, variantName); + context.configureVariant(variant, action); }); } @Override - public void whenFinalized(Action action) { - deferred.whenResolved(registry -> action.execute(registry.variantsContext())); + public void whenAvailable(Action action) { + deferred.whenResolved(handler -> action.execute(handler)); } @Override - public void whenOutgoingVariant(Action action) { - deferred.whenResolved(registry -> registry.configureOutgoing(action)); + public void whenOutgoingConfiguration(Action action) { + deferred.whenResolved(registry -> registry.whenOutgoingConfiguration(action)); } @Override - public void whenOutgoingSlot(Action action) { - deferred.whenResolved(registry -> registry.configureOutgoingSlot(action)); + public void whenOutgoingSlot(Action action) { + deferred.whenResolved(registry -> registry.whenOutgoingSlot(action)); } }; diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/ArtifactAssemblies.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/ArtifactAssemblies.java --- a/variants/src/main/java/org/implab/gradle/variants/artifacts/ArtifactAssemblies.java +++ b/variants/src/main/java/org/implab/gradle/variants/artifacts/ArtifactAssemblies.java @@ -31,23 +31,25 @@ public interface ArtifactAssemblies { } /** - * Регистрирует обработчик на конкретный слот. Если слот еще не зарегистрирован, - * то обработчик будет добавлен в очередь и будет вызван при регистрации слота. - * Порядок и точный момент вызова обработчиков не определен. + * Registers a configuration action for the assembly of the given slot. * - * @param slot Слот на который нужно зарегистрировать обработчик - * @param action Обработчик + *

If the assembly is already registered, the action is invoked immediately. + * Otherwise, it is queued and invoked when slot materialization registers the + * assembly handle. + * + * @param slot slot identity inside a variant outgoing contract + * @param action assembly configuration action */ void when(ArtifactSlot slot, Action action); /** - * Регистрирует глобальный обработчик, который будет вызван для всех слотов. - * Обработчик будет вызван как для уже зарегистрированных слотов так и для тех, - * которые будут зарегистрированы в будущем. + * Adds global configuration action for all materialized assemblies. If some assemblies are + * already registered, the action will be invoked for them immediately. For assemblies that + * are materialized later, the action will be invoked when they are registered. * - * @param action Обработчик + * @param action assembly configuration action. */ - void all(Action action); + void configureEach(Action action); /** Ищет зарегистрированный слот */ Optional find(ArtifactSlot slot); 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 deleted file mode 100644 --- a/variants/src/main/java/org/implab/gradle/variants/artifacts/ArtifactAssemblyRules.java +++ /dev/null @@ -1,19 +0,0 @@ -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/OutgoingConfigurationSlotSpec.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/OutgoingConfigurationSlotSpec.java --- a/variants/src/main/java/org/implab/gradle/variants/artifacts/OutgoingConfigurationSlotSpec.java +++ b/variants/src/main/java/org/implab/gradle/variants/artifacts/OutgoingConfigurationSlotSpec.java @@ -40,7 +40,9 @@ public interface OutgoingConfigurationSl * * @param action task configuration action */ - void assemblyTask(Action action); + default void assemblyTask(Action action) { + getAssembly().getAssemblyTask().configure(action); + } default void assemblyTask(Closure closure) { assemblyTask(Closures.action(closure)); diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/OutgoingVariant.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/OutgoingVariant.java --- a/variants/src/main/java/org/implab/gradle/variants/artifacts/OutgoingVariant.java +++ b/variants/src/main/java/org/implab/gradle/variants/artifacts/OutgoingVariant.java @@ -24,9 +24,9 @@ public interface OutgoingVariant { /** * Провайдер зарегистрированной конфигурации */ - NamedDomainObjectProvider getConfiguration(); + NamedDomainObjectProvider getConfiguration(); - default void configure(Action action) { + default void configureOutgoing(Action action) { getConfiguration().configure(action); } diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/OutgoingVariantsContext.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/OutgoingVariantsContext.java --- a/variants/src/main/java/org/implab/gradle/variants/artifacts/OutgoingVariantsContext.java +++ b/variants/src/main/java/org/implab/gradle/variants/artifacts/OutgoingVariantsContext.java @@ -5,7 +5,6 @@ import java.util.Optional; import org.gradle.api.Action; import org.gradle.api.InvalidUserDataException; import org.implab.gradle.variants.core.Variant; -import org.implab.gradle.variants.core.VariantsView; /** * Контекст работы с вариантами публикации, становится доступным после @@ -13,18 +12,14 @@ import org.implab.gradle.variants.core.V */ public interface OutgoingVariantsContext { - /** - * Зафиксированное представление о вариантах на основе которого адаптеры могут - * конфигурировать артефакты и исходящие конфигурации - */ - VariantsView getVariants(); + ArtifactAssemblies getAssemblies(); - ArtifactAssemblies getAssemblies(); + void configureVariant(Variant variant, Action action); /** * Replayable hook для всех объявленных конфигураций */ - void all(Action action); + void configureEach(Action action); Optional findOutgoing(Variant variant); @@ -33,6 +28,9 @@ public interface OutgoingVariantsContext .orElseThrow(() -> new InvalidUserDataException("Outgoing variant '" + variant + "' isn't registered")); } - ArtifactAssemblyRules slotRules(ArtifactSlot slot); + void whenOutgoingConfiguration(Action action); + + void whenOutgoingSlot(Action action); + } 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 @@ -8,15 +8,18 @@ import groovy.lang.Closure; /** * Project-level DSL entry point for declaring outgoing artifacts per variant. * - *

A variant represents one external outgoing contract. Slots declared inside a variant represent - * artifact sets within that contract. One slot is expected to materialize to one published artifact. + *

+ * A variant represents one external outgoing contract. Slots declared inside a + * variant represent + * artifact sets within that contract. One slot is expected to materialize to + * one published artifact. */ public interface VariantArtifactsExtension { /** * Configures artifact slots of the named variant. * * @param variantName variant name - * @param action variant artifact declaration + * @param action variant artifact declaration */ void variant(String variantName, Action action); @@ -25,25 +28,27 @@ public interface VariantArtifactsExtensi } /** - * Registers a callback invoked with the finalized artifact model. + * Registers a callback invoked when the outgoing variants context becomes + * available. * - * @param action finalized-model callback + * @param action outgoing variants context callback */ - void whenFinalized(Action action); + void whenAvailable(Action action); - default void whenFinalized(Closure closure) { - whenFinalized(Closures.action(closure)); + default void whenAvailable(Closure closure) { + whenAvailable(Closures.action(closure)); } /** - * Registers a callback invoked for each materialized root outgoing configuration. + * Registers a callback invoked for each materialized root outgoing + * configuration. * * @param action variant-level outgoing configuration callback */ - void whenOutgoingVariant(Action action); + void whenOutgoingConfiguration(Action action); - default void whenOutgoingVariant(Closure closure) { - whenOutgoingVariant(Closures.action(closure)); + default void whenOutgoingConfiguration(Closure closure) { + whenOutgoingConfiguration(Closures.action(closure)); } /** diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/ArtifactAssemblyBridge.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/ArtifactAssemblyBinder.java rename from variants/src/main/java/org/implab/gradle/variants/artifacts/internal/ArtifactAssemblyBridge.java rename to variants/src/main/java/org/implab/gradle/variants/artifacts/internal/ArtifactAssemblyBinder.java --- a/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/ArtifactAssemblyBridge.java +++ b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/ArtifactAssemblyBinder.java @@ -11,11 +11,11 @@ import org.implab.gradle.variants.artifa * из {@link ArtifactAssemblies} */ @NonNullByDefault -public class ArtifactAssemblyBridge implements Action { +public class ArtifactAssemblyBinder implements Action { private final ArtifactAssemblies resolver; - public ArtifactAssemblyBridge(ArtifactAssemblies resolver) { + public ArtifactAssemblyBinder(ArtifactAssemblies resolver) { this.resolver = resolver; } @@ -26,7 +26,7 @@ public class ArtifactAssemblyBridge impl var variant = outgoingVariant.getVariant(); // связываем конфигурацию - outgoingVariant.configure(configuration -> { + outgoingVariant.configureOutgoing(configuration -> { var primarySlot = primarySlotProvider.get(); var outgoing = configuration.getOutgoing(); @@ -51,4 +51,6 @@ public class ArtifactAssemblyBridge impl }); } + + } diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/ArtifactAssemblyHandler.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/ArtifactAssemblyHandler.java new file mode 100644 --- /dev/null +++ b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/ArtifactAssemblyHandler.java @@ -0,0 +1,218 @@ +package org.implab.gradle.variants.artifacts.internal; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.gradle.api.Action; +import org.gradle.api.file.ConfigurableFileCollection; +import org.gradle.api.file.Directory; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.file.FileCollection; +import org.gradle.api.model.ObjectFactory; +import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.Sync; +import org.gradle.api.tasks.TaskContainer; +import org.gradle.language.base.plugins.LifecycleBasePlugin; +import org.implab.gradle.common.core.lang.FilePaths; +import org.implab.gradle.variants.artifacts.ArtifactAssemblySpec; +import org.implab.gradle.variants.artifacts.ArtifactSlot; +import org.implab.gradle.variants.sources.CompileUnit; +import org.implab.gradle.variants.sources.CompileUnitsView; +import org.implab.gradle.variants.sources.RoleProjectionsView; +import org.implab.gradle.variants.sources.SourceSetMaterializer; + +/** + * Адаптер между фрагментами артефактов, представленными в виде + * {@link SlotContribution}, + * и ArtifactAssemblyRegistry, который оперирует уже собранными + * {@link ArtifactAssembly}. + * + * Данный класс отвечает за сборку отдельных фрагментов артефактов в единый + * каталог, который затем регистрируется в ArtifactAssemblyRegistry в виде + * {@link ArtifactAssembly}. Сборка конечного артефакта происходит при помощи + * задачи, которая копирует все входные файлы в выходной каталог. Задача + * создается для каждого {@link ArtifactSlot} и использует + * {@link SlotAssembly#inputs()} как источник входных данных. + * + * Для сборки используется паттерн Visitor: каждый фрагмент артефакта + * представлен в виде реализации интерфейса {@link SlotContribution}, который + * имеет метод accept, принимающий Visitor. + * Visitor реализован во внутреннем классе {@link ContributionVisitor}, который + * знает, как обрабатывать каждый тип фрагмента и добавлять его в сборку. + * Для каждого {@link SlotContribution} создается ключ {@link SlotInputKey}, + * который используется для дедупликации: если фрагмент с таким же ключом уже + * был добавлен, то он игнорируется. + */ +@NonNullByDefault +public class ArtifactAssemblyHandler { + private final ObjectFactory objects; + + private final ArtifactAssemblyRegistry assemblyRegistry; + + private final DirectoryProperty assembliesDirectory; + + private final TaskContainer tasks; + + private final CompileUnitsView compileUnitsView; + + private final RoleProjectionsView roleProjectionsView; + + private final SourceSetMaterializer sourceSetMaterializer; + + private final Map slotInputs = new HashMap<>(); + + public ArtifactAssemblyHandler( + ObjectFactory objects, + TaskContainer tasks, + ArtifactAssemblyRegistry assemblyRegistry, + CompileUnitsView compileUnitsView, + RoleProjectionsView roleProjectionsView, + SourceSetMaterializer sourceSetMaterializer) { + this.objects = objects; + this.tasks = tasks; + this.assemblyRegistry = assemblyRegistry; + this.compileUnitsView = compileUnitsView; + this.roleProjectionsView = roleProjectionsView; + this.sourceSetMaterializer = sourceSetMaterializer; + + assembliesDirectory = objects.directoryProperty(); + } + + public DirectoryProperty getAssembliesDirectory() { + return assembliesDirectory; + } + + public void configureAssembly(ArtifactSlot artifactSlot, Action action) { + var visitor = contributionVisitor(artifactSlot); + var spec = new DefaultArtifactAssemblySpec(objects, c -> c.accept(visitor)); + action.execute(spec); + } + + public SlotContributionVisitor contributionVisitor(ArtifactSlot artifactSlot) { + var assembly = slotInputs.computeIfAbsent(artifactSlot, this::createSlotAssembly); + return new ContributionVisitor(artifactSlot, assembly); + } + + /** + * Создает сборку для указанного слота артефакта, сборка регистрируется в + * ArtifactAssemblyRegistry, если для слота сборка уже была зарегистрирована + * кем-то еще, то возникает ошибка. + */ + private SlotAssembly createSlotAssembly(ArtifactSlot artifactSlot) { + var assembly = new SlotAssembly(); + var fileCollection = assembly.inputs(); + + var outputDirectory = outputDirectory(artifactSlot); + + var task = tasks.register(assembleTaskName(artifactSlot), Sync.class, copy -> { + copy.setGroup(LifecycleBasePlugin.BUILD_GROUP); + copy.into(outputDirectory); + copy.from(fileCollection); + }); + + assemblyRegistry.register(artifactSlot, task, t -> outputDirectory); + + return assembly; + } + + private String assembleTaskName(ArtifactSlot artifactSlot) { + var variantName = artifactSlot.variant().getName(); + var slotName = artifactSlot.slot().getName(); + + return "assembleVariantArtifactSlot" + + "_v" + variantName.length() + "_" + variantName + + "_s" + slotName.length() + "_" + slotName; + } + + private Provider outputDirectory(ArtifactSlot artifactSlot) { + return assembliesDirectory.dir( + FilePaths.cat( + artifactSlot.variant().getName(), + artifactSlot.slot().getName())); + } + + /** + * Собирает отдельные фрагменты артефактов в единый источник + * {@link ConfigurableFileCollection}. + * Для фрагментов {@link SlotContribution} используется механизм дедупликации: + * для каждого + * водящего фрагмента создается ключ {@link SlotInputKey} и добавляются + * фрагменты только + * с уникальным ключом, повторы игнорируются. + */ + private class ContributionVisitor implements SlotContributionVisitor { + // artifact slot for this assembly + private final ArtifactSlot artifactSlot; + + // seen inputs, used for deduplication + private final SlotAssembly assembly; + + ContributionVisitor(ArtifactSlot artifactSlot, SlotAssembly assembly) { + this.artifactSlot = artifactSlot; + this.assembly = assembly; + } + + @Override + public void visit(DirectContribution contribution) { + contribute( + SlotInputKey.newUniqueKey("Direct input for " + artifactSlot), + contribution.input()); + } + + @Override + public void visit(VariantOutputsContribution contribution) { + var units = compileUnitsView.getUnitsForVariant(artifactSlot.variant()); + contributeCompileUnits(units, contribution.outputs()); + } + + @Override + public void visit(RoleOutputsContribution contribution) { + var roleProjection = roleProjectionsView.requireProjection(artifactSlot.variant(), + contribution.role()); + var units = roleProjectionsView.getUnits(roleProjection); + + contributeCompileUnits(units, contribution.outputs()); + + } + + @Override + public void visit(LayerOutputsContribution contribution) { + var unit = compileUnitsView.requireUnit(artifactSlot.variant(), contribution.layer()); + contributeCompileUnits(Set.of(unit), contribution.outputs()); + } + + private void contributeCompileUnits(Set units, Set outputs) { + units.stream() + // expand variant compile units, make (compileUnit, outputName) pairs + .flatMap(unit -> outputs.stream() + .map(output -> new CompileUnitOutputKey(unit, output))) + .forEach(key -> contribute( + key, + sourceSetMaterializer.getSourceSet(key.unit()) + .map(s -> s.output(key.outputName())))); + } + + private void contribute(SlotInputKey key, Object resolvedInput) { + assembly.addSlotInput(key, resolvedInput); + } + } + + /** Состояние для отдельного слота */ + class SlotAssembly { + private final ConfigurableFileCollection inputs = objects.fileCollection(); + private final Set seen = new HashSet<>(); + + public void addSlotInput(SlotInputKey key, Object input) { + if (!seen.add(key)) + return; + inputs.from(input); + } + + public FileCollection inputs() { + return inputs; + } + } +} diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/ArtifactAssemblyRegistry.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/ArtifactAssemblyRegistry.java rename from variants/src/main/java/org/implab/gradle/variants/artifacts/ArtifactAssemblyRegistry.java rename to variants/src/main/java/org/implab/gradle/variants/artifacts/internal/ArtifactAssemblyRegistry.java --- a/variants/src/main/java/org/implab/gradle/variants/artifacts/ArtifactAssemblyRegistry.java +++ b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/ArtifactAssemblyRegistry.java @@ -1,4 +1,4 @@ -package org.implab.gradle.variants.artifacts; +package org.implab.gradle.variants.artifacts.internal; import java.util.LinkedHashMap; import java.util.Map; @@ -14,6 +14,9 @@ import org.gradle.api.provider.Provider; import org.gradle.api.tasks.TaskProvider; import org.implab.gradle.common.core.lang.Deferred; import org.implab.gradle.internal.ReplayableQueue; +import org.implab.gradle.variants.artifacts.ArtifactAssemblies; +import org.implab.gradle.variants.artifacts.ArtifactAssembly; +import org.implab.gradle.variants.artifacts.ArtifactSlot; @NonNullByDefault public class ArtifactAssemblyRegistry implements ArtifactAssemblies { @@ -55,7 +58,7 @@ public class ArtifactAssemblyRegistry im } @Override - public void all(Action action) { + public void configureEach(Action action) { assemblies.forEach(action::execute); } diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/ArtifactConfigurationHandler.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/ArtifactConfigurationHandler.java deleted file mode 100644 --- a/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/ArtifactConfigurationHandler.java +++ /dev/null @@ -1,100 +0,0 @@ -package org.implab.gradle.variants.artifacts.internal; - -import java.util.HashMap; -import java.util.Map; - -import org.gradle.api.Action; -import org.gradle.api.file.Directory; -import org.gradle.api.file.DirectoryProperty; -import org.gradle.api.model.ObjectFactory; -import org.gradle.api.provider.Provider; -import org.gradle.api.tasks.Copy; -import org.gradle.api.tasks.TaskContainer; -import org.gradle.language.base.plugins.LifecycleBasePlugin; -import org.implab.gradle.common.core.lang.FilePaths; -import org.implab.gradle.common.core.lang.Strings; -import org.implab.gradle.variants.artifacts.ArtifactAssemblyRegistry; -import org.implab.gradle.variants.artifacts.ArtifactSlot; -import org.implab.gradle.variants.artifacts.OutgoingVariant; -import org.implab.gradle.variants.artifacts.VariantArtifactsSpec; -import org.implab.gradle.variants.sources.CompileUnitsView; -import org.implab.gradle.variants.sources.RoleProjectionsView; -import org.implab.gradle.variants.sources.SourceSetMaterializer; - -public class ArtifactConfigurationHandler { - private final ArtifactAssemblyRegistry registry; - - private final CompileUnitsView compileUnitsView; - - private final RoleProjectionsView roleProjectionsView; - - private final SourceSetMaterializer sourceSetMaterializer; - - private final Map visitors = new HashMap<>(); - - private final ObjectFactory objectFactory; - - private final DirectoryProperty assembliesDirectory; - - private final TaskContainer tasks; - - public ArtifactConfigurationHandler( - ArtifactAssemblyRegistry registry, - CompileUnitsView compileUnitsView, - RoleProjectionsView roleProjectionsView, - SourceSetMaterializer sourceSetMaterializer, - ObjectFactory objectFactory, - TaskContainer tasks) { - this.registry = registry; - this.compileUnitsView = compileUnitsView; - this.roleProjectionsView = roleProjectionsView; - this.sourceSetMaterializer = sourceSetMaterializer; - this.objectFactory = objectFactory; - this.assembliesDirectory = objectFactory.directoryProperty(); - this.tasks = tasks; - } - - public SlotContributionVisitor slotAssembler(ArtifactSlot artifactSlot) { - return visitors.computeIfAbsent(artifactSlot, this::newVisitor); - } - - public void configureVariant(OutgoingVariant outgoingVariant, Action action) { - var spec = new DefaultVariantArtifactSpec(outgoingVariant, objectFactory, this); - - action.execute(spec); - } - - private SlotContributionVisitor newVisitor(ArtifactSlot artifactSlot) { - var fileCollection = objectFactory.fileCollection(); - var outputDirectory = outputDirectory(artifactSlot); - - var task = tasks.register(assembleTaskName(artifactSlot), Copy.class, copy -> { - copy.setGroup(LifecycleBasePlugin.BUILD_GROUP); - copy.into(outputDirectory); - copy.from(fileCollection); - }); - - // регистрируется задача и арефакт сборки слота - registry.register(artifactSlot, task, t -> outputDirectory); - - return new SlotInputsAssembler( - artifactSlot, - fileCollection, - compileUnitsView, - roleProjectionsView, - sourceSetMaterializer); - } - - private String assembleTaskName(ArtifactSlot artifactSlot) { - return "assemble" - + Strings.capitalize(artifactSlot.variant().getName()) - + Strings.capitalize(artifactSlot.slot().getName()); - } - - private Provider outputDirectory(ArtifactSlot artifactSlot) { - return assembliesDirectory.dir( - FilePaths.cat( - artifactSlot.variant().getName(), - artifactSlot.slot().getName())); - } -} diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/DefaultArtifactAssemblySpec.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/DefaultArtifactAssemblySpec.java --- a/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/DefaultArtifactAssemblySpec.java +++ b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/DefaultArtifactAssemblySpec.java @@ -5,6 +5,7 @@ import java.util.Set; import java.util.function.Consumer; import java.util.stream.Stream; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.gradle.api.Action; import org.gradle.api.model.ObjectFactory; import org.implab.gradle.common.core.lang.Strings; @@ -19,6 +20,7 @@ import org.implab.gradle.variants.artifa * вызывает метод {@link #process(Consumer)} для обработки результатов. * */ +@NonNullByDefault final class DefaultArtifactAssemblySpec implements ArtifactAssemblySpec { private final Consumer consumer; private final ObjectFactory objectFactory; 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 deleted file mode 100644 --- a/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/DefaultOutgoingConfiguration.java +++ /dev/null @@ -1,62 +0,0 @@ -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.OutgoingVariant; -import org.implab.gradle.variants.artifacts.Slot; -import org.implab.gradle.variants.core.Variant; - -class DefaultOutgoingConfiguration implements OutgoingVariant { - - 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 getConfiguration() { - 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/DefaultOutgoingVariantsContext.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/DefaultOutgoingVariantsContext.java new file mode 100644 --- /dev/null +++ b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/DefaultOutgoingVariantsContext.java @@ -0,0 +1,94 @@ +package org.implab.gradle.variants.artifacts.internal; + +import java.util.Optional; + +import org.gradle.api.Action; +import org.implab.gradle.variants.artifacts.ArtifactAssemblies; +import org.implab.gradle.variants.artifacts.ArtifactAssemblySpec; +import org.implab.gradle.variants.artifacts.ArtifactSlot; +import org.implab.gradle.variants.artifacts.OutgoingConfigurationSlotSpec; +import org.implab.gradle.variants.artifacts.OutgoingConfigurationSpec; +import org.implab.gradle.variants.artifacts.OutgoingVariant; +import org.implab.gradle.variants.artifacts.OutgoingVariantsContext; +import org.implab.gradle.variants.artifacts.VariantArtifactsSpec; +import org.implab.gradle.variants.core.Variant; + +public class DefaultOutgoingVariantsContext implements OutgoingVariantsContext { + private final ArtifactAssemblies assemblies; + + private final ArtifactAssemblyHandler assemblyHandler; + + private final OutgoingRegistry outgoingVariants; + + private final MaterializationPolicyHandler materializationHandler; + + public DefaultOutgoingVariantsContext( + ArtifactAssemblies assemblies, + OutgoingRegistry outgoingVariants, + ArtifactAssemblyHandler assemblyHandler, + MaterializationPolicyHandler materializationHandler) { + this.assemblies = assemblies; + this.outgoingVariants = outgoingVariants; + this.assemblyHandler = assemblyHandler; + this.materializationHandler = materializationHandler; + } + + @Override + public ArtifactAssemblies getAssemblies() { + return assemblies; + } + + @Override + public void configureVariant(Variant variant, Action action) { + var outgoingVariant = outgoingVariants.maybeCreate(variant); + var variantSpec = new VariantSpec(outgoingVariant); + action.execute(variantSpec); + } + + @Override + public void configureEach(Action action) { + outgoingVariants.configureEach(action::execute); + } + + @Override + public Optional findOutgoing(Variant variant) { + return outgoingVariants.find(variant); + } + + @Override + public void whenOutgoingConfiguration(Action action) { + materializationHandler.whenVariantMaterialized(action); + } + + @Override + public void whenOutgoingSlot(Action action) { + materializationHandler.whenSlotMaterialized(action); + } + + class VariantSpec implements VariantArtifactsSpec { + + private final OutgoingVariant outgoingVariant; + + VariantSpec(OutgoingVariant outgoingVariant) { + this.outgoingVariant = outgoingVariant; + } + + @Override + public void slot(String name, Action action) { + var slot = outgoingVariant.getSlots().maybeCreate(name); + var artifactSlot = new ArtifactSlot(outgoingVariant.getVariant(), slot); + assemblyHandler.configureAssembly(artifactSlot, action); + } + + @Override + public void primarySlot(String name, Action action) { + var slot = outgoingVariant.getSlots().maybeCreate(name); + var artifactSlot = new ArtifactSlot(outgoingVariant.getVariant(), slot); + assemblyHandler.configureAssembly(artifactSlot, action); + + outgoingVariant.getPrimarySlot().set(slot); + } + + } + +} diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/DefaultVariantArtifactSpec.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/DefaultVariantArtifactSpec.java deleted file mode 100644 --- a/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/DefaultVariantArtifactSpec.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.implab.gradle.variants.artifacts.internal; - -import org.gradle.api.Action; -import org.gradle.api.model.ObjectFactory; -import org.implab.gradle.variants.artifacts.ArtifactAssemblySpec; -import org.implab.gradle.variants.artifacts.ArtifactSlot; -import org.implab.gradle.variants.artifacts.OutgoingVariant; -import org.implab.gradle.variants.artifacts.Slot; -import org.implab.gradle.variants.artifacts.VariantArtifactsSpec; - -class DefaultVariantArtifactSpec implements VariantArtifactsSpec { - - private final ObjectFactory objectFactory; - private final ArtifactConfigurationHandler assemblyBuilder; - - private final OutgoingVariant outgoingVariant; - - DefaultVariantArtifactSpec( - OutgoingVariant outgoingVariant, - ObjectFactory objectFactory, - ArtifactConfigurationHandler assemblyBuilder) { - this.objectFactory = objectFactory; - this.assemblyBuilder = assemblyBuilder; - this.outgoingVariant = outgoingVariant; - } - - @Override - public void slot(String name, Action action) { - var slot = outgoingVariant.getSlots().maybeCreate(name); - - configureSlot(slot, action); - } - - @Override - public void primarySlot(String name, Action action) { - var slot = outgoingVariant.getSlots().maybeCreate(name); - outgoingVariant.getPrimarySlot().set(slot); - - configureSlot(slot, action); - } - - private void configureSlot(Slot slot, Action action) { - var artifactSlot = new ArtifactSlot(outgoingVariant.getVariant(), slot); - - var inputsAssembler = assemblyBuilder.slotAssembler(artifactSlot); - - var spec = new DefaultArtifactAssemblySpec(objectFactory, inputsAssembler.consumer()); - action.execute(spec); - } - -} diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/MaterializationPolicyHandler.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/MaterializationPolicyHandler.java new file mode 100644 --- /dev/null +++ b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/MaterializationPolicyHandler.java @@ -0,0 +1,118 @@ +package org.implab.gradle.variants.artifacts.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.gradle.api.Action; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.attributes.AttributeContainer; +import org.implab.gradle.internal.ReplayableQueue; +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.artifacts.OutgoingConfigurationSlotSpec; +import org.implab.gradle.variants.artifacts.OutgoingConfigurationSpec; +import org.implab.gradle.variants.artifacts.OutgoingVariant; +import org.implab.gradle.variants.core.Variant; + +/** + * Handles outgoing artifact materialization policy. + * + *

Materialization is the phase where the plugin interprets variant artifact + * declarations as Gradle outgoing publication state: a consumable configuration, + * its primary artifact set, secondary artifact variants, attributes, and backing + * assembly tasks. + * + *

The handler provides extension points for customizing the materialized + * Gradle-facing objects. These hooks intentionally expose Gradle API objects. + * This allows advanced customization, but also means that callers can bypass + * the plugin model. Such customizations are considered caller responsibility. + * + *

The internal binding mechanics are not part of the public contract. The + * contract is the materialized outgoing state exposed through the specification + * objects. + */ +@NonNullByDefault +public class MaterializationPolicyHandler implements Action { + + private final ArtifactAssemblies resolver; + + private final ReplayableQueue variantMaterialization = new ReplayableQueue<>(); + private final ReplayableQueue slotMaterialization = new ReplayableQueue<>(); + + public MaterializationPolicyHandler(ArtifactAssemblies resolver) { + this.resolver = resolver; + } + + @Override + public void execute(OutgoingVariant outgoingVariant) { + var slots = outgoingVariant.getSlots(); + var primarySlotProvider = outgoingVariant.getPrimarySlot(); + var variant = outgoingVariant.getVariant(); + + // связываем конфигурацию + outgoingVariant.configureOutgoing(configuration -> { + var primarySlot = primarySlotProvider.get(); + var outgoing = configuration.getOutgoing(); + + variantMaterialized(variant, configuration); + + slotMaterialized(new ArtifactSlot(variant, primarySlot), true, outgoing.getAttributes()); + + outgoing.getVariants().configureEach(variantConfiguration -> { + var slotName = variantConfiguration.getName(); + var slot = slots.findByName(slotName); + if (slot != null) { + slotMaterialized(new ArtifactSlot(variant, slot), false, variantConfiguration.getAttributes()); + } + }); + }); + }; + + public void whenVariantMaterialized(Action action) { + variantMaterialization.forEach(action::execute); + } + + public void whenSlotMaterialized(Action action) { + slotMaterialization.forEach(action::execute); + } + + private void variantMaterialized(Variant variant, Configuration configuration) { + variantMaterialization.add(new OutgoingConfigurationSpec() { + + @Override + public Variant getVariant() { + return variant; + } + + @Override + public Configuration getConfiguration() { + return configuration; + } + + }); + } + + private void slotMaterialized(ArtifactSlot slot, boolean primary, AttributeContainer attributes) { + slotMaterialization.add(new OutgoingConfigurationSlotSpec() { + @Override + public boolean isPrimary() { + return primary; + } + + @Override + public ArtifactSlot getArtifactSlot() { + return slot; + } + + @Override + public ArtifactAssembly getAssembly() { + return resolver.require(slot); + } + + @Override + public void artifactAttributes(Action action) { + action.execute(attributes); + } + }); + } + +} diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/OutgoingRegistry.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/OutgoingRegistry.java --- a/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/OutgoingRegistry.java +++ b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/OutgoingRegistry.java @@ -1,56 +1,121 @@ package org.implab.gradle.variants.artifacts.internal; import java.util.LinkedHashMap; -import java.util.LinkedList; -import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.function.Consumer; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.gradle.api.InvalidUserDataException; +import org.gradle.api.NamedDomainObjectContainer; +import org.gradle.api.NamedDomainObjectProvider; +import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.model.ObjectFactory; -import org.gradle.api.provider.ProviderFactory; +import org.gradle.api.provider.Property; +import org.implab.gradle.internal.ReplayableQueue; import org.implab.gradle.variants.artifacts.OutgoingVariant; +import org.implab.gradle.variants.artifacts.Slot; import org.implab.gradle.variants.core.Variant; +/** + * Реестр исходящих вариантов. Связывает исходящие конфигурации с вариантами + * сборки. Связь устанавливается 1:1. + */ +@NonNullByDefault public class OutgoingRegistry { - private final Map outgoingByVariant = new LinkedHashMap<>(); - private final List> hooks = new LinkedList<>(); - + private final Map outgoingByVariant = new LinkedHashMap<>(); + private final ReplayableQueue outgoingVariants = new ReplayableQueue<>(); private final ConfigurationContainer configurations; private final ObjectFactory objects; - private final ProviderFactory providers; + private final Set declaredVariants; - public OutgoingRegistry(ConfigurationContainer configurations, ObjectFactory objects, ProviderFactory providers) { + public OutgoingRegistry( + ConfigurationContainer configurations, + ObjectFactory objects, + Set declaredVariants) { this.configurations = configurations; this.objects = objects; - this.providers = providers; + this.declaredVariants = declaredVariants; } - public Optional findOutgoing(Variant variant) { + public Optional find(Variant variant) { return Optional.ofNullable(outgoingByVariant.get(variant)); } public OutgoingVariant maybeCreate(Variant variant) { - var outgoing = outgoingByVariant.computeIfAbsent(variant, this::newOutgoingConfiguration); - hooks.forEach(hook -> hook.accept(outgoing)); + return find(variant).orElseGet(() -> create(variant)); + } + + public OutgoingVariant create(Variant variant) { + if (!declaredVariants.contains(variant)) + throw new InvalidUserDataException("Variant " + variant + " isn't declared"); + if (outgoingByVariant.containsKey(variant)) + throw new InvalidUserDataException("Outgoing variant " + variant + " already exists"); + + var configuration = configurations.consumable(outgoingConfigurationName(variant)); + var outgoing = new Outgoing(variant, configuration); + + outgoingByVariant.put(variant, outgoing); + + outgoingVariants.add(outgoing); + return outgoing; } - public void all(Consumer action) { - outgoingByVariant.values().forEach(action); - hooks.add(action); - } - - private DefaultOutgoingConfiguration newOutgoingConfiguration(Variant variant) { - var configuration = configurations.register(outgoingConfigurationName(variant)); - - return new DefaultOutgoingConfiguration(variant, configuration, objects, providers); + /** + * Replayable hook which is applied when an outgoing variant is defined + * + * @param action + */ + public void configureEach(Consumer action) { + outgoingVariants.forEach(action); } private String outgoingConfigurationName(Variant variant) { return variant.getName() + "Elements"; } + private class Outgoing implements OutgoingVariant { + + private final Variant variant; + + private final NamedDomainObjectProvider configurationProvider; + + private final NamedDomainObjectContainer slots; + + private final Property primarySlot; + + public Outgoing( + Variant variant, + NamedDomainObjectProvider configurationProvider) { + this.variant = variant; + this.configurationProvider = configurationProvider; + this.slots = objects.domainObjectContainer(Slot.class); + this.primarySlot = objects.property(Slot.class); + primarySlot.finalizeValueOnRead(); + } + + @Override + public Property getPrimarySlot() { + return primarySlot; + } + + @Override + public Variant getVariant() { + return variant; + } + + @Override + public NamedDomainObjectProvider getConfiguration() { + return configurationProvider; + } + + @Override + public NamedDomainObjectContainer getSlots() { + return slots; + } + } } diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/SingleSlotConvention.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/SingleSlotConvention.java new file mode 100644 --- /dev/null +++ b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/SingleSlotConvention.java @@ -0,0 +1,33 @@ +package org.implab.gradle.variants.artifacts.internal; + +import org.gradle.api.Action; +import org.gradle.api.InvalidUserDataException; +import org.gradle.api.provider.ProviderFactory; +import org.implab.gradle.variants.artifacts.OutgoingVariant; + +public class SingleSlotConvention implements Action { + + private final ProviderFactory providers; + + public SingleSlotConvention(ProviderFactory providers) { + this.providers = providers; + } + + @Override + public void execute(OutgoingVariant outgoingVariant) { + var slots = outgoingVariant.getSlots(); + + outgoingVariant.getPrimarySlot().convention( + // если есть ровно один слот, то он считается primary + providers.provider(() -> { + if (slots.size() == 0) + throw new InvalidUserDataException("No slots declared for " + outgoingVariant.getVariant()); + if (slots.size() > 1) + throw new InvalidUserDataException("Multiple slots declared for " + outgoingVariant.getVariant() + + ", please specify primary slot explicitly"); + + return slots.stream().findAny().get(); + })); + } + +} 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 --- 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 @@ -3,7 +3,7 @@ package org.implab.gradle.variants.artif import org.eclipse.jdt.annotation.NonNullByDefault; @NonNullByDefault -public interface SlotContribution { +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 --- 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 @@ -1,8 +1,9 @@ package org.implab.gradle.variants.artifacts.internal; -import java.util.function.Consumer; +import org.eclipse.jdt.annotation.NonNullByDefault; -public interface SlotContributionVisitor { +@NonNullByDefault +interface SlotContributionVisitor { void visit(DirectContribution contribution); void visit(VariantOutputsContribution contribution); @@ -11,7 +12,4 @@ public interface SlotContributionVisitor void visit(LayerOutputsContribution contribution); - default Consumer consumer() { - return slot -> slot.accept(this); - } } diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/SlotInputsAssembler.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/SlotInputsAssembler.java deleted file mode 100644 --- a/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/SlotInputsAssembler.java +++ /dev/null @@ -1,96 +0,0 @@ -package org.implab.gradle.variants.artifacts.internal; - -import java.util.HashSet; -import java.util.Set; - -import org.gradle.api.file.ConfigurableFileCollection; -import org.implab.gradle.variants.artifacts.ArtifactSlot; -import org.implab.gradle.variants.sources.CompileUnit; -import org.implab.gradle.variants.sources.CompileUnitsView; -import org.implab.gradle.variants.sources.RoleProjectionsView; -import org.implab.gradle.variants.sources.SourceSetMaterializer; - -/** - * Сборщик входящих элементов из разных источников {@link SlotContribution}. - * Хранит {@link ConfigurableFileCollection} и добавляет в него новые элементы, - * при этом делается дедупликация по {@link SlotInputKey}. - * - */ -public class SlotInputsAssembler implements SlotContributionVisitor { - - // sources context - private final CompileUnitsView compileUnitsView; - - private final RoleProjectionsView roleProjectionsView; - - private final SourceSetMaterializer sourceSetMaterializer; - - // artifact slot for this assembly - private final ArtifactSlot artifactSlot; - - // content for this assembly - private final ConfigurableFileCollection artifactInputs; - - // seen inputs, used for deduplication - private final Set seen = new HashSet<>(); - - public SlotInputsAssembler( - ArtifactSlot artifactSlot, - ConfigurableFileCollection artifactInputs, - CompileUnitsView compileUnitsView, - RoleProjectionsView roleProjectionsView, - SourceSetMaterializer sourceSetMaterializer) { - this.compileUnitsView = compileUnitsView; - this.roleProjectionsView = roleProjectionsView; - this.sourceSetMaterializer = sourceSetMaterializer; - this.artifactSlot = artifactSlot; - this.artifactInputs = artifactInputs; - } - - @Override - public void visit(DirectContribution contribution) { - contribute( - SlotInputKey.newUniqueKey("Direct input for " + artifactSlot), - contribution.input()); - } - - @Override - public void visit(VariantOutputsContribution contribution) { - var units = compileUnitsView.getUnitsForVariant(artifactSlot.variant()); - contributeCompileUnits(units, contribution.outputs()); - } - - @Override - public void visit(RoleOutputsContribution contribution) { - var roleProjection = roleProjectionsView.requireProjection(artifactSlot.variant(), - contribution.role()); - var units = roleProjectionsView.getUnits(roleProjection); - - contributeCompileUnits(units, contribution.outputs()); - - } - - @Override - public void visit(LayerOutputsContribution contribution) { - var unit = compileUnitsView.requireUnit(artifactSlot.variant(), contribution.layer()); - contributeCompileUnits(Set.of(unit), contribution.outputs()); - } - - private void contributeCompileUnits(Set units, Set outputs) { - units.stream() - // expand variant compile units, make (compileUnit, outputName) pairs - .flatMap(unit -> outputs.stream() - .map(output -> new CompileUnitOutputKey(unit, output))) - .forEach(key -> contribute( - key, - sourceSetMaterializer.getSourceSet(key.unit()) - .map(s -> s.output(key.outputName())))); - } - - private void contribute(SlotInputKey key, Object resolvedInput) { - if (!seen.add(key)) - return; - artifactInputs.from(resolvedInput); - } - -} diff --git a/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/package-info.java b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/package-info.java new file mode 100644 --- /dev/null +++ b/variants/src/main/java/org/implab/gradle/variants/artifacts/internal/package-info.java @@ -0,0 +1,9 @@ +/** + * Internal implementation of the variant artifacts plugin. + * + *

Types in this package are not part of the public API. They may change, + * move, or be removed without compatibility guarantees. Build logic should use + * the public contracts from {@link org.implab.gradle.variants.artifacts} + * instead. + */ +package org.implab.gradle.variants.artifacts.internal;