##// END OF EJS Templates
Rework variant artifacts materialization model...
cin -
r51:9db7822cd26c default
parent child
Show More
@@ -0,0 +1,218
1 package org.implab.gradle.variants.artifacts.internal;
2
3 import java.util.HashMap;
4 import java.util.HashSet;
5 import java.util.Map;
6 import java.util.Set;
7
8 import org.eclipse.jdt.annotation.NonNullByDefault;
9 import org.gradle.api.Action;
10 import org.gradle.api.file.ConfigurableFileCollection;
11 import org.gradle.api.file.Directory;
12 import org.gradle.api.file.DirectoryProperty;
13 import org.gradle.api.file.FileCollection;
14 import org.gradle.api.model.ObjectFactory;
15 import org.gradle.api.provider.Provider;
16 import org.gradle.api.tasks.Sync;
17 import org.gradle.api.tasks.TaskContainer;
18 import org.gradle.language.base.plugins.LifecycleBasePlugin;
19 import org.implab.gradle.common.core.lang.FilePaths;
20 import org.implab.gradle.variants.artifacts.ArtifactAssemblySpec;
21 import org.implab.gradle.variants.artifacts.ArtifactSlot;
22 import org.implab.gradle.variants.sources.CompileUnit;
23 import org.implab.gradle.variants.sources.CompileUnitsView;
24 import org.implab.gradle.variants.sources.RoleProjectionsView;
25 import org.implab.gradle.variants.sources.SourceSetMaterializer;
26
27 /**
28 * Адаптер между фрагментами артефактов, представленными в виде
29 * {@link SlotContribution},
30 * и ArtifactAssemblyRegistry, который оперирует уже собранными
31 * {@link ArtifactAssembly}.
32 *
33 * Данный класс отвечает за сборку отдельных фрагментов артефактов в единый
34 * каталог, который затем регистрируется в ArtifactAssemblyRegistry в виде
35 * {@link ArtifactAssembly}. Сборка конечного артефакта происходит при помощи
36 * задачи, которая копирует все входные файлы в выходной каталог. Задача
37 * создается для каждого {@link ArtifactSlot} и использует
38 * {@link SlotAssembly#inputs()} как источник входных данных.
39 *
40 * Для сборки используется паттерн Visitor: каждый фрагмент артефакта
41 * представлен в виде реализации интерфейса {@link SlotContribution}, который
42 * имеет метод accept, принимающий Visitor.
43 * Visitor реализован во внутреннем классе {@link ContributionVisitor}, который
44 * знает, как обрабатывать каждый тип фрагмента и добавлять его в сборку.
45 * Для каждого {@link SlotContribution} создается ключ {@link SlotInputKey},
46 * который используется для дедупликации: если фрагмент с таким же ключом уже
47 * был добавлен, то он игнорируется.
48 */
49 @NonNullByDefault
50 public class ArtifactAssemblyHandler {
51 private final ObjectFactory objects;
52
53 private final ArtifactAssemblyRegistry assemblyRegistry;
54
55 private final DirectoryProperty assembliesDirectory;
56
57 private final TaskContainer tasks;
58
59 private final CompileUnitsView compileUnitsView;
60
61 private final RoleProjectionsView roleProjectionsView;
62
63 private final SourceSetMaterializer sourceSetMaterializer;
64
65 private final Map<ArtifactSlot, SlotAssembly> slotInputs = new HashMap<>();
66
67 public ArtifactAssemblyHandler(
68 ObjectFactory objects,
69 TaskContainer tasks,
70 ArtifactAssemblyRegistry assemblyRegistry,
71 CompileUnitsView compileUnitsView,
72 RoleProjectionsView roleProjectionsView,
73 SourceSetMaterializer sourceSetMaterializer) {
74 this.objects = objects;
75 this.tasks = tasks;
76 this.assemblyRegistry = assemblyRegistry;
77 this.compileUnitsView = compileUnitsView;
78 this.roleProjectionsView = roleProjectionsView;
79 this.sourceSetMaterializer = sourceSetMaterializer;
80
81 assembliesDirectory = objects.directoryProperty();
82 }
83
84 public DirectoryProperty getAssembliesDirectory() {
85 return assembliesDirectory;
86 }
87
88 public void configureAssembly(ArtifactSlot artifactSlot, Action<? super ArtifactAssemblySpec> action) {
89 var visitor = contributionVisitor(artifactSlot);
90 var spec = new DefaultArtifactAssemblySpec(objects, c -> c.accept(visitor));
91 action.execute(spec);
92 }
93
94 public SlotContributionVisitor contributionVisitor(ArtifactSlot artifactSlot) {
95 var assembly = slotInputs.computeIfAbsent(artifactSlot, this::createSlotAssembly);
96 return new ContributionVisitor(artifactSlot, assembly);
97 }
98
99 /**
100 * Создает сборку для указанного слота артефакта, сборка регистрируется в
101 * ArtifactAssemblyRegistry, если для слота сборка уже была зарегистрирована
102 * кем-то еще, то возникает ошибка.
103 */
104 private SlotAssembly createSlotAssembly(ArtifactSlot artifactSlot) {
105 var assembly = new SlotAssembly();
106 var fileCollection = assembly.inputs();
107
108 var outputDirectory = outputDirectory(artifactSlot);
109
110 var task = tasks.register(assembleTaskName(artifactSlot), Sync.class, copy -> {
111 copy.setGroup(LifecycleBasePlugin.BUILD_GROUP);
112 copy.into(outputDirectory);
113 copy.from(fileCollection);
114 });
115
116 assemblyRegistry.register(artifactSlot, task, t -> outputDirectory);
117
118 return assembly;
119 }
120
121 private String assembleTaskName(ArtifactSlot artifactSlot) {
122 var variantName = artifactSlot.variant().getName();
123 var slotName = artifactSlot.slot().getName();
124
125 return "assembleVariantArtifactSlot"
126 + "_v" + variantName.length() + "_" + variantName
127 + "_s" + slotName.length() + "_" + slotName;
128 }
129
130 private Provider<Directory> outputDirectory(ArtifactSlot artifactSlot) {
131 return assembliesDirectory.dir(
132 FilePaths.cat(
133 artifactSlot.variant().getName(),
134 artifactSlot.slot().getName()));
135 }
136
137 /**
138 * Собирает отдельные фрагменты артефактов в единый источник
139 * {@link ConfigurableFileCollection}.
140 * Для фрагментов {@link SlotContribution} используется механизм дедупликации:
141 * для каждого
142 * водящего фрагмента создается ключ {@link SlotInputKey} и добавляются
143 * фрагменты только
144 * с уникальным ключом, повторы игнорируются.
145 */
146 private class ContributionVisitor implements SlotContributionVisitor {
147 // artifact slot for this assembly
148 private final ArtifactSlot artifactSlot;
149
150 // seen inputs, used for deduplication
151 private final SlotAssembly assembly;
152
153 ContributionVisitor(ArtifactSlot artifactSlot, SlotAssembly assembly) {
154 this.artifactSlot = artifactSlot;
155 this.assembly = assembly;
156 }
157
158 @Override
159 public void visit(DirectContribution contribution) {
160 contribute(
161 SlotInputKey.newUniqueKey("Direct input for " + artifactSlot),
162 contribution.input());
163 }
164
165 @Override
166 public void visit(VariantOutputsContribution contribution) {
167 var units = compileUnitsView.getUnitsForVariant(artifactSlot.variant());
168 contributeCompileUnits(units, contribution.outputs());
169 }
170
171 @Override
172 public void visit(RoleOutputsContribution contribution) {
173 var roleProjection = roleProjectionsView.requireProjection(artifactSlot.variant(),
174 contribution.role());
175 var units = roleProjectionsView.getUnits(roleProjection);
176
177 contributeCompileUnits(units, contribution.outputs());
178
179 }
180
181 @Override
182 public void visit(LayerOutputsContribution contribution) {
183 var unit = compileUnitsView.requireUnit(artifactSlot.variant(), contribution.layer());
184 contributeCompileUnits(Set.of(unit), contribution.outputs());
185 }
186
187 private void contributeCompileUnits(Set<CompileUnit> units, Set<String> outputs) {
188 units.stream()
189 // expand variant compile units, make (compileUnit, outputName) pairs
190 .flatMap(unit -> outputs.stream()
191 .map(output -> new CompileUnitOutputKey(unit, output)))
192 .forEach(key -> contribute(
193 key,
194 sourceSetMaterializer.getSourceSet(key.unit())
195 .map(s -> s.output(key.outputName()))));
196 }
197
198 private void contribute(SlotInputKey key, Object resolvedInput) {
199 assembly.addSlotInput(key, resolvedInput);
200 }
201 }
202
203 /** Состояние для отдельного слота */
204 class SlotAssembly {
205 private final ConfigurableFileCollection inputs = objects.fileCollection();
206 private final Set<SlotInputKey> seen = new HashSet<>();
207
208 public void addSlotInput(SlotInputKey key, Object input) {
209 if (!seen.add(key))
210 return;
211 inputs.from(input);
212 }
213
214 public FileCollection inputs() {
215 return inputs;
216 }
217 }
218 }
@@ -0,0 +1,94
1 package org.implab.gradle.variants.artifacts.internal;
2
3 import java.util.Optional;
4
5 import org.gradle.api.Action;
6 import org.implab.gradle.variants.artifacts.ArtifactAssemblies;
7 import org.implab.gradle.variants.artifacts.ArtifactAssemblySpec;
8 import org.implab.gradle.variants.artifacts.ArtifactSlot;
9 import org.implab.gradle.variants.artifacts.OutgoingConfigurationSlotSpec;
10 import org.implab.gradle.variants.artifacts.OutgoingConfigurationSpec;
11 import org.implab.gradle.variants.artifacts.OutgoingVariant;
12 import org.implab.gradle.variants.artifacts.OutgoingVariantsContext;
13 import org.implab.gradle.variants.artifacts.VariantArtifactsSpec;
14 import org.implab.gradle.variants.core.Variant;
15
16 public class DefaultOutgoingVariantsContext implements OutgoingVariantsContext {
17 private final ArtifactAssemblies assemblies;
18
19 private final ArtifactAssemblyHandler assemblyHandler;
20
21 private final OutgoingRegistry outgoingVariants;
22
23 private final MaterializationPolicyHandler materializationHandler;
24
25 public DefaultOutgoingVariantsContext(
26 ArtifactAssemblies assemblies,
27 OutgoingRegistry outgoingVariants,
28 ArtifactAssemblyHandler assemblyHandler,
29 MaterializationPolicyHandler materializationHandler) {
30 this.assemblies = assemblies;
31 this.outgoingVariants = outgoingVariants;
32 this.assemblyHandler = assemblyHandler;
33 this.materializationHandler = materializationHandler;
34 }
35
36 @Override
37 public ArtifactAssemblies getAssemblies() {
38 return assemblies;
39 }
40
41 @Override
42 public void configureVariant(Variant variant, Action<? super VariantArtifactsSpec> action) {
43 var outgoingVariant = outgoingVariants.maybeCreate(variant);
44 var variantSpec = new VariantSpec(outgoingVariant);
45 action.execute(variantSpec);
46 }
47
48 @Override
49 public void configureEach(Action<? super OutgoingVariant> action) {
50 outgoingVariants.configureEach(action::execute);
51 }
52
53 @Override
54 public Optional<OutgoingVariant> findOutgoing(Variant variant) {
55 return outgoingVariants.find(variant);
56 }
57
58 @Override
59 public void whenOutgoingConfiguration(Action<? super OutgoingConfigurationSpec> action) {
60 materializationHandler.whenVariantMaterialized(action);
61 }
62
63 @Override
64 public void whenOutgoingSlot(Action<? super OutgoingConfigurationSlotSpec> action) {
65 materializationHandler.whenSlotMaterialized(action);
66 }
67
68 class VariantSpec implements VariantArtifactsSpec {
69
70 private final OutgoingVariant outgoingVariant;
71
72 VariantSpec(OutgoingVariant outgoingVariant) {
73 this.outgoingVariant = outgoingVariant;
74 }
75
76 @Override
77 public void slot(String name, Action<? super ArtifactAssemblySpec> action) {
78 var slot = outgoingVariant.getSlots().maybeCreate(name);
79 var artifactSlot = new ArtifactSlot(outgoingVariant.getVariant(), slot);
80 assemblyHandler.configureAssembly(artifactSlot, action);
81 }
82
83 @Override
84 public void primarySlot(String name, Action<? super ArtifactAssemblySpec> action) {
85 var slot = outgoingVariant.getSlots().maybeCreate(name);
86 var artifactSlot = new ArtifactSlot(outgoingVariant.getVariant(), slot);
87 assemblyHandler.configureAssembly(artifactSlot, action);
88
89 outgoingVariant.getPrimarySlot().set(slot);
90 }
91
92 }
93
94 }
@@ -0,0 +1,118
1 package org.implab.gradle.variants.artifacts.internal;
2
3 import org.eclipse.jdt.annotation.NonNullByDefault;
4 import org.gradle.api.Action;
5 import org.gradle.api.artifacts.Configuration;
6 import org.gradle.api.attributes.AttributeContainer;
7 import org.implab.gradle.internal.ReplayableQueue;
8 import org.implab.gradle.variants.artifacts.ArtifactAssemblies;
9 import org.implab.gradle.variants.artifacts.ArtifactAssembly;
10 import org.implab.gradle.variants.artifacts.ArtifactSlot;
11 import org.implab.gradle.variants.artifacts.OutgoingConfigurationSlotSpec;
12 import org.implab.gradle.variants.artifacts.OutgoingConfigurationSpec;
13 import org.implab.gradle.variants.artifacts.OutgoingVariant;
14 import org.implab.gradle.variants.core.Variant;
15
16 /**
17 * Handles outgoing artifact materialization policy.
18 *
19 * <p>Materialization is the phase where the plugin interprets variant artifact
20 * declarations as Gradle outgoing publication state: a consumable configuration,
21 * its primary artifact set, secondary artifact variants, attributes, and backing
22 * assembly tasks.
23 *
24 * <p>The handler provides extension points for customizing the materialized
25 * Gradle-facing objects. These hooks intentionally expose Gradle API objects.
26 * This allows advanced customization, but also means that callers can bypass
27 * the plugin model. Such customizations are considered caller responsibility.
28 *
29 * <p>The internal binding mechanics are not part of the public contract. The
30 * contract is the materialized outgoing state exposed through the specification
31 * objects.
32 */
33 @NonNullByDefault
34 public class MaterializationPolicyHandler implements Action<OutgoingVariant> {
35
36 private final ArtifactAssemblies resolver;
37
38 private final ReplayableQueue<OutgoingConfigurationSpec> variantMaterialization = new ReplayableQueue<>();
39 private final ReplayableQueue<OutgoingConfigurationSlotSpec> slotMaterialization = new ReplayableQueue<>();
40
41 public MaterializationPolicyHandler(ArtifactAssemblies resolver) {
42 this.resolver = resolver;
43 }
44
45 @Override
46 public void execute(OutgoingVariant outgoingVariant) {
47 var slots = outgoingVariant.getSlots();
48 var primarySlotProvider = outgoingVariant.getPrimarySlot();
49 var variant = outgoingVariant.getVariant();
50
51 // связываем конфигурацию
52 outgoingVariant.configureOutgoing(configuration -> {
53 var primarySlot = primarySlotProvider.get();
54 var outgoing = configuration.getOutgoing();
55
56 variantMaterialized(variant, configuration);
57
58 slotMaterialized(new ArtifactSlot(variant, primarySlot), true, outgoing.getAttributes());
59
60 outgoing.getVariants().configureEach(variantConfiguration -> {
61 var slotName = variantConfiguration.getName();
62 var slot = slots.findByName(slotName);
63 if (slot != null) {
64 slotMaterialized(new ArtifactSlot(variant, slot), false, variantConfiguration.getAttributes());
65 }
66 });
67 });
68 };
69
70 public void whenVariantMaterialized(Action<? super OutgoingConfigurationSpec> action) {
71 variantMaterialization.forEach(action::execute);
72 }
73
74 public void whenSlotMaterialized(Action<? super OutgoingConfigurationSlotSpec> action) {
75 slotMaterialization.forEach(action::execute);
76 }
77
78 private void variantMaterialized(Variant variant, Configuration configuration) {
79 variantMaterialization.add(new OutgoingConfigurationSpec() {
80
81 @Override
82 public Variant getVariant() {
83 return variant;
84 }
85
86 @Override
87 public Configuration getConfiguration() {
88 return configuration;
89 }
90
91 });
92 }
93
94 private void slotMaterialized(ArtifactSlot slot, boolean primary, AttributeContainer attributes) {
95 slotMaterialization.add(new OutgoingConfigurationSlotSpec() {
96 @Override
97 public boolean isPrimary() {
98 return primary;
99 }
100
101 @Override
102 public ArtifactSlot getArtifactSlot() {
103 return slot;
104 }
105
106 @Override
107 public ArtifactAssembly getAssembly() {
108 return resolver.require(slot);
109 }
110
111 @Override
112 public void artifactAttributes(Action<? super AttributeContainer> action) {
113 action.execute(attributes);
114 }
115 });
116 }
117
118 }
@@ -0,0 +1,33
1 package org.implab.gradle.variants.artifacts.internal;
2
3 import org.gradle.api.Action;
4 import org.gradle.api.InvalidUserDataException;
5 import org.gradle.api.provider.ProviderFactory;
6 import org.implab.gradle.variants.artifacts.OutgoingVariant;
7
8 public class SingleSlotConvention implements Action<OutgoingVariant> {
9
10 private final ProviderFactory providers;
11
12 public SingleSlotConvention(ProviderFactory providers) {
13 this.providers = providers;
14 }
15
16 @Override
17 public void execute(OutgoingVariant outgoingVariant) {
18 var slots = outgoingVariant.getSlots();
19
20 outgoingVariant.getPrimarySlot().convention(
21 // если есть ровно один слот, то он считается primary
22 providers.provider(() -> {
23 if (slots.size() == 0)
24 throw new InvalidUserDataException("No slots declared for " + outgoingVariant.getVariant());
25 if (slots.size() > 1)
26 throw new InvalidUserDataException("Multiple slots declared for " + outgoingVariant.getVariant() +
27 ", please specify primary slot explicitly");
28
29 return slots.stream().findAny().get();
30 }));
31 }
32
33 }
@@ -0,0 +1,9
1 /**
2 * Internal implementation of the variant artifacts plugin.
3 *
4 * <p>Types in this package are not part of the public API. They may change,
5 * move, or be removed without compatibility guarantees. Build logic should use
6 * the public contracts from {@link org.implab.gradle.variants.artifacts}
7 * instead.
8 */
9 package org.implab.gradle.variants.artifacts.internal;
@@ -1,54 +1,56
1 # AGENTS.md
1 # AGENTS.md
2
2
3 ## Проектные договоренности
3 ## Проектные договоренности
4
4
5 - для управления исходным кодом используется Mercurial
6
5 ### Публичное API библиотек
7 ### Публичное API библиотек
6
8
7 - Предпочтителен `non-null` подход.
9 - Предпочтителен `non-null` подход.
8 - Там, где значение живет в Gradle Provider API, возвращается `Provider<T>` (не `null`).
10 - Там, где значение живет в Gradle Provider API, возвращается `Provider<T>` (не `null`).
9 - Там, где lookup синхронный, возвращается `Optional<T>` (не `null`).
11 - Там, где lookup синхронный, возвращается `Optional<T>` (не `null`).
10 - `find*` рассматривается как синоним legacy `get*` (поиск без `fail-fast`).
12 - `find*` рассматривается как синоним legacy `get*` (поиск без `fail-fast`).
11 - `require*` это `find*` + `fail-fast` с понятной ошибкой в месте вызова.
13 - `require*` это `find*` + `fail-fast` с понятной ошибкой в месте вызова.
12 - Для нового API предпочтительны формы `find/require`; новые `get*` по возможности не добавлять.
14 - Для нового API предпочтительны формы `find/require`; новые `get*` по возможности не добавлять.
13 - Интерфейсы и классы, описывающие модели DSL должны иметь суффикс `Spec` у моделей описывающих уровень сервисов и состояние сценария сборки такого суффикса не должно быть.
15 - Интерфейсы и классы, описывающие модели DSL должны иметь суффикс `Spec` у моделей описывающих уровень сервисов и состояние сценария сборки такого суффикса не должно быть.
14 - Модель расширения на уровне проекта должна иметь суффикс `Extension`.
16 - Модель расширения на уровне проекта должна иметь суффикс `Extension`.
15
17
16 ### Документация
18 ### Документация
17
19
18 - документирование кода должно быть на английском языке
20 - документирование кода должно быть на английском языке
19 - к публичному API
21 - к публичному API
20 - описание должно отражать назначение, где используется и какое влияние оказывает на остальные части программы
22 - описание должно отражать назначение, где используется и какое влияние оказывает на остальные части программы
21 - давать небольшое описание концепции, а также краткие примеры
23 - давать небольшое описание концепции, а также краткие примеры
22 - к приватному API достаточно давать краткую справку о назначении и использовании
24 - к приватному API достаточно давать краткую справку о назначении и использовании
23 - реализацию алгоритмов в коде сопровождать комментариями с пояснениями, тривиальные операции пояснять не требуется.
25 - реализацию алгоритмов в коде сопровождать комментариями с пояснениями, тривиальные операции пояснять не требуется.
24 - документация должна формироваться согласно требованиям по форматированию типа javadoc, jsdoc и т.п., в зависимости от используемых в проекте языках и инструментах.
26 - документация должна формироваться согласно требованиям по форматированию типа javadoc, jsdoc и т.п., в зависимости от используемых в проекте языках и инструментах.
25
27
26 ## Identity-first modeling
28 ## Identity-first modeling
27
29
28 Prefer an **identity-first** split between:
30 Prefer an **identity-first** split between:
29
31
30 - **identity objects** used for discovery and selection
32 - **identity objects** used for discovery and selection
31 - **stateful/materialized objects** obtained through separate API calls
33 - **stateful/materialized objects** obtained through separate API calls
32
34
33 ### Rules
35 ### Rules
34
36
35 - Objects intended for replayable observation (`all(...)`, similar collection APIs) should be **identity objects**.
37 - Objects intended for replayable observation (`all(...)`, similar collection APIs) should be **identity objects**.
36 - Identity objects must be:
38 - Identity objects must be:
37 - cheap to create
39 - cheap to create
38 - effectively immutable
40 - effectively immutable
39 - limited to identity and cheap selection metadata
41 - limited to identity and cheap selection metadata
40 - Heavy, computed, provider-based, or runtime-bound state must be resolved separately.
42 - Heavy, computed, provider-based, or runtime-bound state must be resolved separately.
41 - Aggregate content should be accessed through dedicated lookup/materialization APIs.
43 - Aggregate content should be accessed through dedicated lookup/materialization APIs.
42 - Eager observation of identity is acceptable.
44 - Eager observation of identity is acceptable.
43 - Eager observation of computed state is not.
45 - Eager observation of computed state is not.
44
46
45 ### Do not
47 ### Do not
46
48
47 - Do not store heavy computed state inside identity objects.
49 - Do not store heavy computed state inside identity objects.
48 - Do not store runtime references to foreign domains inside identity objects.
50 - Do not store runtime references to foreign domains inside identity objects.
49 - Do not use custom events when replayable identity registries plus on-demand lookup are sufficient.
51 - Do not use custom events when replayable identity registries plus on-demand lookup are sufficient.
50
52
51 ### Rule of thumb
53 ### Rule of thumb
52
54
53 **Identity objects contain selection metadata.
55 **Identity objects contain selection metadata.
54 Aggregate content is obtained separately, on demand.**
56 Aggregate content is obtained separately, on demand.**
@@ -1,149 +1,147
1 package org.implab.gradle.common.core.lang;
1 package org.implab.gradle.common.core.lang;
2
2
3 import java.util.function.Consumer;
4 import java.util.function.Function;
5 import java.util.regex.Pattern;
3 import java.util.regex.Pattern;
6
4
7 import org.eclipse.jdt.annotation.NonNullByDefault;
5 import org.eclipse.jdt.annotation.NonNullByDefault;
8 import org.gradle.api.provider.Provider;
6 import org.gradle.api.provider.Provider;
9
7
10 @NonNullByDefault
8 @NonNullByDefault
11 public class Strings {
9 public class Strings {
12
10
13 private static final boolean[] ALLOWED_FILE_NAME_CHAR = new boolean[128];
11 private static final boolean[] ALLOWED_FILE_NAME_CHAR = new boolean[128];
14
12
15 private static final char[] HEX_DIGITS = "0123456789ABCDEF".toCharArray();
13 private static final char[] HEX_DIGITS = "0123456789ABCDEF".toCharArray();
16
14
17 private static final Pattern firstLetter = Pattern.compile("^\\w");
15 private static final Pattern firstLetter = Pattern.compile("^\\w");
18
16
19 private static final Pattern INVALID_NAME_CHAR = Pattern.compile("[^A-Za-z0-9_.-]");
17 private static final Pattern INVALID_NAME_CHAR = Pattern.compile("[^A-Za-z0-9_.-]");
20
18
21 static {
19 static {
22 for (char c = '0'; c <= '9'; c++)
20 for (char c = '0'; c <= '9'; c++)
23 ALLOWED_FILE_NAME_CHAR[c] = true;
21 ALLOWED_FILE_NAME_CHAR[c] = true;
24 for (char c = 'A'; c <= 'Z'; c++)
22 for (char c = 'A'; c <= 'Z'; c++)
25 ALLOWED_FILE_NAME_CHAR[c] = true;
23 ALLOWED_FILE_NAME_CHAR[c] = true;
26 for (char c = 'a'; c <= 'z'; c++)
24 for (char c = 'a'; c <= 'z'; c++)
27 ALLOWED_FILE_NAME_CHAR[c] = true;
25 ALLOWED_FILE_NAME_CHAR[c] = true;
28 ALLOWED_FILE_NAME_CHAR['.'] = true;
26 ALLOWED_FILE_NAME_CHAR['.'] = true;
29 ALLOWED_FILE_NAME_CHAR['_'] = true;
27 ALLOWED_FILE_NAME_CHAR['_'] = true;
30 ALLOWED_FILE_NAME_CHAR['-'] = true;
28 ALLOWED_FILE_NAME_CHAR['-'] = true;
31 }
29 }
32
30
33 public static String capitalize(String string) {
31 public static String capitalize(String string) {
34 return string == null ? null
32 return string == null ? null
35 : string.length() == 0 ? string
33 : string.length() == 0 ? string
36 : firstLetter.matcher(string).replaceFirst(m -> m.group().toUpperCase());
34 : firstLetter.matcher(string).replaceFirst(m -> m.group().toUpperCase());
37 }
35 }
38
36
39 public static String toCamelCase(String name) {
37 public static String toCamelCase(String name) {
40 if (name == null || name.isEmpty())
38 if (name == null || name.isEmpty())
41 return name;
39 return name;
42 StringBuilder out = new StringBuilder(name.length());
40 StringBuilder out = new StringBuilder(name.length());
43 boolean up = false;
41 boolean up = false;
44 boolean first = true;
42 boolean first = true;
45 for (int i = 0; i < name.length(); i++) {
43 for (int i = 0; i < name.length(); i++) {
46 char c = name.charAt(i);
44 char c = name.charAt(i);
47 switch (c) {
45 switch (c) {
48 case '-', '_', ' ', '.' -> up = true;
46 case '-', '_', ' ', '.' -> up = true;
49 default -> {
47 default -> {
50 out.append(
48 out.append(
51 first ? Character.toLowerCase(c)
49 first ? Character.toLowerCase(c)
52 : up ? Character.toUpperCase(c): c);
50 : up ? Character.toUpperCase(c): c);
53 up = false;
51 up = false;
54 first = false;
52 first = false;
55 }
53 }
56 }
54 }
57 }
55 }
58 return out.toString();
56 return out.toString();
59 }
57 }
60
58
61 public static void argumentNotNullOrEmpty(String value, String argumentName) {
59 public static void argumentNotNullOrEmpty(String value, String argumentName) {
62 if (value == null || value.length() == 0)
60 if (value == null || value.length() == 0)
63 throw new IllegalArgumentException(String.format("Argument %s can't be null or empty", argumentName));
61 throw new IllegalArgumentException(String.format("Argument %s can't be null or empty", argumentName));
64 }
62 }
65
63
66 public static void argumentNotNullOrBlank(String value, String argumentName) {
64 public static void argumentNotNullOrBlank(String value, String argumentName) {
67 if (value == null || value.trim().length() == 0)
65 if (value == null || value.trim().length() == 0)
68 throw new IllegalArgumentException(String.format("Argument %s can't be null or blank", argumentName));
66 throw new IllegalArgumentException(String.format("Argument %s can't be null or blank", argumentName));
69 }
67 }
70
68
71 public static String requireNonBlank(String value) {
69 public static String requireNonBlank(String value) {
72 argumentNotNullOrBlank(value, "value");
70 argumentNotNullOrBlank(value, "value");
73 return value;
71 return value;
74 }
72 }
75
73
76 public static String requireNonEmpty(String value) {
74 public static String requireNonEmpty(String value) {
77 argumentNotNullOrEmpty(value, "value");
75 argumentNotNullOrEmpty(value, "value");
78 return value;
76 return value;
79 }
77 }
80
78
81
79
82 public static String sanitizeName(String value) {
80 public static String sanitizeName(String value) {
83 return INVALID_NAME_CHAR.matcher(value).replaceAll("_");
81 return INVALID_NAME_CHAR.matcher(value).replaceAll("_");
84 }
82 }
85
83
86 public static String sanitizeFileName(String value) {
84 public static String sanitizeFileName(String value) {
87 int length = value.length();
85 int length = value.length();
88 for (int i = 0; i < length; i++) {
86 for (int i = 0; i < length; i++) {
89 char c = value.charAt(i);
87 char c = value.charAt(i);
90 if (c >= ALLOWED_FILE_NAME_CHAR.length || !ALLOWED_FILE_NAME_CHAR[c])
88 if (c >= ALLOWED_FILE_NAME_CHAR.length || !ALLOWED_FILE_NAME_CHAR[c])
91 return sanitizeFileName(value, i);
89 return sanitizeFileName(value, i);
92 }
90 }
93 return value;
91 return value;
94 }
92 }
95
93
96 public static String asString(Object value) {
94 public static String asString(Object value) {
97 if (value == null)
95 if (value == null)
98 return null;
96 return null;
99 if (value instanceof Provider<?> provider)
97 if (value instanceof Provider<?> provider)
100 return asString(provider.get());
98 return asString(provider.get());
101 else
99 else
102 return value.toString();
100 return value.toString();
103 }
101 }
104
102
105 private static String sanitizeFileName(String value, int invalidIndex) {
103 private static String sanitizeFileName(String value, int invalidIndex) {
106 int length = value.length();
104 int length = value.length();
107 StringBuilder out = new StringBuilder(length + 16);
105 StringBuilder out = new StringBuilder(length + 16);
108 out.append(value, 0, invalidIndex);
106 out.append(value, 0, invalidIndex);
109
107
110 for (int i = invalidIndex; i < length; i++) {
108 for (int i = invalidIndex; i < length; i++) {
111 char c = value.charAt(i);
109 char c = value.charAt(i);
112 if (c < ALLOWED_FILE_NAME_CHAR.length && ALLOWED_FILE_NAME_CHAR[c]) {
110 if (c < ALLOWED_FILE_NAME_CHAR.length && ALLOWED_FILE_NAME_CHAR[c]) {
113 out.append(c);
111 out.append(c);
114 } else if (Character.isHighSurrogate(c) && i + 1 < length && Character.isLowSurrogate(value.charAt(i + 1))) {
112 } else if (Character.isHighSurrogate(c) && i + 1 < length && Character.isLowSurrogate(value.charAt(i + 1))) {
115 appendUrlEncodedUtf8(out, Character.toCodePoint(c, value.charAt(++i)));
113 appendUrlEncodedUtf8(out, Character.toCodePoint(c, value.charAt(++i)));
116 } else if (Character.isSurrogate(c)) {
114 } else if (Character.isSurrogate(c)) {
117 appendUrlEncodedUtf8(out, 0xFFFD);
115 appendUrlEncodedUtf8(out, 0xFFFD);
118 } else {
116 } else {
119 appendUrlEncodedUtf8(out, c);
117 appendUrlEncodedUtf8(out, c);
120 }
118 }
121 }
119 }
122
120
123 return out.toString();
121 return out.toString();
124 }
122 }
125
123
126 private static void appendUrlEncodedUtf8(StringBuilder out, int codePoint) {
124 private static void appendUrlEncodedUtf8(StringBuilder out, int codePoint) {
127 if (codePoint <= 0x7F) {
125 if (codePoint <= 0x7F) {
128 appendUrlEncodedByte(out, codePoint);
126 appendUrlEncodedByte(out, codePoint);
129 } else if (codePoint <= 0x7FF) {
127 } else if (codePoint <= 0x7FF) {
130 appendUrlEncodedByte(out, 0xC0 | (codePoint >>> 6));
128 appendUrlEncodedByte(out, 0xC0 | (codePoint >>> 6));
131 appendUrlEncodedByte(out, 0x80 | (codePoint & 0x3F));
129 appendUrlEncodedByte(out, 0x80 | (codePoint & 0x3F));
132 } else if (codePoint <= 0xFFFF) {
130 } else if (codePoint <= 0xFFFF) {
133 appendUrlEncodedByte(out, 0xE0 | (codePoint >>> 12));
131 appendUrlEncodedByte(out, 0xE0 | (codePoint >>> 12));
134 appendUrlEncodedByte(out, 0x80 | ((codePoint >>> 6) & 0x3F));
132 appendUrlEncodedByte(out, 0x80 | ((codePoint >>> 6) & 0x3F));
135 appendUrlEncodedByte(out, 0x80 | (codePoint & 0x3F));
133 appendUrlEncodedByte(out, 0x80 | (codePoint & 0x3F));
136 } else {
134 } else {
137 appendUrlEncodedByte(out, 0xF0 | (codePoint >>> 18));
135 appendUrlEncodedByte(out, 0xF0 | (codePoint >>> 18));
138 appendUrlEncodedByte(out, 0x80 | ((codePoint >>> 12) & 0x3F));
136 appendUrlEncodedByte(out, 0x80 | ((codePoint >>> 12) & 0x3F));
139 appendUrlEncodedByte(out, 0x80 | ((codePoint >>> 6) & 0x3F));
137 appendUrlEncodedByte(out, 0x80 | ((codePoint >>> 6) & 0x3F));
140 appendUrlEncodedByte(out, 0x80 | (codePoint & 0x3F));
138 appendUrlEncodedByte(out, 0x80 | (codePoint & 0x3F));
141 }
139 }
142 }
140 }
143
141
144 private static void appendUrlEncodedByte(StringBuilder out, int value) {
142 private static void appendUrlEncodedByte(StringBuilder out, int value) {
145 out.append('%');
143 out.append('%');
146 out.append(HEX_DIGITS[(value >>> 4) & 0x0F]);
144 out.append(HEX_DIGITS[(value >>> 4) & 0x0F]);
147 out.append(HEX_DIGITS[value & 0x0F]);
145 out.append(HEX_DIGITS[value & 0x0F]);
148 }
146 }
149 }
147 }
@@ -1,45 +1,38
1 # design notes
1 # design notes
2
2
3 ## core model
3 ## core model
4
4
5 - OutgoingRegistry (Variant)
5 - OutgoingRegistry (Variant)
6 исходящая конфигурация
6 исходящая конфигурация
7 - [provider] configuration
7 - [provider] configuration
8 - [container, live] slots
8 - [container, live] slots
9 набор вариантов (слотов)
9 набор вариантов (слотов)
10 - [property] primarySlot
10 - [property] primarySlot
11 - AssemblyRegistry (Varaint, Slot)
11 - AssemblyRegistry (Varaint, Slot)
12 содержимое слота может быть добавлено после появления слота в OutgoingRegistry
12 содержимое слота может быть добавлено после появления слота в OutgoingRegistry
13 - assembleTask
13 - assembleTask
14 - inputs
14 - inputs
15 - compile unit output (CompileUnit, String)
15 - compile unit output (CompileUnit, String)
16 - direct object (task, file collection, etc.)
16 - direct object (task, file collection, etc.)
17 - artifact: directory
17 - artifact: directory
18 - customTask
18 - customTask
19 - artifact: FileSystemLocation
19 - artifact: FileSystemLocation
20 - when, all - replayable hooks для получения содержимого сборки
20
21
21 ## extension
22 ## extension
22
23
23 - adapter
24 - adapter
24 - whenFinalized
25 - whenFinalized
25 - dsl
26 - dsl
26 - variant(name)
27 - variant(name)
27 - slot(name)
28 - slot(name)
28 - from***
29 - from***
29 - whenFinalized
30 - whenFinalized
30 - whenOutgoingConfiguration
31 - whenOutgoingConfiguration
31 - whenOutgoingSlot
32 - whenOutgoingSlot
32
33
33 outgoing = outgoings.maybeCreate(variant)
34 - ArtifactAssemblyBridge
34
35 связывает содержимое ArtifactAssembly и исходящей конфигурации OutgoingVariant
35 slot = outgoings.slots.maybeCreate(slotName)
36 - VariantArtifactsHandler
36 assembly = assemblies.register(variantSlot, task, mapOutput)
37 управляет состоянием ArtifactAssembly, используется для DSL сборки
37 outgoing.configure(configuration -> {
38 - configureVariant конфигурирует VariantArtifactsSpec No newline at end of file
38 slots.all(slot -> {
39 assemblies.when(variant, slot) { assembly ->
40 configuration.variants.create(slot.name) {
41 artifact(assembly.artifact)
42 }
43 }
44 });
45 }); No newline at end of file
@@ -1,24 +1,27
1 package org.implab.gradle.internal;
1 package org.implab.gradle.internal;
2
2
3 import java.util.LinkedList;
3 import java.util.LinkedList;
4 import java.util.List;
4 import java.util.List;
5 import java.util.function.Consumer;
5 import java.util.function.Consumer;
6
6
7 import org.eclipse.jdt.annotation.NonNullByDefault;
8
9 @NonNullByDefault
7 public class ReplayableQueue<T> {
10 public class ReplayableQueue<T> {
8 private final List<Consumer<? super T>> consumers = new LinkedList<>();
11 private final List<Consumer<? super T>> consumers = new LinkedList<>();
9 private final List<T> values = new LinkedList<>();
12 private final List<T> values = new LinkedList<>();
10
13
11 public void add(T value) {
14 public void add(T value) {
12 consumers.forEach(consumer -> consumer.accept(value));
15 consumers.forEach(consumer -> consumer.accept(value));
13 values.add(value);
16 values.add(value);
14 }
17 }
15
18
16 List<T> values() {
19 List<T> values() {
17 return List.copyOf(values);
20 return List.copyOf(values);
18 }
21 }
19
22
20 public void forEach(Consumer<? super T> consumer) {
23 public void forEach(Consumer<? super T> consumer) {
21 values.forEach(consumer);
24 values.forEach(consumer);
22 consumers.add(consumer);
25 consumers.add(consumer);
23 }
26 }
24 } No newline at end of file
27 }
@@ -1,78 +1,112
1 package org.implab.gradle.variants;
1 package org.implab.gradle.variants;
2
2
3 import org.gradle.api.Action;
3 import org.gradle.api.Action;
4 import org.gradle.api.Plugin;
4 import org.gradle.api.Plugin;
5 import org.gradle.api.Project;
5 import org.gradle.api.Project;
6 import org.implab.gradle.common.core.lang.Deferred;
6 import org.implab.gradle.common.core.lang.Deferred;
7 import org.implab.gradle.variants.artifacts.ArtifactAssemblyRegistry;
8 import org.implab.gradle.variants.artifacts.OutgoingConfigurationSpec;
7 import org.implab.gradle.variants.artifacts.OutgoingConfigurationSpec;
9 import org.implab.gradle.variants.artifacts.OutgoingVariantsContext;
8 import org.implab.gradle.variants.artifacts.OutgoingVariantsContext;
10 import org.implab.gradle.variants.artifacts.VariantArtifactsExtension;
9 import org.implab.gradle.variants.artifacts.VariantArtifactsExtension;
11 import org.implab.gradle.variants.artifacts.VariantArtifactsSpec;
10 import org.implab.gradle.variants.artifacts.VariantArtifactsSpec;
12 import org.implab.gradle.variants.artifacts.internal.ArtifactAssemblyBridge;
11 import org.implab.gradle.variants.artifacts.internal.ArtifactAssemblyBinder;
13 import org.implab.gradle.variants.artifacts.internal.DefaultVariantArtifactSpec;
12 import org.implab.gradle.variants.artifacts.internal.ArtifactAssemblyHandler;
13 import org.implab.gradle.variants.artifacts.internal.ArtifactAssemblyRegistry;
14 import org.implab.gradle.variants.artifacts.internal.DefaultOutgoingVariantsContext;
15 import org.implab.gradle.variants.artifacts.internal.MaterializationPolicyHandler;
14 import org.implab.gradle.variants.artifacts.internal.OutgoingRegistry;
16 import org.implab.gradle.variants.artifacts.internal.OutgoingRegistry;
17 import org.implab.gradle.variants.artifacts.internal.SingleSlotConvention;
15 import org.implab.gradle.variants.core.Variant;
18 import org.implab.gradle.variants.core.Variant;
16 import org.implab.gradle.variants.core.VariantsExtension;
19 import org.implab.gradle.variants.core.VariantsExtension;
20 import org.implab.gradle.variants.sources.VariantSourcesExtension;
21 import org.implab.gradle.variants.artifacts.OutgoingConfigurationSlotSpec;
17
22
18 public abstract class VariantArtifactsPlugin implements Plugin<Project> {
23 public abstract class VariantArtifactsPlugin implements Plugin<Project> {
19
24
20 @Override
25 @Override
21 public void apply(Project target) {
26 public void apply(Project target) {
22 var extensions = target.getExtensions();
27 var extensions = target.getExtensions();
23 var objects = target.getObjects();
28 var objects = target.getObjects();
24 var providers = target.getProviders();
29 var providers = target.getProviders();
30 var plugins = target.getPlugins();
25 var configurations = target.getConfigurations();
31 var configurations = target.getConfigurations();
26 var tasks = target.getTasks();
32 var tasks = target.getTasks();
33 var layout = target.getLayout();
27
34
28 // Apply the main VariantsPlugin to ensure the core variant model is available.
35 // Apply the main VariantsPlugin to ensure the core variant model is available.
29 target.getPlugins().apply(VariantsPlugin.class);
36 plugins.apply(VariantsPlugin.class);
37 plugins.apply(VariantSourcesPlugin.class);
30 // Access the VariantsExtension to configure variant sources.
38 // Access the VariantsExtension to configure variant sources.
31 var variantsExtension = extensions.getByType(VariantsExtension.class);
39 var variantsExtension = extensions.getByType(VariantsExtension.class);
32
40 var sourcesExtension = extensions.getByType(VariantSourcesExtension.class);
33 var outgoing = new OutgoingRegistry(configurations, objects, providers);
34 var assemblies = new ArtifactAssemblyRegistry(objects, tasks);
35
36 // wire artifact assemblies to configuration variants
37 var assembliesBridge = new ArtifactAssemblyBridge(assemblies);
38
41
39 var deferred = new Deferred<OutgoingVariantsContext>();
42 var deferred = new Deferred<OutgoingVariantsContext>();
40
43
41 deferred.whenResolved(context -> context.all(assembliesBridge));
44 variantsExtension.whenFinalized(variants -> {
45
46 var outgoing = new OutgoingRegistry(configurations, objects, variants.getVariants());
47 var assemblies = new ArtifactAssemblyRegistry();
48
49 // wire artifact assemblies to configuration variants
50 var assembliesBridge = new ArtifactAssemblyBinder(assemblies);
51 var primarySlotConvention = new SingleSlotConvention(providers);
52 var materializationHandler = new MaterializationPolicyHandler(assemblies);
53
54 // bind slot assemblies to outgoing variants
55 outgoing.configureEach(assembliesBridge::execute);
56 // apply primary slot convention when outgoing variant has a single slot
57 outgoing.configureEach(primarySlotConvention::execute);
58 // apply materialization policy hooks to outgoing variants
59 outgoing.configureEach(materializationHandler::execute);
42
60
43 variantsExtension.whenFinalized(variants -> {
61 sourcesExtension.whenFinalized(sources -> {
62 var assemblyHandler = new ArtifactAssemblyHandler(
63 objects,
64 tasks,
65 assemblies,
66 sources.getCompileUnits(),
67 sources.getRoleProjections(),
68 sources.getSourceSets());
69 assemblyHandler.getAssembliesDirectory().set(layout.getBuildDirectory().dir("variant-assemblies"));
70
71 deferred.resolve(new DefaultOutgoingVariantsContext(
72 assemblies,
73 outgoing,
74 assemblyHandler,
75 materializationHandler));
76 });
44
77
45 });
78 });
46
79
47 var variantArtifacts = new VariantArtifactsExtension() {
80 var variantArtifacts = new VariantArtifactsExtension() {
48
81
49 @Override
82 @Override
50 public void variant(String variantName, Action<? super VariantArtifactsSpec> action) {
83 public void variant(String variantName, Action<? super VariantArtifactsSpec> action) {
51 deferred.whenResolved(context -> {
84 deferred.whenResolved(context -> {
52 new DefaultVariantArtifactSpec();
85 var variant = objects.named(Variant.class, variantName);
86 context.configureVariant(variant, action);
53 });
87 });
54 }
88 }
55
89
56 @Override
90 @Override
57 public void whenFinalized(Action<? super OutgoingVariantsContext> action) {
91 public void whenAvailable(Action<? super OutgoingVariantsContext> action) {
58 deferred.whenResolved(registry -> action.execute(registry.variantsContext()));
92 deferred.whenResolved(handler -> action.execute(handler));
59 }
93 }
60
94
61 @Override
95 @Override
62 public void whenOutgoingVariant(Action<? super OutgoingConfigurationSpec> action) {
96 public void whenOutgoingConfiguration(Action<? super OutgoingConfigurationSpec> action) {
63 deferred.whenResolved(registry -> registry.configureOutgoing(action));
97 deferred.whenResolved(registry -> registry.whenOutgoingConfiguration(action));
64
98
65 }
99 }
66
100
67 @Override
101 @Override
68 public void whenOutgoingSlot(Action<? super OutgoingArtifactSlotSpec> action) {
102 public void whenOutgoingSlot(Action<? super OutgoingConfigurationSlotSpec> action) {
69 deferred.whenResolved(registry -> registry.configureOutgoingSlot(action));
103 deferred.whenResolved(registry -> registry.whenOutgoingSlot(action));
70 }
104 }
71
105
72 };
106 };
73
107
74 extensions.add(VariantArtifactsExtension.class, "variantArtifacts", variantArtifacts);
108 extensions.add(VariantArtifactsExtension.class, "variantArtifacts", variantArtifacts);
75
109
76 }
110 }
77
111
78 }
112 }
@@ -1,54 +1,56
1 package org.implab.gradle.variants.artifacts;
1 package org.implab.gradle.variants.artifacts;
2
2
3 import java.util.Optional;
3 import java.util.Optional;
4
4
5 import org.eclipse.jdt.annotation.NonNullByDefault;
5 import org.eclipse.jdt.annotation.NonNullByDefault;
6 import org.gradle.api.Action;
6 import org.gradle.api.Action;
7 import org.gradle.api.InvalidUserDataException;
7 import org.gradle.api.InvalidUserDataException;
8
8
9 /**
9 /**
10 * Resolves stateful slot assemblies from cheap slot identities.
10 * Resolves stateful slot assemblies from cheap slot identities.
11 *
11 *
12 * <p>
12 * <p>
13 * The returned assembly is a materialized build-model handle. It may expose
13 * The returned assembly is a materialized build-model handle. It may expose
14 * lazy Gradle providers, but
14 * lazy Gradle providers, but
15 * it is no longer an identity object suitable for replayable discovery.
15 * it is no longer an identity object suitable for replayable discovery.
16 */
16 */
17 @NonNullByDefault
17 @NonNullByDefault
18 public interface ArtifactAssemblies {
18 public interface ArtifactAssemblies {
19 /**
19 /**
20 * Resolves the assembly for the given slot.
20 * Resolves the assembly for the given slot.
21 *
21 *
22 * <p>
22 * <p>
23 * This call materializes the build-facing body of the slot from its identity.
23 * This call materializes the build-facing body of the slot from its identity.
24 *
24 *
25 * @param slot slot identity inside a variant outgoing contract
25 * @param slot slot identity inside a variant outgoing contract
26 * @return assembly handle for the slot
26 * @return assembly handle for the slot
27 */
27 */
28 default ArtifactAssembly require(ArtifactSlot slot) {
28 default ArtifactAssembly require(ArtifactSlot slot) {
29 return find(slot)
29 return find(slot)
30 .orElseThrow(() -> new InvalidUserDataException("Artifact assembly '" + slot + "' isn't registered"));
30 .orElseThrow(() -> new InvalidUserDataException("Artifact assembly '" + slot + "' isn't registered"));
31 }
31 }
32
32
33 /**
33 /**
34 * Регистрирует обработчик на конкретный слот. Если слот еще не зарегистрирован,
34 * Registers a configuration action for the assembly of the given slot.
35 * то обработчик будет добавлен в очередь и будет вызван при регистрации слота.
36 * Порядок и точный момент вызова обработчиков не определен.
37 *
35 *
38 * @param slot Слот на который нужно зарегистрировать обработчик
36 * <p>If the assembly is already registered, the action is invoked immediately.
39 * @param action Обработчик
37 * Otherwise, it is queued and invoked when slot materialization registers the
38 * assembly handle.
39 *
40 * @param slot slot identity inside a variant outgoing contract
41 * @param action assembly configuration action
40 */
42 */
41 void when(ArtifactSlot slot, Action<? super ArtifactAssembly> action);
43 void when(ArtifactSlot slot, Action<? super ArtifactAssembly> action);
42
44
43 /**
45 /**
44 * Регистрирует глобальный обработчик, который будет вызван для всех слотов.
46 * Adds global configuration action for all materialized assemblies. If some assemblies are
45 * Обработчик будет вызван как для уже зарегистрированных слотов так и для тех,
47 * already registered, the action will be invoked for them immediately. For assemblies that
46 * которые будут зарегистрированы в будущем.
48 * are materialized later, the action will be invoked when they are registered.
47 *
49 *
48 * @param action Обработчик
50 * @param action assembly configuration action.
49 */
51 */
50 void all(Action<? super ArtifactAssembly> action);
52 void configureEach(Action<? super ArtifactAssembly> action);
51
53
52 /** Ищет зарегистрированный слот */
54 /** Ищет зарегистрированный слот */
53 Optional<ArtifactAssembly> find(ArtifactSlot slot);
55 Optional<ArtifactAssembly> find(ArtifactSlot slot);
54 }
56 }
@@ -1,59 +1,61
1 package org.implab.gradle.variants.artifacts;
1 package org.implab.gradle.variants.artifacts;
2
2
3 import org.gradle.api.Action;
3 import org.gradle.api.Action;
4 import org.gradle.api.Task;
4 import org.gradle.api.Task;
5 import org.gradle.api.attributes.AttributeContainer;
5 import org.gradle.api.attributes.AttributeContainer;
6 import groovy.lang.Closure;
6 import groovy.lang.Closure;
7 import org.implab.gradle.common.core.lang.Closures;
7 import org.implab.gradle.common.core.lang.Closures;
8
8
9 /**
9 /**
10 * Materialized outgoing publication state of a single slot.
10 * Materialized outgoing publication state of a single slot.
11 *
11 *
12 * <p>This type is a DSL facade to represent already created publication-facing state. Slot-specific
12 * <p>This type is a DSL facade to represent already created publication-facing state. Slot-specific
13 * publication tweaks should be applied here rather than through {@link OutgoingConfigurationSpec}, which
13 * publication tweaks should be applied here rather than through {@link OutgoingConfigurationSpec}, which
14 * is limited to the root outgoing configuration of the variant.
14 * is limited to the root outgoing configuration of the variant.
15 */
15 */
16 public interface OutgoingConfigurationSlotSpec {
16 public interface OutgoingConfigurationSlotSpec {
17 /**
17 /**
18 * Returns the published slot identity.
18 * Returns the published slot identity.
19 *
19 *
20 * @return slot identity
20 * @return slot identity
21 */
21 */
22 ArtifactSlot getArtifactSlot();
22 ArtifactSlot getArtifactSlot();
23
23
24 /**
24 /**
25 * Returns the assembly backing the published slot.
25 * Returns the assembly backing the published slot.
26 *
26 *
27 * @return slot assembly
27 * @return slot assembly
28 */
28 */
29 ArtifactAssembly getAssembly();
29 ArtifactAssembly getAssembly();
30
30
31 /**
31 /**
32 * Returns whether this slot is the primary outgoing artifact set of the variant.
32 * Returns whether this slot is the primary outgoing artifact set of the variant.
33 *
33 *
34 * @return {@code true} for the primary slot
34 * @return {@code true} for the primary slot
35 */
35 */
36 boolean isPrimary();
36 boolean isPrimary();
37
37
38 /**
38 /**
39 * Configures the task producing the slot artifact.
39 * Configures the task producing the slot artifact.
40 *
40 *
41 * @param action task configuration action
41 * @param action task configuration action
42 */
42 */
43 void assemblyTask(Action<? super Task> action);
43 default void assemblyTask(Action<? super Task> action) {
44 getAssembly().getAssemblyTask().configure(action);
45 }
44
46
45 default void assemblyTask(Closure<?> closure) {
47 default void assemblyTask(Closure<?> closure) {
46 assemblyTask(Closures.action(closure));
48 assemblyTask(Closures.action(closure));
47 }
49 }
48
50
49 /**
51 /**
50 * Configures attributes of this slot publication.
52 * Configures attributes of this slot publication.
51 *
53 *
52 * @param action artifact attribute configuration action
54 * @param action artifact attribute configuration action
53 */
55 */
54 void artifactAttributes(Action<? super AttributeContainer> action);
56 void artifactAttributes(Action<? super AttributeContainer> action);
55
57
56 default void artifactAttributes(Closure<?> closure) {
58 default void artifactAttributes(Closure<?> closure) {
57 artifactAttributes(Closures.action(closure));
59 artifactAttributes(Closures.action(closure));
58 }
60 }
59 }
61 }
@@ -1,50 +1,50
1 package org.implab.gradle.variants.artifacts;
1 package org.implab.gradle.variants.artifacts;
2
2
3 import org.gradle.api.Action;
3 import org.gradle.api.Action;
4 import org.gradle.api.NamedDomainObjectContainer;
4 import org.gradle.api.NamedDomainObjectContainer;
5 import org.gradle.api.NamedDomainObjectProvider;
5 import org.gradle.api.NamedDomainObjectProvider;
6 import org.gradle.api.artifacts.Configuration;
6 import org.gradle.api.artifacts.Configuration;
7 import org.gradle.api.provider.Property;
7 import org.gradle.api.provider.Property;
8 import org.implab.gradle.variants.core.Variant;
8 import org.implab.gradle.variants.core.Variant;
9
9
10 /**
10 /**
11 * Описывает исходящую конфигурацию варианта
11 * Описывает исходящую конфигурацию варианта
12 *
12 *
13 * Задает связь между моделью вариантов и моделью конфигураций gradle через
13 * Задает связь между моделью вариантов и моделью конфигураций gradle через
14 * свойство {@link #getConfiguration()}. Также задает отдельную ось слотов
14 * свойство {@link #getConfiguration()}. Также задает отдельную ось слотов
15 * публикации, но не задает правил связывания этих слотов с самой конфигурацией
15 * публикации, но не задает правил связывания этих слотов с самой конфигурацией
16 * и их содержимым. Самый простой вариант это {@link ArtifactAssemblies}.
16 * и их содержимым. Самый простой вариант это {@link ArtifactAssemblies}.
17 */
17 */
18 public interface OutgoingVariant {
18 public interface OutgoingVariant {
19 /**
19 /**
20 * Исходный вариант для которого строится Outgoing конфигурация
20 * Исходный вариант для которого строится Outgoing конфигурация
21 */
21 */
22 Variant getVariant();
22 Variant getVariant();
23
23
24 /**
24 /**
25 * Провайдер зарегистрированной конфигурации
25 * Провайдер зарегистрированной конфигурации
26 */
26 */
27 NamedDomainObjectProvider<Configuration> getConfiguration();
27 NamedDomainObjectProvider<? extends Configuration> getConfiguration();
28
28
29 default void configure(Action<? super Configuration> action) {
29 default void configureOutgoing(Action<? super Configuration> action) {
30 getConfiguration().configure(action);
30 getConfiguration().configure(action);
31 }
31 }
32
32
33 /**
33 /**
34 * Слоты конфигурации, данная коллекция живая, используется для
34 * Слоты конфигурации, данная коллекция живая, используется для
35 * получения информации об объявленных слотах, но эти слоты не
35 * получения информации об объявленных слотах, но эти слоты не
36 * обязаны быть сконфигурированы, т.е. это только Identity.
36 * обязаны быть сконфигурированы, т.е. это только Identity.
37 *
37 *
38 * @see {@link ArtifactSlot}
38 * @see {@link ArtifactSlot}
39 */
39 */
40 NamedDomainObjectContainer<Slot> getSlots();
40 NamedDomainObjectContainer<Slot> getSlots();
41
41
42 /**
42 /**
43 * Основной набор артефактов (primary variant) для исходящей конфигурации
43 * Основной набор артефактов (primary variant) для исходящей конфигурации
44 *
44 *
45 * <p>
45 * <p>
46 * Если в свойстве {@link #getSlots()} есть только один слой, то по конвенции он
46 * Если в свойстве {@link #getSlots()} есть только один слой, то по конвенции он
47 * считается также основным.
47 * считается также основным.
48 */
48 */
49 Property<Slot> getPrimarySlot();
49 Property<Slot> getPrimarySlot();
50 }
50 }
@@ -1,38 +1,36
1 package org.implab.gradle.variants.artifacts;
1 package org.implab.gradle.variants.artifacts;
2
2
3 import java.util.Optional;
3 import java.util.Optional;
4
4
5 import org.gradle.api.Action;
5 import org.gradle.api.Action;
6 import org.gradle.api.InvalidUserDataException;
6 import org.gradle.api.InvalidUserDataException;
7 import org.implab.gradle.variants.core.Variant;
7 import org.implab.gradle.variants.core.Variant;
8 import org.implab.gradle.variants.core.VariantsView;
9
8
10 /**
9 /**
11 * Контекст работы с вариантами публикации, становится доступным после
10 * Контекст работы с вариантами публикации, становится доступным после
12 * финализации модели вариантов. Фактически является живой моделью
11 * финализации модели вариантов. Фактически является живой моделью
13 */
12 */
14 public interface OutgoingVariantsContext {
13 public interface OutgoingVariantsContext {
15
14
16 /**
15 ArtifactAssemblies getAssemblies();
17 * Зафиксированное представление о вариантах на основе которого адаптеры могут
18 * конфигурировать артефакты и исходящие конфигурации
19 */
20 VariantsView getVariants();
21
16
22 ArtifactAssemblies getAssemblies();
17 void configureVariant(Variant variant, Action<? super VariantArtifactsSpec> action);
23
18
24 /**
19 /**
25 * Replayable hook для всех объявленных конфигураций
20 * Replayable hook для всех объявленных конфигураций
26 */
21 */
27 void all(Action<? super OutgoingVariant> action);
22 void configureEach(Action<? super OutgoingVariant> action);
28
23
29 Optional<OutgoingVariant> findOutgoing(Variant variant);
24 Optional<OutgoingVariant> findOutgoing(Variant variant);
30
25
31 default OutgoingVariant requireOutgoing(Variant variant) {
26 default OutgoingVariant requireOutgoing(Variant variant) {
32 return findOutgoing(variant)
27 return findOutgoing(variant)
33 .orElseThrow(() -> new InvalidUserDataException("Outgoing variant '" + variant + "' isn't registered"));
28 .orElseThrow(() -> new InvalidUserDataException("Outgoing variant '" + variant + "' isn't registered"));
34 }
29 }
35
30
36 ArtifactAssemblyRules slotRules(ArtifactSlot slot);
31 void whenOutgoingConfiguration(Action<? super OutgoingConfigurationSpec> action);
32
33 void whenOutgoingSlot(Action<? super OutgoingConfigurationSlotSpec> action);
34
37
35
38 }
36 }
@@ -1,59 +1,64
1 package org.implab.gradle.variants.artifacts;
1 package org.implab.gradle.variants.artifacts;
2
2
3 import org.gradle.api.Action;
3 import org.gradle.api.Action;
4 import org.implab.gradle.common.core.lang.Closures;
4 import org.implab.gradle.common.core.lang.Closures;
5
5
6 import groovy.lang.Closure;
6 import groovy.lang.Closure;
7
7
8 /**
8 /**
9 * Project-level DSL entry point for declaring outgoing artifacts per variant.
9 * Project-level DSL entry point for declaring outgoing artifacts per variant.
10 *
10 *
11 * <p>A variant represents one external outgoing contract. Slots declared inside a variant represent
11 * <p>
12 * artifact sets within that contract. One slot is expected to materialize to one published artifact.
12 * A variant represents one external outgoing contract. Slots declared inside a
13 * variant represent
14 * artifact sets within that contract. One slot is expected to materialize to
15 * one published artifact.
13 */
16 */
14 public interface VariantArtifactsExtension {
17 public interface VariantArtifactsExtension {
15 /**
18 /**
16 * Configures artifact slots of the named variant.
19 * Configures artifact slots of the named variant.
17 *
20 *
18 * @param variantName variant name
21 * @param variantName variant name
19 * @param action variant artifact declaration
22 * @param action variant artifact declaration
20 */
23 */
21 void variant(String variantName, Action<? super VariantArtifactsSpec> action);
24 void variant(String variantName, Action<? super VariantArtifactsSpec> action);
22
25
23 default void variant(String variantName, Closure<?> closure) {
26 default void variant(String variantName, Closure<?> closure) {
24 variant(variantName, Closures.action(closure));
27 variant(variantName, Closures.action(closure));
25 }
28 }
26
29
27 /**
30 /**
28 * Registers a callback invoked with the finalized artifact model.
31 * Registers a callback invoked when the outgoing variants context becomes
32 * available.
29 *
33 *
30 * @param action finalized-model callback
34 * @param action outgoing variants context callback
31 */
35 */
32 void whenFinalized(Action<? super OutgoingVariantsContext> action);
36 void whenAvailable(Action<? super OutgoingVariantsContext> action);
33
37
34 default void whenFinalized(Closure<?> closure) {
38 default void whenAvailable(Closure<?> closure) {
35 whenFinalized(Closures.action(closure));
39 whenAvailable(Closures.action(closure));
36 }
40 }
37
41
38 /**
42 /**
39 * Registers a callback invoked for each materialized root outgoing configuration.
43 * Registers a callback invoked for each materialized root outgoing
44 * configuration.
40 *
45 *
41 * @param action variant-level outgoing configuration callback
46 * @param action variant-level outgoing configuration callback
42 */
47 */
43 void whenOutgoingVariant(Action<? super OutgoingConfigurationSpec> action);
48 void whenOutgoingConfiguration(Action<? super OutgoingConfigurationSpec> action);
44
49
45 default void whenOutgoingVariant(Closure<?> closure) {
50 default void whenOutgoingConfiguration(Closure<?> closure) {
46 whenOutgoingVariant(Closures.action(closure));
51 whenOutgoingConfiguration(Closures.action(closure));
47 }
52 }
48
53
49 /**
54 /**
50 * Registers a callback invoked for each materialized outgoing slot publication.
55 * Registers a callback invoked for each materialized outgoing slot publication.
51 *
56 *
52 * @param action slot-level outgoing publication callback
57 * @param action slot-level outgoing publication callback
53 */
58 */
54 void whenOutgoingSlot(Action<? super OutgoingConfigurationSlotSpec> action);
59 void whenOutgoingSlot(Action<? super OutgoingConfigurationSlotSpec> action);
55
60
56 default void whenOutgoingSlot(Closure<?> closure) {
61 default void whenOutgoingSlot(Closure<?> closure) {
57 whenOutgoingSlot(Closures.action(closure));
62 whenOutgoingSlot(Closures.action(closure));
58 }
63 }
59 }
64 }
@@ -1,54 +1,56
1 package org.implab.gradle.variants.artifacts.internal;
1 package org.implab.gradle.variants.artifacts.internal;
2
2
3 import org.eclipse.jdt.annotation.NonNullByDefault;
3 import org.eclipse.jdt.annotation.NonNullByDefault;
4 import org.gradle.api.Action;
4 import org.gradle.api.Action;
5 import org.implab.gradle.variants.artifacts.ArtifactAssemblies;
5 import org.implab.gradle.variants.artifacts.ArtifactAssemblies;
6 import org.implab.gradle.variants.artifacts.ArtifactSlot;
6 import org.implab.gradle.variants.artifacts.ArtifactSlot;
7 import org.implab.gradle.variants.artifacts.OutgoingVariant;
7 import org.implab.gradle.variants.artifacts.OutgoingVariant;
8
8
9 /**
9 /**
10 * Связывает описание исходящих конфигураций gradle и сборку содержимого слотов
10 * Связывает описание исходящих конфигураций gradle и сборку содержимого слотов
11 * из {@link ArtifactAssemblies}
11 * из {@link ArtifactAssemblies}
12 */
12 */
13 @NonNullByDefault
13 @NonNullByDefault
14 public class ArtifactAssemblyBridge implements Action<OutgoingVariant> {
14 public class ArtifactAssemblyBinder implements Action<OutgoingVariant> {
15
15
16 private final ArtifactAssemblies resolver;
16 private final ArtifactAssemblies resolver;
17
17
18 public ArtifactAssemblyBridge(ArtifactAssemblies resolver) {
18 public ArtifactAssemblyBinder(ArtifactAssemblies resolver) {
19 this.resolver = resolver;
19 this.resolver = resolver;
20 }
20 }
21
21
22 @Override
22 @Override
23 public void execute(OutgoingVariant outgoingVariant) {
23 public void execute(OutgoingVariant outgoingVariant) {
24 var slots = outgoingVariant.getSlots();
24 var slots = outgoingVariant.getSlots();
25 var primarySlotProvider = outgoingVariant.getPrimarySlot();
25 var primarySlotProvider = outgoingVariant.getPrimarySlot();
26 var variant = outgoingVariant.getVariant();
26 var variant = outgoingVariant.getVariant();
27
27
28 // связываем конфигурацию
28 // связываем конфигурацию
29 outgoingVariant.configure(configuration -> {
29 outgoingVariant.configureOutgoing(configuration -> {
30 var primarySlot = primarySlotProvider.get();
30 var primarySlot = primarySlotProvider.get();
31 var outgoing = configuration.getOutgoing();
31 var outgoing = configuration.getOutgoing();
32
32
33 // связываем основной вариант конфигурации
33 // связываем основной вариант конфигурации
34 resolver.when(
34 resolver.when(
35 new ArtifactSlot(variant, primarySlot),
35 new ArtifactSlot(variant, primarySlot),
36 assembly -> outgoing.artifact(assembly.getArtifact()));
36 assembly -> outgoing.artifact(assembly.getArtifact()));
37
37
38 // для всех объявленных слотов
38 // для всех объявленных слотов
39 slots.all(slot -> {
39 slots.all(slot -> {
40 // кроме основного
40 // кроме основного
41 if (slot.equals(primarySlot))
41 if (slot.equals(primarySlot))
42 return;
42 return;
43
43
44 // связываем артефакты
44 // связываем артефакты
45 resolver.when(
45 resolver.when(
46 new ArtifactSlot(variant, slot),
46 new ArtifactSlot(variant, slot),
47 assembly -> outgoing.getVariants()
47 assembly -> outgoing.getVariants()
48 .register(slot.getName())
48 .register(slot.getName())
49 .configure(cv -> cv.artifact(assembly.getArtifact())));
49 .configure(cv -> cv.artifact(assembly.getArtifact())));
50 });
50 });
51 });
51 });
52 }
52 }
53
53
54
55
54 }
56 }
@@ -1,87 +1,90
1 package org.implab.gradle.variants.artifacts;
1 package org.implab.gradle.variants.artifacts.internal;
2
2
3 import java.util.LinkedHashMap;
3 import java.util.LinkedHashMap;
4 import java.util.Map;
4 import java.util.Map;
5 import java.util.Optional;
5 import java.util.Optional;
6 import java.util.function.Function;
6 import java.util.function.Function;
7
7
8 import org.eclipse.jdt.annotation.NonNullByDefault;
8 import org.eclipse.jdt.annotation.NonNullByDefault;
9 import org.gradle.api.Action;
9 import org.gradle.api.Action;
10 import org.gradle.api.InvalidUserDataException;
10 import org.gradle.api.InvalidUserDataException;
11 import org.gradle.api.Task;
11 import org.gradle.api.Task;
12 import org.gradle.api.file.FileSystemLocation;
12 import org.gradle.api.file.FileSystemLocation;
13 import org.gradle.api.provider.Provider;
13 import org.gradle.api.provider.Provider;
14 import org.gradle.api.tasks.TaskProvider;
14 import org.gradle.api.tasks.TaskProvider;
15 import org.implab.gradle.common.core.lang.Deferred;
15 import org.implab.gradle.common.core.lang.Deferred;
16 import org.implab.gradle.internal.ReplayableQueue;
16 import org.implab.gradle.internal.ReplayableQueue;
17 import org.implab.gradle.variants.artifacts.ArtifactAssemblies;
18 import org.implab.gradle.variants.artifacts.ArtifactAssembly;
19 import org.implab.gradle.variants.artifacts.ArtifactSlot;
17
20
18 @NonNullByDefault
21 @NonNullByDefault
19 public class ArtifactAssemblyRegistry implements ArtifactAssemblies {
22 public class ArtifactAssemblyRegistry implements ArtifactAssemblies {
20 private final Map<ArtifactSlot, Deferred<ArtifactAssembly>> assembliesBySlots = new LinkedHashMap<>();
23 private final Map<ArtifactSlot, Deferred<ArtifactAssembly>> assembliesBySlots = new LinkedHashMap<>();
21 private final ReplayableQueue<ArtifactAssembly> assemblies = new ReplayableQueue<>();
24 private final ReplayableQueue<ArtifactAssembly> assemblies = new ReplayableQueue<>();
22
25
23 public ArtifactAssemblyRegistry() {
26 public ArtifactAssemblyRegistry() {
24 }
27 }
25
28
26 public <T extends Task> ArtifactAssembly register(
29 public <T extends Task> ArtifactAssembly register(
27 ArtifactSlot slot,
30 ArtifactSlot slot,
28 TaskProvider<T> task,
31 TaskProvider<T> task,
29 Function<? super T, ? extends Provider<? extends FileSystemLocation>> mapOutputArtifact) {
32 Function<? super T, ? extends Provider<? extends FileSystemLocation>> mapOutputArtifact) {
30
33
31 var deferred = getDeferred(slot);
34 var deferred = getDeferred(slot);
32 if (deferred.resolved()) {
35 if (deferred.resolved()) {
33 throw new InvalidUserDataException("Artifact assembly '" + slot + "' is already registered");
36 throw new InvalidUserDataException("Artifact assembly '" + slot + "' is already registered");
34 }
37 }
35 var outputArtifact = task.flatMap(mapOutputArtifact::apply);
38 var outputArtifact = task.flatMap(mapOutputArtifact::apply);
36
39
37 var assembly = new Assembly(outputArtifact, task);
40 var assembly = new Assembly(outputArtifact, task);
38 deferred.resolve(assembly);
41 deferred.resolve(assembly);
39 assemblies.add(assembly);
42 assemblies.add(assembly);
40 return assembly;
43 return assembly;
41 }
44 }
42
45
43 @Override
46 @Override
44 public Optional<ArtifactAssembly> find(ArtifactSlot slot) {
47 public Optional<ArtifactAssembly> find(ArtifactSlot slot) {
45 // to prevent creation of map entries on lookup use the map directly
48 // to prevent creation of map entries on lookup use the map directly
46 var deferred = assembliesBySlots.get(slot);
49 var deferred = assembliesBySlots.get(slot);
47 return deferred != null && deferred.resolved()
50 return deferred != null && deferred.resolved()
48 ? Optional.of(deferred.value())
51 ? Optional.of(deferred.value())
49 : Optional.empty();
52 : Optional.empty();
50 }
53 }
51
54
52 @Override
55 @Override
53 public void when(ArtifactSlot slot, Action<? super ArtifactAssembly> action) {
56 public void when(ArtifactSlot slot, Action<? super ArtifactAssembly> action) {
54 getDeferred(slot).whenResolved(action::execute);
57 getDeferred(slot).whenResolved(action::execute);
55 }
58 }
56
59
57 @Override
60 @Override
58 public void all(Action<? super ArtifactAssembly> action) {
61 public void configureEach(Action<? super ArtifactAssembly> action) {
59 assemblies.forEach(action::execute);
62 assemblies.forEach(action::execute);
60 }
63 }
61
64
62 private Deferred<ArtifactAssembly> getDeferred(ArtifactSlot slot) {
65 private Deferred<ArtifactAssembly> getDeferred(ArtifactSlot slot) {
63 return assembliesBySlots.computeIfAbsent(slot, k -> new Deferred<>());
66 return assembliesBySlots.computeIfAbsent(slot, k -> new Deferred<>());
64 }
67 }
65
68
66 static class Assembly implements ArtifactAssembly {
69 static class Assembly implements ArtifactAssembly {
67
70
68 private final Provider<? extends FileSystemLocation> artifact;
71 private final Provider<? extends FileSystemLocation> artifact;
69 private final TaskProvider<? extends Task> task;
72 private final TaskProvider<? extends Task> task;
70
73
71 Assembly(Provider<? extends FileSystemLocation> artifact, TaskProvider<? extends Task> task) {
74 Assembly(Provider<? extends FileSystemLocation> artifact, TaskProvider<? extends Task> task) {
72 this.artifact = artifact;
75 this.artifact = artifact;
73 this.task = task;
76 this.task = task;
74 }
77 }
75
78
76 @Override
79 @Override
77 public Provider<? extends FileSystemLocation> getArtifact() {
80 public Provider<? extends FileSystemLocation> getArtifact() {
78 return artifact;
81 return artifact;
79 }
82 }
80
83
81 @Override
84 @Override
82 public TaskProvider<? extends Task> getAssemblyTask() {
85 public TaskProvider<? extends Task> getAssemblyTask() {
83 return task;
86 return task;
84 }
87 }
85
88
86 }
89 }
87 }
90 }
@@ -1,77 +1,79
1 package org.implab.gradle.variants.artifacts.internal;
1 package org.implab.gradle.variants.artifacts.internal;
2
2
3 import java.util.HashSet;
3 import java.util.HashSet;
4 import java.util.Set;
4 import java.util.Set;
5 import java.util.function.Consumer;
5 import java.util.function.Consumer;
6 import java.util.stream.Stream;
6 import java.util.stream.Stream;
7
7
8 import org.eclipse.jdt.annotation.NonNullByDefault;
8 import org.gradle.api.Action;
9 import org.gradle.api.Action;
9 import org.gradle.api.model.ObjectFactory;
10 import org.gradle.api.model.ObjectFactory;
10 import org.implab.gradle.common.core.lang.Strings;
11 import org.implab.gradle.common.core.lang.Strings;
11 import org.implab.gradle.variants.artifacts.ArtifactAssemblySpec;
12 import org.implab.gradle.variants.artifacts.ArtifactAssemblySpec;
12 import org.implab.gradle.variants.core.Layer;
13 import org.implab.gradle.variants.core.Layer;
13 import org.implab.gradle.variants.core.Role;
14 import org.implab.gradle.variants.core.Role;
14 import org.implab.gradle.variants.artifacts.OutputSelectionSpec;
15 import org.implab.gradle.variants.artifacts.OutputSelectionSpec;
15
16
16 /**
17 /**
17 * Реализация DSL модели, строит набор {@link SlotContribution}. При построении набора
18 * Реализация DSL модели, строит набор {@link SlotContribution}. При построении набора
18 * правила и корректность не проверяются. По окончании использования клиент
19 * правила и корректность не проверяются. По окончании использования клиент
19 * вызывает метод {@link #process(Consumer)} для обработки результатов.
20 * вызывает метод {@link #process(Consumer)} для обработки результатов.
20 *
21 *
21 */
22 */
23 @NonNullByDefault
22 final class DefaultArtifactAssemblySpec implements ArtifactAssemblySpec {
24 final class DefaultArtifactAssemblySpec implements ArtifactAssemblySpec {
23 private final Consumer<? super SlotContribution> consumer;
25 private final Consumer<? super SlotContribution> consumer;
24 private final ObjectFactory objectFactory;
26 private final ObjectFactory objectFactory;
25
27
26 DefaultArtifactAssemblySpec(ObjectFactory objectFactory, Consumer<? super SlotContribution> consumer) {
28 DefaultArtifactAssemblySpec(ObjectFactory objectFactory, Consumer<? super SlotContribution> consumer) {
27 this.consumer = consumer;
29 this.consumer = consumer;
28 this.objectFactory = objectFactory;
30 this.objectFactory = objectFactory;
29 }
31 }
30
32
31 @Override
33 @Override
32 public void from(Object artifact) {
34 public void from(Object artifact) {
33 consumer.accept(new DirectContribution(artifact));
35 consumer.accept(new DirectContribution(artifact));
34 }
36 }
35
37
36 @Override
38 @Override
37 public void fromVariant(Action<? super OutputSelectionSpec> action) {
39 public void fromVariant(Action<? super OutputSelectionSpec> action) {
38 consumer.accept(new VariantOutputsContribution(outputs(action)));
40 consumer.accept(new VariantOutputsContribution(outputs(action)));
39 }
41 }
40
42
41 @Override
43 @Override
42 public void fromRole(String roleName, Action<? super OutputSelectionSpec> action) {
44 public void fromRole(String roleName, Action<? super OutputSelectionSpec> action) {
43
45
44 consumer.accept(new RoleOutputsContribution(
46 consumer.accept(new RoleOutputsContribution(
45 objectFactory.named(Role.class, roleName),
47 objectFactory.named(Role.class, roleName),
46 outputs(action)));
48 outputs(action)));
47 }
49 }
48
50
49 @Override
51 @Override
50 public void fromLayer(String layerName, Action<? super OutputSelectionSpec> action) {
52 public void fromLayer(String layerName, Action<? super OutputSelectionSpec> action) {
51 consumer.accept(new LayerOutputsContribution(
53 consumer.accept(new LayerOutputsContribution(
52 objectFactory.named(Layer.class, layerName),
54 objectFactory.named(Layer.class, layerName),
53 outputs(action)));
55 outputs(action)));
54 }
56 }
55
57
56 private static Set<String> outputs(Action<? super OutputSelectionSpec> action) {
58 private static Set<String> outputs(Action<? super OutputSelectionSpec> action) {
57 var spec = new OutputsSetSpec();
59 var spec = new OutputsSetSpec();
58 action.execute(spec);
60 action.execute(spec);
59 return spec.outputs();
61 return spec.outputs();
60 }
62 }
61
63
62 private static class OutputsSetSpec implements OutputSelectionSpec {
64 private static class OutputsSetSpec implements OutputSelectionSpec {
63 private final Set<String> outputs = new HashSet<>();
65 private final Set<String> outputs = new HashSet<>();
64
66
65 @Override
67 @Override
66 public void output(String name, String... extra) {
68 public void output(String name, String... extra) {
67 Stream.concat(Stream.of(name), Stream.of(extra))
69 Stream.concat(Stream.of(name), Stream.of(extra))
68 .map(Strings::requireNonBlank)
70 .map(Strings::requireNonBlank)
69 .forEach(outputs::add);
71 .forEach(outputs::add);
70 }
72 }
71
73
72 Set<String> outputs() {
74 Set<String> outputs() {
73 return Set.copyOf(outputs);
75 return Set.copyOf(outputs);
74 }
76 }
75
77
76 }
78 }
77 }
79 }
@@ -1,56 +1,121
1 package org.implab.gradle.variants.artifacts.internal;
1 package org.implab.gradle.variants.artifacts.internal;
2
2
3 import java.util.LinkedHashMap;
3 import java.util.LinkedHashMap;
4 import java.util.LinkedList;
5 import java.util.List;
6 import java.util.Map;
4 import java.util.Map;
7 import java.util.Optional;
5 import java.util.Optional;
6 import java.util.Set;
8 import java.util.function.Consumer;
7 import java.util.function.Consumer;
9
8
9 import org.eclipse.jdt.annotation.NonNullByDefault;
10 import org.gradle.api.InvalidUserDataException;
11 import org.gradle.api.NamedDomainObjectContainer;
12 import org.gradle.api.NamedDomainObjectProvider;
13 import org.gradle.api.artifacts.Configuration;
10 import org.gradle.api.artifacts.ConfigurationContainer;
14 import org.gradle.api.artifacts.ConfigurationContainer;
11 import org.gradle.api.model.ObjectFactory;
15 import org.gradle.api.model.ObjectFactory;
12 import org.gradle.api.provider.ProviderFactory;
16 import org.gradle.api.provider.Property;
17 import org.implab.gradle.internal.ReplayableQueue;
13 import org.implab.gradle.variants.artifacts.OutgoingVariant;
18 import org.implab.gradle.variants.artifacts.OutgoingVariant;
19 import org.implab.gradle.variants.artifacts.Slot;
14 import org.implab.gradle.variants.core.Variant;
20 import org.implab.gradle.variants.core.Variant;
15
21
22 /**
23 * Реестр исходящих вариантов. Связывает исходящие конфигурации с вариантами
24 * сборки. Связь устанавливается 1:1.
25 */
26 @NonNullByDefault
16 public class OutgoingRegistry {
27 public class OutgoingRegistry {
17 private final Map<Variant, DefaultOutgoingConfiguration> outgoingByVariant = new LinkedHashMap<>();
28 private final Map<Variant, OutgoingVariant> outgoingByVariant = new LinkedHashMap<>();
18 private final List<Consumer<? super DefaultOutgoingConfiguration>> hooks = new LinkedList<>();
29 private final ReplayableQueue<OutgoingVariant> outgoingVariants = new ReplayableQueue<>();
19
20 private final ConfigurationContainer configurations;
30 private final ConfigurationContainer configurations;
21 private final ObjectFactory objects;
31 private final ObjectFactory objects;
22 private final ProviderFactory providers;
32 private final Set<Variant> declaredVariants;
23
33
24 public OutgoingRegistry(ConfigurationContainer configurations, ObjectFactory objects, ProviderFactory providers) {
34 public OutgoingRegistry(
35 ConfigurationContainer configurations,
36 ObjectFactory objects,
37 Set<Variant> declaredVariants) {
25 this.configurations = configurations;
38 this.configurations = configurations;
26 this.objects = objects;
39 this.objects = objects;
27 this.providers = providers;
40 this.declaredVariants = declaredVariants;
28 }
41 }
29
42
30 public Optional<OutgoingVariant> findOutgoing(Variant variant) {
43 public Optional<OutgoingVariant> find(Variant variant) {
31 return Optional.ofNullable(outgoingByVariant.get(variant));
44 return Optional.ofNullable(outgoingByVariant.get(variant));
32 }
45 }
33
46
34 public OutgoingVariant maybeCreate(Variant variant) {
47 public OutgoingVariant maybeCreate(Variant variant) {
35 var outgoing = outgoingByVariant.computeIfAbsent(variant, this::newOutgoingConfiguration);
48 return find(variant).orElseGet(() -> create(variant));
36 hooks.forEach(hook -> hook.accept(outgoing));
49 }
50
51 public OutgoingVariant create(Variant variant) {
52 if (!declaredVariants.contains(variant))
53 throw new InvalidUserDataException("Variant " + variant + " isn't declared");
54 if (outgoingByVariant.containsKey(variant))
55 throw new InvalidUserDataException("Outgoing variant " + variant + " already exists");
56
57 var configuration = configurations.consumable(outgoingConfigurationName(variant));
58 var outgoing = new Outgoing(variant, configuration);
59
60 outgoingByVariant.put(variant, outgoing);
61
62 outgoingVariants.add(outgoing);
63
37 return outgoing;
64 return outgoing;
38 }
65 }
39
66
40 public void all(Consumer<? super OutgoingVariant> action) {
67 /**
41 outgoingByVariant.values().forEach(action);
68 * Replayable hook which is applied when an outgoing variant is defined
42 hooks.add(action);
69 *
43 }
70 * @param action
44
71 */
45 private DefaultOutgoingConfiguration newOutgoingConfiguration(Variant variant) {
72 public void configureEach(Consumer<? super OutgoingVariant> action) {
46 var configuration = configurations.register(outgoingConfigurationName(variant));
73 outgoingVariants.forEach(action);
47
48 return new DefaultOutgoingConfiguration(variant, configuration, objects, providers);
49 }
74 }
50
75
51 private String outgoingConfigurationName(Variant variant) {
76 private String outgoingConfigurationName(Variant variant) {
52 return variant.getName() + "Elements";
77 return variant.getName() + "Elements";
53 }
78 }
54
79
80 private class Outgoing implements OutgoingVariant {
81
82 private final Variant variant;
83
84 private final NamedDomainObjectProvider<? extends Configuration> configurationProvider;
85
86 private final NamedDomainObjectContainer<Slot> slots;
87
88 private final Property<Slot> primarySlot;
89
90 public Outgoing(
91 Variant variant,
92 NamedDomainObjectProvider<? extends Configuration> configurationProvider) {
93 this.variant = variant;
94 this.configurationProvider = configurationProvider;
95 this.slots = objects.domainObjectContainer(Slot.class);
96 this.primarySlot = objects.property(Slot.class);
97 primarySlot.finalizeValueOnRead();
98 }
99
100 @Override
101 public Property<Slot> getPrimarySlot() {
102 return primarySlot;
103 }
104
105 @Override
106 public Variant getVariant() {
107 return variant;
108 }
109
110 @Override
111 public NamedDomainObjectProvider<? extends Configuration> getConfiguration() {
112 return configurationProvider;
113 }
114
115 @Override
116 public NamedDomainObjectContainer<Slot> getSlots() {
117 return slots;
118 }
119 }
55
120
56 }
121 }
@@ -1,9 +1,9
1 package org.implab.gradle.variants.artifacts.internal;
1 package org.implab.gradle.variants.artifacts.internal;
2
2
3 import org.eclipse.jdt.annotation.NonNullByDefault;
3 import org.eclipse.jdt.annotation.NonNullByDefault;
4
4
5 @NonNullByDefault
5 @NonNullByDefault
6 public interface SlotContribution {
6 interface SlotContribution {
7
7
8 void accept(SlotContributionVisitor visitor);
8 void accept(SlotContributionVisitor visitor);
9 }
9 }
@@ -1,17 +1,15
1 package org.implab.gradle.variants.artifacts.internal;
1 package org.implab.gradle.variants.artifacts.internal;
2
2
3 import java.util.function.Consumer;
3 import org.eclipse.jdt.annotation.NonNullByDefault;
4
4
5 public interface SlotContributionVisitor {
5 @NonNullByDefault
6 interface SlotContributionVisitor {
6 void visit(DirectContribution contribution);
7 void visit(DirectContribution contribution);
7
8
8 void visit(VariantOutputsContribution contribution);
9 void visit(VariantOutputsContribution contribution);
9
10
10 void visit(RoleOutputsContribution contribution);
11 void visit(RoleOutputsContribution contribution);
11
12
12 void visit(LayerOutputsContribution contribution);
13 void visit(LayerOutputsContribution contribution);
13
14
14 default Consumer<SlotContribution> consumer() {
15 return slot -> slot.accept(this);
16 }
17 }
15 }
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now