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;