##// 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 1 # AGENTS.md
2 2
3 3 ## Проектные договоренности
4 4
5 - для управления исходным кодом используется Mercurial
6
5 7 ### Публичное API библиотек
6 8
7 9 - Предпочтителен `non-null` подход.
8 10 - Там, где значение живет в Gradle Provider API, возвращается `Provider<T>` (не `null`).
9 11 - Там, где lookup синхронный, возвращается `Optional<T>` (не `null`).
10 12 - `find*` рассматривается как синоним legacy `get*` (поиск без `fail-fast`).
11 13 - `require*` это `find*` + `fail-fast` с понятной ошибкой в месте вызова.
12 14 - Для нового API предпочтительны формы `find/require`; новые `get*` по возможности не добавлять.
13 15 - Интерфейсы и классы, описывающие модели DSL должны иметь суффикс `Spec` у моделей описывающих уровень сервисов и состояние сценария сборки такого суффикса не должно быть.
14 16 - Модель расширения на уровне проекта должна иметь суффикс `Extension`.
15 17
16 18 ### Документация
17 19
18 20 - документирование кода должно быть на английском языке
19 21 - к публичному API
20 22 - описание должно отражать назначение, где используется и какое влияние оказывает на остальные части программы
21 23 - давать небольшое описание концепции, а также краткие примеры
22 24 - к приватному API достаточно давать краткую справку о назначении и использовании
23 25 - реализацию алгоритмов в коде сопровождать комментариями с пояснениями, тривиальные операции пояснять не требуется.
24 26 - документация должна формироваться согласно требованиям по форматированию типа javadoc, jsdoc и т.п., в зависимости от используемых в проекте языках и инструментах.
25 27
26 28 ## Identity-first modeling
27 29
28 30 Prefer an **identity-first** split between:
29 31
30 32 - **identity objects** used for discovery and selection
31 33 - **stateful/materialized objects** obtained through separate API calls
32 34
33 35 ### Rules
34 36
35 37 - Objects intended for replayable observation (`all(...)`, similar collection APIs) should be **identity objects**.
36 38 - Identity objects must be:
37 39 - cheap to create
38 40 - effectively immutable
39 41 - limited to identity and cheap selection metadata
40 42 - Heavy, computed, provider-based, or runtime-bound state must be resolved separately.
41 43 - Aggregate content should be accessed through dedicated lookup/materialization APIs.
42 44 - Eager observation of identity is acceptable.
43 45 - Eager observation of computed state is not.
44 46
45 47 ### Do not
46 48
47 49 - Do not store heavy computed state inside identity objects.
48 50 - Do not store runtime references to foreign domains inside identity objects.
49 51 - Do not use custom events when replayable identity registries plus on-demand lookup are sufficient.
50 52
51 53 ### Rule of thumb
52 54
53 55 **Identity objects contain selection metadata.
54 56 Aggregate content is obtained separately, on demand.**
@@ -1,149 +1,147
1 1 package org.implab.gradle.common.core.lang;
2 2
3 import java.util.function.Consumer;
4 import java.util.function.Function;
5 3 import java.util.regex.Pattern;
6 4
7 5 import org.eclipse.jdt.annotation.NonNullByDefault;
8 6 import org.gradle.api.provider.Provider;
9 7
10 8 @NonNullByDefault
11 9 public class Strings {
12 10
13 11 private static final boolean[] ALLOWED_FILE_NAME_CHAR = new boolean[128];
14 12
15 13 private static final char[] HEX_DIGITS = "0123456789ABCDEF".toCharArray();
16 14
17 15 private static final Pattern firstLetter = Pattern.compile("^\\w");
18 16
19 17 private static final Pattern INVALID_NAME_CHAR = Pattern.compile("[^A-Za-z0-9_.-]");
20 18
21 19 static {
22 20 for (char c = '0'; c <= '9'; c++)
23 21 ALLOWED_FILE_NAME_CHAR[c] = true;
24 22 for (char c = 'A'; c <= 'Z'; c++)
25 23 ALLOWED_FILE_NAME_CHAR[c] = true;
26 24 for (char c = 'a'; c <= 'z'; c++)
27 25 ALLOWED_FILE_NAME_CHAR[c] = true;
28 26 ALLOWED_FILE_NAME_CHAR['.'] = true;
29 27 ALLOWED_FILE_NAME_CHAR['_'] = true;
30 28 ALLOWED_FILE_NAME_CHAR['-'] = true;
31 29 }
32 30
33 31 public static String capitalize(String string) {
34 32 return string == null ? null
35 33 : string.length() == 0 ? string
36 34 : firstLetter.matcher(string).replaceFirst(m -> m.group().toUpperCase());
37 35 }
38 36
39 37 public static String toCamelCase(String name) {
40 38 if (name == null || name.isEmpty())
41 39 return name;
42 40 StringBuilder out = new StringBuilder(name.length());
43 41 boolean up = false;
44 42 boolean first = true;
45 43 for (int i = 0; i < name.length(); i++) {
46 44 char c = name.charAt(i);
47 45 switch (c) {
48 46 case '-', '_', ' ', '.' -> up = true;
49 47 default -> {
50 48 out.append(
51 49 first ? Character.toLowerCase(c)
52 50 : up ? Character.toUpperCase(c): c);
53 51 up = false;
54 52 first = false;
55 53 }
56 54 }
57 55 }
58 56 return out.toString();
59 57 }
60 58
61 59 public static void argumentNotNullOrEmpty(String value, String argumentName) {
62 60 if (value == null || value.length() == 0)
63 61 throw new IllegalArgumentException(String.format("Argument %s can't be null or empty", argumentName));
64 62 }
65 63
66 64 public static void argumentNotNullOrBlank(String value, String argumentName) {
67 65 if (value == null || value.trim().length() == 0)
68 66 throw new IllegalArgumentException(String.format("Argument %s can't be null or blank", argumentName));
69 67 }
70 68
71 69 public static String requireNonBlank(String value) {
72 70 argumentNotNullOrBlank(value, "value");
73 71 return value;
74 72 }
75 73
76 74 public static String requireNonEmpty(String value) {
77 75 argumentNotNullOrEmpty(value, "value");
78 76 return value;
79 77 }
80 78
81 79
82 80 public static String sanitizeName(String value) {
83 81 return INVALID_NAME_CHAR.matcher(value).replaceAll("_");
84 82 }
85 83
86 84 public static String sanitizeFileName(String value) {
87 85 int length = value.length();
88 86 for (int i = 0; i < length; i++) {
89 87 char c = value.charAt(i);
90 88 if (c >= ALLOWED_FILE_NAME_CHAR.length || !ALLOWED_FILE_NAME_CHAR[c])
91 89 return sanitizeFileName(value, i);
92 90 }
93 91 return value;
94 92 }
95 93
96 94 public static String asString(Object value) {
97 95 if (value == null)
98 96 return null;
99 97 if (value instanceof Provider<?> provider)
100 98 return asString(provider.get());
101 99 else
102 100 return value.toString();
103 101 }
104 102
105 103 private static String sanitizeFileName(String value, int invalidIndex) {
106 104 int length = value.length();
107 105 StringBuilder out = new StringBuilder(length + 16);
108 106 out.append(value, 0, invalidIndex);
109 107
110 108 for (int i = invalidIndex; i < length; i++) {
111 109 char c = value.charAt(i);
112 110 if (c < ALLOWED_FILE_NAME_CHAR.length && ALLOWED_FILE_NAME_CHAR[c]) {
113 111 out.append(c);
114 112 } else if (Character.isHighSurrogate(c) && i + 1 < length && Character.isLowSurrogate(value.charAt(i + 1))) {
115 113 appendUrlEncodedUtf8(out, Character.toCodePoint(c, value.charAt(++i)));
116 114 } else if (Character.isSurrogate(c)) {
117 115 appendUrlEncodedUtf8(out, 0xFFFD);
118 116 } else {
119 117 appendUrlEncodedUtf8(out, c);
120 118 }
121 119 }
122 120
123 121 return out.toString();
124 122 }
125 123
126 124 private static void appendUrlEncodedUtf8(StringBuilder out, int codePoint) {
127 125 if (codePoint <= 0x7F) {
128 126 appendUrlEncodedByte(out, codePoint);
129 127 } else if (codePoint <= 0x7FF) {
130 128 appendUrlEncodedByte(out, 0xC0 | (codePoint >>> 6));
131 129 appendUrlEncodedByte(out, 0x80 | (codePoint & 0x3F));
132 130 } else if (codePoint <= 0xFFFF) {
133 131 appendUrlEncodedByte(out, 0xE0 | (codePoint >>> 12));
134 132 appendUrlEncodedByte(out, 0x80 | ((codePoint >>> 6) & 0x3F));
135 133 appendUrlEncodedByte(out, 0x80 | (codePoint & 0x3F));
136 134 } else {
137 135 appendUrlEncodedByte(out, 0xF0 | (codePoint >>> 18));
138 136 appendUrlEncodedByte(out, 0x80 | ((codePoint >>> 12) & 0x3F));
139 137 appendUrlEncodedByte(out, 0x80 | ((codePoint >>> 6) & 0x3F));
140 138 appendUrlEncodedByte(out, 0x80 | (codePoint & 0x3F));
141 139 }
142 140 }
143 141
144 142 private static void appendUrlEncodedByte(StringBuilder out, int value) {
145 143 out.append('%');
146 144 out.append(HEX_DIGITS[(value >>> 4) & 0x0F]);
147 145 out.append(HEX_DIGITS[value & 0x0F]);
148 146 }
149 147 }
@@ -1,45 +1,38
1 1 # design notes
2 2
3 3 ## core model
4 4
5 5 - OutgoingRegistry (Variant)
6 6 исходящая конфигурация
7 7 - [provider] configuration
8 8 - [container, live] slots
9 9 набор вариантов (слотов)
10 10 - [property] primarySlot
11 11 - AssemblyRegistry (Varaint, Slot)
12 12 содержимое слота может быть добавлено после появления слота в OutgoingRegistry
13 13 - assembleTask
14 14 - inputs
15 15 - compile unit output (CompileUnit, String)
16 16 - direct object (task, file collection, etc.)
17 17 - artifact: directory
18 18 - customTask
19 19 - artifact: FileSystemLocation
20 - when, all - replayable hooks для получения содержимого сборки
20 21
21 22 ## extension
22 23
23 24 - adapter
24 25 - whenFinalized
25 26 - dsl
26 27 - variant(name)
27 28 - slot(name)
28 29 - from***
29 30 - whenFinalized
30 31 - whenOutgoingConfiguration
31 32 - whenOutgoingSlot
32 33
33 outgoing = outgoings.maybeCreate(variant)
34
35 slot = outgoings.slots.maybeCreate(slotName)
36 assembly = assemblies.register(variantSlot, task, mapOutput)
37 outgoing.configure(configuration -> {
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
34 - ArtifactAssemblyBridge
35 связывает содержимое ArtifactAssembly и исходящей конфигурации OutgoingVariant
36 - VariantArtifactsHandler
37 управляет состоянием ArtifactAssembly, используется для DSL сборки
38 - configureVariant конфигурирует VariantArtifactsSpec No newline at end of file
@@ -1,24 +1,27
1 1 package org.implab.gradle.internal;
2 2
3 3 import java.util.LinkedList;
4 4 import java.util.List;
5 5 import java.util.function.Consumer;
6 6
7 import org.eclipse.jdt.annotation.NonNullByDefault;
8
9 @NonNullByDefault
7 10 public class ReplayableQueue<T> {
8 11 private final List<Consumer<? super T>> consumers = new LinkedList<>();
9 12 private final List<T> values = new LinkedList<>();
10 13
11 14 public void add(T value) {
12 15 consumers.forEach(consumer -> consumer.accept(value));
13 16 values.add(value);
14 17 }
15 18
16 19 List<T> values() {
17 20 return List.copyOf(values);
18 21 }
19 22
20 23 public void forEach(Consumer<? super T> consumer) {
21 24 values.forEach(consumer);
22 25 consumers.add(consumer);
23 26 }
24 27 } No newline at end of file
@@ -1,78 +1,112
1 1 package org.implab.gradle.variants;
2 2
3 3 import org.gradle.api.Action;
4 4 import org.gradle.api.Plugin;
5 5 import org.gradle.api.Project;
6 6 import org.implab.gradle.common.core.lang.Deferred;
7 import org.implab.gradle.variants.artifacts.ArtifactAssemblyRegistry;
8 7 import org.implab.gradle.variants.artifacts.OutgoingConfigurationSpec;
9 8 import org.implab.gradle.variants.artifacts.OutgoingVariantsContext;
10 9 import org.implab.gradle.variants.artifacts.VariantArtifactsExtension;
11 10 import org.implab.gradle.variants.artifacts.VariantArtifactsSpec;
12 import org.implab.gradle.variants.artifacts.internal.ArtifactAssemblyBridge;
13 import org.implab.gradle.variants.artifacts.internal.DefaultVariantArtifactSpec;
11 import org.implab.gradle.variants.artifacts.internal.ArtifactAssemblyBinder;
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 16 import org.implab.gradle.variants.artifacts.internal.OutgoingRegistry;
17 import org.implab.gradle.variants.artifacts.internal.SingleSlotConvention;
15 18 import org.implab.gradle.variants.core.Variant;
16 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 23 public abstract class VariantArtifactsPlugin implements Plugin<Project> {
19 24
20 25 @Override
21 26 public void apply(Project target) {
22 27 var extensions = target.getExtensions();
23 28 var objects = target.getObjects();
24 29 var providers = target.getProviders();
30 var plugins = target.getPlugins();
25 31 var configurations = target.getConfigurations();
26 32 var tasks = target.getTasks();
33 var layout = target.getLayout();
27 34
28 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 38 // Access the VariantsExtension to configure variant sources.
31 39 var variantsExtension = extensions.getByType(VariantsExtension.class);
32
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);
40 var sourcesExtension = extensions.getByType(VariantSourcesExtension.class);
38 41
39 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 80 var variantArtifacts = new VariantArtifactsExtension() {
48 81
49 82 @Override
50 83 public void variant(String variantName, Action<? super VariantArtifactsSpec> action) {
51 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 90 @Override
57 public void whenFinalized(Action<? super OutgoingVariantsContext> action) {
58 deferred.whenResolved(registry -> action.execute(registry.variantsContext()));
91 public void whenAvailable(Action<? super OutgoingVariantsContext> action) {
92 deferred.whenResolved(handler -> action.execute(handler));
59 93 }
60 94
61 95 @Override
62 public void whenOutgoingVariant(Action<? super OutgoingConfigurationSpec> action) {
63 deferred.whenResolved(registry -> registry.configureOutgoing(action));
96 public void whenOutgoingConfiguration(Action<? super OutgoingConfigurationSpec> action) {
97 deferred.whenResolved(registry -> registry.whenOutgoingConfiguration(action));
64 98
65 99 }
66 100
67 101 @Override
68 public void whenOutgoingSlot(Action<? super OutgoingArtifactSlotSpec> action) {
69 deferred.whenResolved(registry -> registry.configureOutgoingSlot(action));
102 public void whenOutgoingSlot(Action<? super OutgoingConfigurationSlotSpec> action) {
103 deferred.whenResolved(registry -> registry.whenOutgoingSlot(action));
70 104 }
71 105
72 106 };
73 107
74 108 extensions.add(VariantArtifactsExtension.class, "variantArtifacts", variantArtifacts);
75 109
76 110 }
77 111
78 112 }
@@ -1,54 +1,56
1 1 package org.implab.gradle.variants.artifacts;
2 2
3 3 import java.util.Optional;
4 4
5 5 import org.eclipse.jdt.annotation.NonNullByDefault;
6 6 import org.gradle.api.Action;
7 7 import org.gradle.api.InvalidUserDataException;
8 8
9 9 /**
10 10 * Resolves stateful slot assemblies from cheap slot identities.
11 11 *
12 12 * <p>
13 13 * The returned assembly is a materialized build-model handle. It may expose
14 14 * lazy Gradle providers, but
15 15 * it is no longer an identity object suitable for replayable discovery.
16 16 */
17 17 @NonNullByDefault
18 18 public interface ArtifactAssemblies {
19 19 /**
20 20 * Resolves the assembly for the given slot.
21 21 *
22 22 * <p>
23 23 * This call materializes the build-facing body of the slot from its identity.
24 24 *
25 25 * @param slot slot identity inside a variant outgoing contract
26 26 * @return assembly handle for the slot
27 27 */
28 28 default ArtifactAssembly require(ArtifactSlot slot) {
29 29 return find(slot)
30 30 .orElseThrow(() -> new InvalidUserDataException("Artifact assembly '" + slot + "' isn't registered"));
31 31 }
32 32
33 33 /**
34 * Регистрирует обработчик на конкретный слот. Если слот еще не зарегистрирован,
35 * то обработчик будет добавлен в очередь и будет вызван при регистрации слота.
36 * Порядок и точный момент вызова обработчиков не определен.
34 * Registers a configuration action for the assembly of the given slot.
37 35 *
38 * @param slot Слот на который нужно зарегистрировать обработчик
39 * @param action Обработчик
36 * <p>If the assembly is already registered, the action is invoked immediately.
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 43 void when(ArtifactSlot slot, Action<? super ArtifactAssembly> action);
42 44
43 45 /**
44 * Регистрирует глобальный обработчик, который будет вызван для всех слотов.
45 * Обработчик будет вызван как для уже зарегистрированных слотов так и для тех,
46 * которые будут зарегистрированы в будущем.
46 * Adds global configuration action for all materialized assemblies. If some assemblies are
47 * already registered, the action will be invoked for them immediately. For assemblies that
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 55 Optional<ArtifactAssembly> find(ArtifactSlot slot);
54 56 }
@@ -1,59 +1,61
1 1 package org.implab.gradle.variants.artifacts;
2 2
3 3 import org.gradle.api.Action;
4 4 import org.gradle.api.Task;
5 5 import org.gradle.api.attributes.AttributeContainer;
6 6 import groovy.lang.Closure;
7 7 import org.implab.gradle.common.core.lang.Closures;
8 8
9 9 /**
10 10 * Materialized outgoing publication state of a single slot.
11 11 *
12 12 * <p>This type is a DSL facade to represent already created publication-facing state. Slot-specific
13 13 * publication tweaks should be applied here rather than through {@link OutgoingConfigurationSpec}, which
14 14 * is limited to the root outgoing configuration of the variant.
15 15 */
16 16 public interface OutgoingConfigurationSlotSpec {
17 17 /**
18 18 * Returns the published slot identity.
19 19 *
20 20 * @return slot identity
21 21 */
22 22 ArtifactSlot getArtifactSlot();
23 23
24 24 /**
25 25 * Returns the assembly backing the published slot.
26 26 *
27 27 * @return slot assembly
28 28 */
29 29 ArtifactAssembly getAssembly();
30 30
31 31 /**
32 32 * Returns whether this slot is the primary outgoing artifact set of the variant.
33 33 *
34 34 * @return {@code true} for the primary slot
35 35 */
36 36 boolean isPrimary();
37 37
38 38 /**
39 39 * Configures the task producing the slot artifact.
40 40 *
41 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 47 default void assemblyTask(Closure<?> closure) {
46 48 assemblyTask(Closures.action(closure));
47 49 }
48 50
49 51 /**
50 52 * Configures attributes of this slot publication.
51 53 *
52 54 * @param action artifact attribute configuration action
53 55 */
54 56 void artifactAttributes(Action<? super AttributeContainer> action);
55 57
56 58 default void artifactAttributes(Closure<?> closure) {
57 59 artifactAttributes(Closures.action(closure));
58 60 }
59 61 }
@@ -1,50 +1,50
1 1 package org.implab.gradle.variants.artifacts;
2 2
3 3 import org.gradle.api.Action;
4 4 import org.gradle.api.NamedDomainObjectContainer;
5 5 import org.gradle.api.NamedDomainObjectProvider;
6 6 import org.gradle.api.artifacts.Configuration;
7 7 import org.gradle.api.provider.Property;
8 8 import org.implab.gradle.variants.core.Variant;
9 9
10 10 /**
11 11 * Описывает исходящую конфигурацию варианта
12 12 *
13 13 * Задает связь между моделью вариантов и моделью конфигураций gradle через
14 14 * свойство {@link #getConfiguration()}. Также задает отдельную ось слотов
15 15 * публикации, но не задает правил связывания этих слотов с самой конфигурацией
16 16 * и их содержимым. Самый простой вариант это {@link ArtifactAssemblies}.
17 17 */
18 18 public interface OutgoingVariant {
19 19 /**
20 20 * Исходный вариант для которого строится Outgoing конфигурация
21 21 */
22 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 30 getConfiguration().configure(action);
31 31 }
32 32
33 33 /**
34 34 * Слоты конфигурации, данная коллекция живая, используется для
35 35 * получения информации об объявленных слотах, но эти слоты не
36 36 * обязаны быть сконфигурированы, т.е. это только Identity.
37 37 *
38 38 * @see {@link ArtifactSlot}
39 39 */
40 40 NamedDomainObjectContainer<Slot> getSlots();
41 41
42 42 /**
43 43 * Основной набор артефактов (primary variant) для исходящей конфигурации
44 44 *
45 45 * <p>
46 46 * Если в свойстве {@link #getSlots()} есть только один слой, то по конвенции он
47 47 * считается также основным.
48 48 */
49 49 Property<Slot> getPrimarySlot();
50 50 }
@@ -1,38 +1,36
1 1 package org.implab.gradle.variants.artifacts;
2 2
3 3 import java.util.Optional;
4 4
5 5 import org.gradle.api.Action;
6 6 import org.gradle.api.InvalidUserDataException;
7 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 13 public interface OutgoingVariantsContext {
15 14
16 /**
17 * Зафиксированное представление о вариантах на основе которого адаптеры могут
18 * конфигурировать артефакты и исходящие конфигурации
19 */
20 VariantsView getVariants();
15 ArtifactAssemblies getAssemblies();
21 16
22 ArtifactAssemblies getAssemblies();
17 void configureVariant(Variant variant, Action<? super VariantArtifactsSpec> action);
23 18
24 19 /**
25 20 * Replayable hook для всех объявленных конфигураций
26 21 */
27 void all(Action<? super OutgoingVariant> action);
22 void configureEach(Action<? super OutgoingVariant> action);
28 23
29 24 Optional<OutgoingVariant> findOutgoing(Variant variant);
30 25
31 26 default OutgoingVariant requireOutgoing(Variant variant) {
32 27 return findOutgoing(variant)
33 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 1 package org.implab.gradle.variants.artifacts;
2 2
3 3 import org.gradle.api.Action;
4 4 import org.implab.gradle.common.core.lang.Closures;
5 5
6 6 import groovy.lang.Closure;
7 7
8 8 /**
9 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
12 * artifact sets within that contract. One slot is expected to materialize to one published artifact.
11 * <p>
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 17 public interface VariantArtifactsExtension {
15 18 /**
16 19 * Configures artifact slots of the named variant.
17 20 *
18 21 * @param variantName variant name
19 22 * @param action variant artifact declaration
20 23 */
21 24 void variant(String variantName, Action<? super VariantArtifactsSpec> action);
22 25
23 26 default void variant(String variantName, Closure<?> closure) {
24 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) {
35 whenFinalized(Closures.action(closure));
38 default void whenAvailable(Closure<?> 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 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) {
46 whenOutgoingVariant(Closures.action(closure));
50 default void whenOutgoingConfiguration(Closure<?> closure) {
51 whenOutgoingConfiguration(Closures.action(closure));
47 52 }
48 53
49 54 /**
50 55 * Registers a callback invoked for each materialized outgoing slot publication.
51 56 *
52 57 * @param action slot-level outgoing publication callback
53 58 */
54 59 void whenOutgoingSlot(Action<? super OutgoingConfigurationSlotSpec> action);
55 60
56 61 default void whenOutgoingSlot(Closure<?> closure) {
57 62 whenOutgoingSlot(Closures.action(closure));
58 63 }
59 64 }
@@ -1,54 +1,56
1 1 package org.implab.gradle.variants.artifacts.internal;
2 2
3 3 import org.eclipse.jdt.annotation.NonNullByDefault;
4 4 import org.gradle.api.Action;
5 5 import org.implab.gradle.variants.artifacts.ArtifactAssemblies;
6 6 import org.implab.gradle.variants.artifacts.ArtifactSlot;
7 7 import org.implab.gradle.variants.artifacts.OutgoingVariant;
8 8
9 9 /**
10 10 * Связывает описание исходящих конфигураций gradle и сборку содержимого слотов
11 11 * из {@link ArtifactAssemblies}
12 12 */
13 13 @NonNullByDefault
14 public class ArtifactAssemblyBridge implements Action<OutgoingVariant> {
14 public class ArtifactAssemblyBinder implements Action<OutgoingVariant> {
15 15
16 16 private final ArtifactAssemblies resolver;
17 17
18 public ArtifactAssemblyBridge(ArtifactAssemblies resolver) {
18 public ArtifactAssemblyBinder(ArtifactAssemblies resolver) {
19 19 this.resolver = resolver;
20 20 }
21 21
22 22 @Override
23 23 public void execute(OutgoingVariant outgoingVariant) {
24 24 var slots = outgoingVariant.getSlots();
25 25 var primarySlotProvider = outgoingVariant.getPrimarySlot();
26 26 var variant = outgoingVariant.getVariant();
27 27
28 28 // связываем конфигурацию
29 outgoingVariant.configure(configuration -> {
29 outgoingVariant.configureOutgoing(configuration -> {
30 30 var primarySlot = primarySlotProvider.get();
31 31 var outgoing = configuration.getOutgoing();
32 32
33 33 // связываем основной вариант конфигурации
34 34 resolver.when(
35 35 new ArtifactSlot(variant, primarySlot),
36 36 assembly -> outgoing.artifact(assembly.getArtifact()));
37 37
38 38 // для всех объявленных слотов
39 39 slots.all(slot -> {
40 40 // кроме основного
41 41 if (slot.equals(primarySlot))
42 42 return;
43 43
44 44 // связываем артефакты
45 45 resolver.when(
46 46 new ArtifactSlot(variant, slot),
47 47 assembly -> outgoing.getVariants()
48 48 .register(slot.getName())
49 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 3 import java.util.LinkedHashMap;
4 4 import java.util.Map;
5 5 import java.util.Optional;
6 6 import java.util.function.Function;
7 7
8 8 import org.eclipse.jdt.annotation.NonNullByDefault;
9 9 import org.gradle.api.Action;
10 10 import org.gradle.api.InvalidUserDataException;
11 11 import org.gradle.api.Task;
12 12 import org.gradle.api.file.FileSystemLocation;
13 13 import org.gradle.api.provider.Provider;
14 14 import org.gradle.api.tasks.TaskProvider;
15 15 import org.implab.gradle.common.core.lang.Deferred;
16 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 21 @NonNullByDefault
19 22 public class ArtifactAssemblyRegistry implements ArtifactAssemblies {
20 23 private final Map<ArtifactSlot, Deferred<ArtifactAssembly>> assembliesBySlots = new LinkedHashMap<>();
21 24 private final ReplayableQueue<ArtifactAssembly> assemblies = new ReplayableQueue<>();
22 25
23 26 public ArtifactAssemblyRegistry() {
24 27 }
25 28
26 29 public <T extends Task> ArtifactAssembly register(
27 30 ArtifactSlot slot,
28 31 TaskProvider<T> task,
29 32 Function<? super T, ? extends Provider<? extends FileSystemLocation>> mapOutputArtifact) {
30 33
31 34 var deferred = getDeferred(slot);
32 35 if (deferred.resolved()) {
33 36 throw new InvalidUserDataException("Artifact assembly '" + slot + "' is already registered");
34 37 }
35 38 var outputArtifact = task.flatMap(mapOutputArtifact::apply);
36 39
37 40 var assembly = new Assembly(outputArtifact, task);
38 41 deferred.resolve(assembly);
39 42 assemblies.add(assembly);
40 43 return assembly;
41 44 }
42 45
43 46 @Override
44 47 public Optional<ArtifactAssembly> find(ArtifactSlot slot) {
45 48 // to prevent creation of map entries on lookup use the map directly
46 49 var deferred = assembliesBySlots.get(slot);
47 50 return deferred != null && deferred.resolved()
48 51 ? Optional.of(deferred.value())
49 52 : Optional.empty();
50 53 }
51 54
52 55 @Override
53 56 public void when(ArtifactSlot slot, Action<? super ArtifactAssembly> action) {
54 57 getDeferred(slot).whenResolved(action::execute);
55 58 }
56 59
57 60 @Override
58 public void all(Action<? super ArtifactAssembly> action) {
61 public void configureEach(Action<? super ArtifactAssembly> action) {
59 62 assemblies.forEach(action::execute);
60 63 }
61 64
62 65 private Deferred<ArtifactAssembly> getDeferred(ArtifactSlot slot) {
63 66 return assembliesBySlots.computeIfAbsent(slot, k -> new Deferred<>());
64 67 }
65 68
66 69 static class Assembly implements ArtifactAssembly {
67 70
68 71 private final Provider<? extends FileSystemLocation> artifact;
69 72 private final TaskProvider<? extends Task> task;
70 73
71 74 Assembly(Provider<? extends FileSystemLocation> artifact, TaskProvider<? extends Task> task) {
72 75 this.artifact = artifact;
73 76 this.task = task;
74 77 }
75 78
76 79 @Override
77 80 public Provider<? extends FileSystemLocation> getArtifact() {
78 81 return artifact;
79 82 }
80 83
81 84 @Override
82 85 public TaskProvider<? extends Task> getAssemblyTask() {
83 86 return task;
84 87 }
85 88
86 89 }
87 90 }
@@ -1,77 +1,79
1 1 package org.implab.gradle.variants.artifacts.internal;
2 2
3 3 import java.util.HashSet;
4 4 import java.util.Set;
5 5 import java.util.function.Consumer;
6 6 import java.util.stream.Stream;
7 7
8 import org.eclipse.jdt.annotation.NonNullByDefault;
8 9 import org.gradle.api.Action;
9 10 import org.gradle.api.model.ObjectFactory;
10 11 import org.implab.gradle.common.core.lang.Strings;
11 12 import org.implab.gradle.variants.artifacts.ArtifactAssemblySpec;
12 13 import org.implab.gradle.variants.core.Layer;
13 14 import org.implab.gradle.variants.core.Role;
14 15 import org.implab.gradle.variants.artifacts.OutputSelectionSpec;
15 16
16 17 /**
17 18 * Реализация DSL модели, строит набор {@link SlotContribution}. При построении набора
18 19 * правила и корректность не проверяются. По окончании использования клиент
19 20 * вызывает метод {@link #process(Consumer)} для обработки результатов.
20 21 *
21 22 */
23 @NonNullByDefault
22 24 final class DefaultArtifactAssemblySpec implements ArtifactAssemblySpec {
23 25 private final Consumer<? super SlotContribution> consumer;
24 26 private final ObjectFactory objectFactory;
25 27
26 28 DefaultArtifactAssemblySpec(ObjectFactory objectFactory, Consumer<? super SlotContribution> consumer) {
27 29 this.consumer = consumer;
28 30 this.objectFactory = objectFactory;
29 31 }
30 32
31 33 @Override
32 34 public void from(Object artifact) {
33 35 consumer.accept(new DirectContribution(artifact));
34 36 }
35 37
36 38 @Override
37 39 public void fromVariant(Action<? super OutputSelectionSpec> action) {
38 40 consumer.accept(new VariantOutputsContribution(outputs(action)));
39 41 }
40 42
41 43 @Override
42 44 public void fromRole(String roleName, Action<? super OutputSelectionSpec> action) {
43 45
44 46 consumer.accept(new RoleOutputsContribution(
45 47 objectFactory.named(Role.class, roleName),
46 48 outputs(action)));
47 49 }
48 50
49 51 @Override
50 52 public void fromLayer(String layerName, Action<? super OutputSelectionSpec> action) {
51 53 consumer.accept(new LayerOutputsContribution(
52 54 objectFactory.named(Layer.class, layerName),
53 55 outputs(action)));
54 56 }
55 57
56 58 private static Set<String> outputs(Action<? super OutputSelectionSpec> action) {
57 59 var spec = new OutputsSetSpec();
58 60 action.execute(spec);
59 61 return spec.outputs();
60 62 }
61 63
62 64 private static class OutputsSetSpec implements OutputSelectionSpec {
63 65 private final Set<String> outputs = new HashSet<>();
64 66
65 67 @Override
66 68 public void output(String name, String... extra) {
67 69 Stream.concat(Stream.of(name), Stream.of(extra))
68 70 .map(Strings::requireNonBlank)
69 71 .forEach(outputs::add);
70 72 }
71 73
72 74 Set<String> outputs() {
73 75 return Set.copyOf(outputs);
74 76 }
75 77
76 78 }
77 79 }
@@ -1,56 +1,121
1 1 package org.implab.gradle.variants.artifacts.internal;
2 2
3 3 import java.util.LinkedHashMap;
4 import java.util.LinkedList;
5 import java.util.List;
6 4 import java.util.Map;
7 5 import java.util.Optional;
6 import java.util.Set;
8 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 14 import org.gradle.api.artifacts.ConfigurationContainer;
11 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 18 import org.implab.gradle.variants.artifacts.OutgoingVariant;
19 import org.implab.gradle.variants.artifacts.Slot;
14 20 import org.implab.gradle.variants.core.Variant;
15 21
22 /**
23 * Реестр исходящих вариантов. Связывает исходящие конфигурации с вариантами
24 * сборки. Связь устанавливается 1:1.
25 */
26 @NonNullByDefault
16 27 public class OutgoingRegistry {
17 private final Map<Variant, DefaultOutgoingConfiguration> outgoingByVariant = new LinkedHashMap<>();
18 private final List<Consumer<? super DefaultOutgoingConfiguration>> hooks = new LinkedList<>();
19
28 private final Map<Variant, OutgoingVariant> outgoingByVariant = new LinkedHashMap<>();
29 private final ReplayableQueue<OutgoingVariant> outgoingVariants = new ReplayableQueue<>();
20 30 private final ConfigurationContainer configurations;
21 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 38 this.configurations = configurations;
26 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 44 return Optional.ofNullable(outgoingByVariant.get(variant));
32 45 }
33 46
34 47 public OutgoingVariant maybeCreate(Variant variant) {
35 var outgoing = outgoingByVariant.computeIfAbsent(variant, this::newOutgoingConfiguration);
36 hooks.forEach(hook -> hook.accept(outgoing));
48 return find(variant).orElseGet(() -> create(variant));
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 64 return outgoing;
38 65 }
39 66
40 public void all(Consumer<? super OutgoingVariant> action) {
41 outgoingByVariant.values().forEach(action);
42 hooks.add(action);
43 }
44
45 private DefaultOutgoingConfiguration newOutgoingConfiguration(Variant variant) {
46 var configuration = configurations.register(outgoingConfigurationName(variant));
47
48 return new DefaultOutgoingConfiguration(variant, configuration, objects, providers);
67 /**
68 * Replayable hook which is applied when an outgoing variant is defined
69 *
70 * @param action
71 */
72 public void configureEach(Consumer<? super OutgoingVariant> action) {
73 outgoingVariants.forEach(action);
49 74 }
50 75
51 76 private String outgoingConfigurationName(Variant variant) {
52 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 1 package org.implab.gradle.variants.artifacts.internal;
2 2
3 3 import org.eclipse.jdt.annotation.NonNullByDefault;
4 4
5 5 @NonNullByDefault
6 public interface SlotContribution {
6 interface SlotContribution {
7 7
8 8 void accept(SlotContributionVisitor visitor);
9 9 }
@@ -1,17 +1,15
1 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 7 void visit(DirectContribution contribution);
7 8
8 9 void visit(VariantOutputsContribution contribution);
9 10
10 11 void visit(RoleOutputsContribution contribution);
11 12
12 13 void visit(LayerOutputsContribution contribution);
13 14
14 default Consumer<SlotContribution> consumer() {
15 return slot -> slot.accept(this);
16 15 }
17 }
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now