##// END OF EJS Templates
Variants mutation guard after finalize
cin -
r27:33c11dfc2c2e default
parent child
Show More
@@ -0,0 +1,7
1 package org.implab.gradle.common.sources;
2
3 /**
4 * Directed relation between two layers within a variant.
5 */
6 public record LayerLink(String from, String to, String kind) {
7 }
@@ -3,7 +3,7
3 3 ## NAME
4 4
5 5 `gradle-common/common` — набор плагинов для моделирования вариантов сборки,
6 материализации source sets и интеграции этой модели с toolchain-адаптерами.
6 регистрации source sets и интеграции этой модели с toolchain-адаптерами.
7 7
8 8 ## SYNOPSIS
9 9
@@ -46,7 +46,7 variantSources {
46 46 Модуль состоит из трех логических частей:
47 47
48 48 - `variants` — декларативная доменная модель сборки;
49 - `sources` — модель физически материализуемых source sets;
49 - `sources` — модель физически регистрируемых source sets;
50 50 - `variantSources` — адаптер, который связывает первые две модели.
51 51
52 52 Ниже раскрытие каждой части.
@@ -77,14 +77,14 outputs. Это уже "физический" уровень, к которому удобно привязывать задачи,
77 77
78 78 ### variantSources
79 79
80 `variantSources` материализует source sets на основе `variants`, применяет
80 `variantSources` регистрирует source sets на основе `variants`, применяет
81 81 конфигурацию layer-bindings и отдает события (`whenRegistered`, `whenBound`) для
82 82 адаптеров других плагинов.
83 83
84 84 Практический смысл:
85 85
86 86 - переводить логическую модель `variants` в executable-модель `sources`;
87 - навешивать политики toolchain на materialized source sets;
87 - навешивать политики toolchain на зарегистрированные source sets;
88 88 - синхронизировать плагины через replayable callback-контракт.
89 89
90 90 ## DOMAIN MODEL
@@ -92,10 +92,10 outputs. Это уже "физический" уровень, к которому удобно привязывать задачи,
92 92 - `BuildLayer` — глобальный идентификатор слоя.
93 93 - `BuildVariant` — агрегат ролей, связей, атрибутов, артефактных слотов.
94 94 - `BuildRole` — роль внутри варианта, содержит ссылки на layer names.
95 - `BuildLink` — ориентированная связь `from -> to` в графе определенного `kind`.
96 - `GenericSourceSet`materialized набор исходников и outputs.
97 - `BuildLayerBinding` — правила materialization source set для конкретного layer.
98 - `SourceSetContext` — контекст callback-событий materialization.
95 - `LayerLink` — ориентированная связь `from -> to` в графе определенного `kind`.
96 - `GenericSourceSet`зарегистрированный набор исходников и outputs.
97 - `BuildLayerBinding` — правила registration source set для конкретного layer.
98 - `SourceSetContext` — контекст callback-событий registration.
99 99
100 100 ## EVENT CONTRACT
101 101
@@ -117,7 +117,7 Closure callbacks работают в delegate-first режиме (`@DelegatesTo`). Для
117 117 - `BuildVariantsExtension` — корневой API модели вариантов.
118 118 - `BuildVariant` — API ролей, links, attributes и artifact slots варианта.
119 119 - `VariantsSourcesPlugin` — применяет `variants` + `sources` и запускает адаптер.
120 - `VariantSourcesExtension` — API bind/events materialization.
120 - `VariantSourcesExtension` — API bind/events registration.
121 121 - `BuildLayerBinding` — слой-конкретный DSL для имени и конфигурации source set.
122 122 - `SourceSetContext` — payload событий и sugar `configureSourceSet(...)`.
123 123
@@ -5,6 +5,7 import java.util.Objects;
5 5
6 6 import javax.inject.Inject;
7 7
8 import org.gradle.api.InvalidUserDataException;
8 9 import org.gradle.api.Named;
9 10 import org.gradle.api.provider.ListProperty;
10 11
@@ -13,6 +14,7 import org.gradle.api.provider.ListPrope
13 14 */
14 15 public abstract class BuildRole implements Named {
15 16 private final String name;
17 private boolean finalized;
16 18
17 19 @Inject
18 20 public BuildRole(String name) {
@@ -30,6 +32,8 public abstract class BuildRole implemen
30 32 * Binds this role to one or more declared layers.
31 33 */
32 34 public void layers(String layer, String... extra) {
35 ensureMutable("add role layers");
36
33 37 var values = new ArrayList<String>(1 + extra.length);
34 38
35 39 values.add(Objects.requireNonNull(layer, "Layer name is required"));
@@ -38,4 +42,18 public abstract class BuildRole implemen
38 42
39 43 getLayers().addAll(values);
40 44 }
45
46 void finalizeModel() {
47 if (finalized)
48 return;
49
50 getLayers().finalizeValue();
51 getLayers().disallowChanges();
52 finalized = true;
41 53 }
54
55 private void ensureMutable(String operation) {
56 if (finalized)
57 throw new InvalidUserDataException("Role '" + name + "' is finalized and cannot " + operation);
58 }
59 }
@@ -1,16 +1,18
1 1 package org.implab.gradle.common.sources;
2 2
3 import java.util.ArrayList;
3 4 import java.util.Collection;
4 5 import java.util.Collections;
5 6 import java.util.LinkedHashMap;
6 7 import java.util.LinkedHashSet;
8 import java.util.List;
7 9 import java.util.Set;
8 import java.util.regex.Pattern;
9 10
10 11 import javax.inject.Inject;
11 12
12 13 import org.implab.gradle.common.core.lang.Closures;
13 14 import org.gradle.api.Action;
15 import org.gradle.api.InvalidUserDataException;
14 16 import org.gradle.api.Named;
15 17 import org.gradle.api.model.ObjectFactory;
16 18 import org.gradle.api.provider.Provider;
@@ -20,17 +22,16 import org.gradle.api.attributes.Attribu
20 22 import groovy.lang.Closure;
21 23
22 24 public abstract class BuildVariant implements Named {
23 private static final Pattern INVALID_NAME_CHAR = Pattern.compile("[^A-Za-z0-9_.-]");
24
25 25 private final String name;
26 26 private final ObjectFactory objects;
27 private boolean finalized;
27 28
28 29 /**
29 30 * Variant aggregate parts.
30 31 */
31 32 private final VariantAttributes attributes;
32 33 private final LinkedHashMap<String, BuildRole> roles = new LinkedHashMap<>();
33 private final LinkedHashMap<String, BuildLink> links = new LinkedHashMap<>();
34 private final List<LayerLink> links = new ArrayList<>();
34 35 private final LinkedHashMap<String, BuildArtifactSlot> artifactSlots = new LinkedHashMap<>();
35 36
36 37 @Inject
@@ -53,6 +54,7 public abstract class BuildVariant imple
53 54 }
54 55
55 56 public void attributes(Action<? super AttributesSpec> action) {
57 ensureMutable("configure attributes");
56 58 action.execute(new AttributesSpec(attributes));
57 59 }
58 60
@@ -61,10 +63,12 public abstract class BuildVariant imple
61 63 }
62 64
63 65 public <T> void attribute(Attribute<T> key, T value) {
66 ensureMutable("set attributes");
64 67 attributes.attribute(key, value);
65 68 }
66 69
67 70 public <T> void attributeProvider(Attribute<T> key, Provider<? extends T> value) {
71 ensureMutable("set attributes");
68 72 attributes.attributeProvider(key, value);
69 73 }
70 74
@@ -73,6 +77,7 public abstract class BuildVariant imple
73 77 }
74 78
75 79 public void roles(Action<? super RolesSpec> action) {
80 ensureMutable("configure roles");
76 81 action.execute(new RolesSpec());
77 82 }
78 83
@@ -81,6 +86,7 public abstract class BuildVariant imple
81 86 }
82 87
83 88 public BuildRole role(String name, Action<? super BuildRole> configure) {
89 ensureMutable("configure roles");
84 90 var role = roles.computeIfAbsent(name, this::newRole);
85 91 configure.execute(role);
86 92 return role;
@@ -99,11 +105,12 public abstract class BuildVariant imple
99 105 return roles.get(name);
100 106 }
101 107
102 public Collection<BuildLink> getLinks() {
103 return Collections.unmodifiableCollection(links.values());
108 public Collection<LayerLink> getLinks() {
109 return Collections.unmodifiableList(links);
104 110 }
105 111
106 112 public void links(Action<? super LinksSpec> action) {
113 ensureMutable("configure links");
107 114 action.execute(new LinksSpec());
108 115 }
109 116
@@ -111,43 +118,19 public abstract class BuildVariant imple
111 118 links(Closures.action(configure));
112 119 }
113 120
114 public BuildLink link(String from, String to, String kind, Action<? super BuildLink> configure) {
115 return link(defaultLinkName(from, to, kind), link -> {
116 link.from(from);
117 link.to(to);
118 link.kind(kind);
119 configure.execute(link);
120 });
121 }
122
123 public BuildLink link(String from, String to, String kind, Closure<?> configure) {
124 return link(from, to, kind, Closures.action(configure));
125 }
126
127 public BuildLink link(String from, String to, String kind) {
128 return link(from, to, kind, it -> {
129 });
130 }
131
132 public BuildLink link(String name, Action<? super BuildLink> configure) {
133 var link = links.computeIfAbsent(name, this::newLink);
134 configure.execute(link);
121 public LayerLink link(String from, String to, String kind) {
122 ensureMutable("add links");
123 var link = new LayerLink(from, to, kind);
124 links.add(link);
135 125 return link;
136 126 }
137 127
138 public BuildLink link(String name, Closure<?> configure) {
139 return link(name, Closures.action(configure));
140 }
141
142 public BuildLink getLinkByName(String name) {
143 return links.get(name);
144 }
145
146 128 public Collection<BuildArtifactSlot> getArtifactSlots() {
147 129 return Collections.unmodifiableCollection(artifactSlots.values());
148 130 }
149 131
150 132 public void artifactSlots(Action<? super ArtifactSlotsSpec> action) {
133 ensureMutable("configure artifact slots");
151 134 action.execute(new ArtifactSlotsSpec());
152 135 }
153 136
@@ -161,6 +144,7 public abstract class BuildVariant imple
161 144 }
162 145
163 146 public BuildArtifactSlot artifactSlot(String name, Action<? super BuildArtifactSlot> configure) {
147 ensureMutable("configure artifact slots");
164 148 var slot = artifactSlots.computeIfAbsent(name, this::newArtifactSlot);
165 149 configure.execute(slot);
166 150 return slot;
@@ -183,24 +167,28 public abstract class BuildVariant imple
183 167 return result;
184 168 }
185 169
170 void finalizeModel() {
171 if (finalized)
172 return;
173
174 for (var role : roles.values())
175 role.finalizeModel();
176
177 attributes.finalizeModel();
178 finalized = true;
179 }
180
186 181 private BuildRole newRole(String roleName) {
187 182 return objects.newInstance(BuildRole.class, roleName);
188 183 }
189 184
190 private BuildLink newLink(String linkName) {
191 return objects.newInstance(BuildLink.class, linkName);
192 }
193
194 185 private BuildArtifactSlot newArtifactSlot(String slotName) {
195 186 return objects.newInstance(BuildArtifactSlot.class, slotName);
196 187 }
197 188
198 private static String defaultLinkName(String from, String to, String kind) {
199 return "link_" + sanitize(from) + "__" + sanitize(to) + "__" + sanitize(kind);
200 }
201
202 private static String sanitize(String value) {
203 return INVALID_NAME_CHAR.matcher(String.valueOf(value)).replaceAll("_");
189 private void ensureMutable(String operation) {
190 if (finalized)
191 throw new InvalidUserDataException("Variant '" + name + "' is finalized and cannot " + operation);
204 192 }
205 193
206 194 public final class RolesSpec {
@@ -226,25 +214,13 public abstract class BuildVariant imple
226 214 }
227 215
228 216 public final class LinksSpec {
229 public BuildLink link(String from, String to, String kind, Action<? super BuildLink> configure) {
230 return BuildVariant.this.link(from, to, kind, configure);
231 }
232
233 public BuildLink link(String from, String to, String kind, Closure<?> configure) {
234 return BuildVariant.this.link(from, to, kind, configure);
235 }
236
237 public BuildLink link(String from, String to, String kind) {
217 public LayerLink link(String from, String to, String kind) {
238 218 return BuildVariant.this.link(from, to, kind);
239 219 }
240 220
241 public Collection<BuildLink> getAll() {
221 public Collection<LayerLink> getAll() {
242 222 return BuildVariant.this.getLinks();
243 223 }
244
245 public BuildLink getByName(String name) {
246 return BuildVariant.this.getLinkByName(name);
247 }
248 224 }
249 225
250 226 public final class ArtifactSlotsSpec {
@@ -31,6 +31,18 public abstract class BuildVariantsExten
31 31 public BuildVariantsExtension(ObjectFactory objects) {
32 32 layers = objects.domainObjectContainer(BuildLayer.class);
33 33 variants = objects.domainObjectContainer(BuildVariant.class);
34
35 layers.all(layer -> {
36 if (finalized)
37 throw new InvalidUserDataException(
38 "Variants model is finalized and cannot add layer '" + layer.getName() + "'");
39 });
40
41 variants.all(variant -> {
42 if (finalized)
43 throw new InvalidUserDataException(
44 "Variants model is finalized and cannot add variant '" + variant.getName() + "'");
45 });
34 46 }
35 47
36 48 public NamedDomainObjectContainer<BuildLayer> getLayers() {
@@ -42,6 +54,7 public abstract class BuildVariantsExten
42 54 }
43 55
44 56 public void layers(Action<? super NamedDomainObjectContainer<BuildLayer>> action) {
57 ensureMutable("configure layers");
45 58 action.execute(layers);
46 59 }
47 60
@@ -50,6 +63,7 public abstract class BuildVariantsExten
50 63 }
51 64
52 65 public void variants(Action<? super NamedDomainObjectContainer<BuildVariant>> action) {
66 ensureMutable("configure variants");
53 67 action.execute(variants);
54 68 }
55 69
@@ -58,6 +72,7 public abstract class BuildVariantsExten
58 72 }
59 73
60 74 public BuildLayer layer(String name, Action<? super BuildLayer> configure) {
75 ensureMutable("configure layers");
61 76 var layer = layers.maybeCreate(name);
62 77 configure.execute(layer);
63 78 return layer;
@@ -73,6 +88,7 public abstract class BuildVariantsExten
73 88 }
74 89
75 90 public BuildVariant variant(String name, Action<? super BuildVariant> configure) {
91 ensureMutable("configure variants");
76 92 var variant = variants.maybeCreate(name);
77 93 configure.execute(variant);
78 94 return variant;
@@ -126,6 +142,10 public abstract class BuildVariantsExten
126 142 return;
127 143
128 144 validate();
145
146 for (var variant : variants)
147 variant.finalizeModel();
148
129 149 finalized = true;
130 150
131 151 var actions = new ArrayList<>(finalizedActions);
@@ -187,24 +207,23 public abstract class BuildVariantsExten
187 207 var edgesByKind = new HashMap<String, Map<String, Set<String>>>();
188 208
189 209 for (var link : variant.getLinks()) {
190 var from = normalize(link.getFrom().getOrNull());
191 var to = normalize(link.getTo().getOrNull());
192 var kind = normalize(link.getKind().getOrNull());
210 var from = normalize(link.from());
211 var to = normalize(link.to());
212 var kind = normalize(link.kind());
193 213
194 214 if (from == null || to == null || kind == null) {
195 errors.add("Variant '" + variant.getName() + "' has incomplete link '" + link.getName()
196 + "' (from/to/kind are required)");
215 errors.add("Variant '" + variant.getName() + "' has incomplete link (from/to/kind are required)");
197 216 continue;
198 217 }
199 218
200 219 if (!variantLayers.contains(from)) {
201 errors.add("Variant '" + variant.getName() + "' link '" + link.getName() + "' references unknown source layer '"
220 errors.add("Variant '" + variant.getName() + "' link references unknown source layer '"
202 221 + from + "'");
203 222 continue;
204 223 }
205 224
206 225 if (!variantLayers.contains(to)) {
207 errors.add("Variant '" + variant.getName() + "' link '" + link.getName() + "' references unknown target layer '"
226 errors.add("Variant '" + variant.getName() + "' link references unknown target layer '"
208 227 + to + "'");
209 228 continue;
210 229 }
@@ -268,4 +287,9 public abstract class BuildVariantsExten
268 287 private static boolean isBlank(String value) {
269 288 return normalize(value) == null;
270 289 }
290
291 private void ensureMutable(String operation) {
292 if (finalized)
293 throw new InvalidUserDataException("Variants model is finalized and cannot " + operation);
271 294 }
295 }
@@ -19,6 +19,7 public abstract class SourcesPlugin impl
19 19
20 20 @Override
21 21 public void apply(Project target) {
22 logger.debug("Registering '{}' extension on project '{}'", SOURCES_EXTENSION_NAME, target.getPath());
22 23 var sources = target.getObjects().domainObjectContainer(GenericSourceSet.class);
23 24 target.getExtensions().add(SOURCES_EXTENSION_NAME, sources);
24 25 }
@@ -27,10 +28,13 public abstract class SourcesPlugin impl
27 28 var extensions = target.getExtensions();
28 29
29 30 @SuppressWarnings("unchecked")
30 var extension = (NamedDomainObjectContainer<GenericSourceSet>)extensions.getByName(SOURCES_EXTENSION_NAME);
31 var extension = (NamedDomainObjectContainer<GenericSourceSet>) extensions.findByName(SOURCES_EXTENSION_NAME);
31 32
32 if (extension == null)
33 if (extension == null) {
34 logger.error("Sources extension '{}' isn't found on project '{}'", SOURCES_EXTENSION_NAME, target.getPath());
33 35 throw new GradleException("Sources extension isn't found");
36 }
37 logger.debug("Resolved '{}' extension on project '{}'", SOURCES_EXTENSION_NAME, target.getPath());
34 38 return extension;
35 39 }
36 40 }
@@ -4,6 +4,7 import java.util.Collections;
4 4 import java.util.LinkedHashMap;
5 5 import java.util.Map;
6 6
7 import org.gradle.api.InvalidUserDataException;
7 8 import org.gradle.api.attributes.Attribute;
8 9 import org.gradle.api.provider.ProviderFactory;
9 10 import org.gradle.api.provider.Provider;
@@ -14,16 +15,19 import org.gradle.api.provider.Provider;
14 15 public final class VariantAttributes {
15 16 private final ProviderFactory providers;
16 17 private final LinkedHashMap<Attribute<?>, Provider<?>> values = new LinkedHashMap<>();
18 private boolean finalized;
17 19
18 20 VariantAttributes(ProviderFactory providers) {
19 21 this.providers = providers;
20 22 }
21 23
22 24 public <T> void attribute(Attribute<T> key, T value) {
25 ensureMutable("set attribute '" + key.getName() + "'");
23 26 attributeProvider(key, providers.provider(() -> value));
24 27 }
25 28
26 29 public <T> void attributeProvider(Attribute<T> key, Provider<? extends T> value) {
30 ensureMutable("set attribute provider '" + key.getName() + "'");
27 31 values.put(key, value);
28 32 }
29 33
@@ -43,4 +47,13 public final class VariantAttributes {
43 47 public Map<Attribute<?>, Provider<?>> asMap() {
44 48 return Collections.unmodifiableMap(values);
45 49 }
50
51 void finalizeModel() {
52 finalized = true;
46 53 }
54
55 private void ensureMutable(String operation) {
56 if (finalized)
57 throw new InvalidUserDataException("Variant attributes are finalized and cannot " + operation);
58 }
59 }
@@ -17,15 +17,18 import org.gradle.api.InvalidUserDataExc
17 17 import org.gradle.api.NamedDomainObjectContainer;
18 18 import org.gradle.api.NamedDomainObjectProvider;
19 19 import org.gradle.api.model.ObjectFactory;
20 import org.gradle.api.logging.Logger;
21 import org.gradle.api.logging.Logging;
20 22
21 23 import groovy.lang.Closure;
22 24 import groovy.lang.DelegatesTo;
23 25
24 26 /**
25 * Adapter extension that materializes source sets for variant/layer pairs.
27 * Adapter extension that registers source sets for variant/layer pairs.
26 28 */
27 29 @NonNullByDefault
28 30 public abstract class VariantSourcesExtension {
31 private static final Logger logger = Logging.getLogger(VariantSourcesExtension.class);
29 32 private static final Pattern INVALID_NAME_CHAR = Pattern.compile("[^A-Za-z0-9_.-]");
30 33 private static final Pattern SOURCE_SET_NAME_TOKEN = Pattern.compile("\\{([A-Za-z][A-Za-z0-9]*)\\}");
31 34
@@ -135,7 +138,25 public abstract class VariantSourcesExte
135 138
136 139 void registerSourceSets(BuildVariantsExtension variants, NamedDomainObjectContainer<GenericSourceSet> sources) {
137 140 validateBindings(variants);
138 layerUsages(variants).forEach(usage -> materializeLayerUsage(usage, sources));
141
142 var usages = layerUsages(variants).toList();
143 var registeredBefore = registeredContexts.size();
144 var boundBefore = boundContexts.size();
145
146 logger.debug(
147 "Starting variant source-set registration (variants={}, layers={}, bindings={}, usages={})",
148 variants.getVariants().size(),
149 variants.getLayers().size(),
150 bindings.size(),
151 usages.size());
152
153 usages.forEach(usage -> registerLayerUsage(usage, sources));
154
155 logger.debug(
156 "Completed variant source-set registration (newSourceSets={}, newBounds={}, totalSourceSets={})",
157 registeredContexts.size() - registeredBefore,
158 boundContexts.size() - boundBefore,
159 sourceSetsByName.size());
139 160 }
140 161
141 162 private Stream<LayerUsage> layerUsages(BuildVariantsExtension variants) {
@@ -148,7 +169,7 public abstract class VariantSourcesExte
148 169 normalize(layerName)))));
149 170 }
150 171
151 private void materializeLayerUsage(LayerUsage usage, NamedDomainObjectContainer<GenericSourceSet> sources) {
172 private void registerLayerUsage(LayerUsage usage, NamedDomainObjectContainer<GenericSourceSet> sources) {
152 173 var resolvedBinding = bind(usage.layerName());
153 174 var sourceSetNamePattern = resolvedBinding.getSourceSetNamePattern();
154 175 sourceSetNamePattern.finalizeValueOnRead();
@@ -3,24 +3,36 package org.implab.gradle.common.sources
3 3 import org.gradle.api.GradleException;
4 4 import org.gradle.api.Plugin;
5 5 import org.gradle.api.Project;
6 import org.gradle.api.logging.Logger;
7 import org.gradle.api.logging.Logging;
6 8
7 9 /**
8 10 * Registers {@code variants} extension for build-variant modeling.
9 11 */
10 12 public abstract class VariantsPlugin implements Plugin<Project> {
13 private static final Logger logger = Logging.getLogger(VariantsPlugin.class);
11 14 public static final String VARIANTS_EXTENSION_NAME = "variants";
12 15
13 16 @Override
14 17 public void apply(Project target) {
18 logger.debug("Registering '{}' extension on project '{}'", VARIANTS_EXTENSION_NAME, target.getPath());
15 19 var variants = target.getExtensions().create(VARIANTS_EXTENSION_NAME, BuildVariantsExtension.class);
16 target.afterEvaluate(project -> variants.finalizeModel());
20 target.afterEvaluate(project -> {
21 logger.debug("Finalizing variants model on project '{}'", project.getPath());
22 variants.finalizeModel();
23 logger.debug("Variants model finalized on project '{}'", project.getPath());
24 });
17 25 }
18 26
19 27 public static BuildVariantsExtension getVariantsExtension(Project target) {
20 28 var extension = target.getExtensions().findByType(BuildVariantsExtension.class);
21 29
22 if (extension == null)
30 if (extension == null) {
31 logger.error("Variants extension '{}' isn't found on project '{}'", VARIANTS_EXTENSION_NAME, target.getPath());
23 32 throw new GradleException("Variants extension isn't found");
33 }
34
35 logger.debug("Resolved '{}' extension on project '{}'", VARIANTS_EXTENSION_NAME, target.getPath());
24 36
25 37 return extension;
26 38 }
@@ -2,15 +2,19 package org.implab.gradle.common.sources
2 2
3 3 import org.gradle.api.Plugin;
4 4 import org.gradle.api.Project;
5 import org.gradle.api.logging.Logger;
6 import org.gradle.api.logging.Logging;
5 7
6 8 /**
7 * Binds variant layers to materialized source sets.
9 * Binds variant layers to registered source sets.
8 10 */
9 11 public abstract class VariantsSourcesPlugin implements Plugin<Project> {
12 private static final Logger logger = Logging.getLogger(VariantsSourcesPlugin.class);
10 13 public static final String VARIANT_SOURCES_EXTENSION_NAME = "variantSources";
11 14
12 15 @Override
13 16 public void apply(Project target) {
17 logger.debug("Applying variant-sources plugin on project '{}'", target.getPath());
14 18 target.getPluginManager().apply(VariantsPlugin.class);
15 19 target.getPluginManager().apply(SourcesPlugin.class);
16 20
@@ -20,6 +24,10 public abstract class VariantsSourcesPlu
20 24 var variantSources = target.getExtensions()
21 25 .create(VARIANT_SOURCES_EXTENSION_NAME, VariantSourcesExtension.class);
22 26
23 variants.whenFinalized(model -> variantSources.registerSourceSets(model, sources));
27 variants.whenFinalized(model -> {
28 logger.debug("Registering source sets for variants on project '{}'", target.getPath());
29 variantSources.registerSourceSets(model, sources);
30 logger.debug("Registered source sets on project '{}'", target.getPath());
31 });
24 32 }
25 33 }
@@ -153,13 +153,10 class VariantsPluginFunctionalTest {
153 153 role('main') {
154 154 layers('a', 'b')
155 155 }
156 link('l1') {
157 from('a')
158 to('b')
156 link('a', 'b', null)
159 157 }
160 158 }
161 }
162 """, "has incomplete link 'l1'");
159 """, "has incomplete link (from/to/kind are required)");
163 160 }
164 161
165 162 @Test
@@ -177,16 +174,8 class VariantsPluginFunctionalTest {
177 174 role('main') {
178 175 layers('a', 'b')
179 176 }
180 link('first') {
181 from('a')
182 to('b')
183 kind('ts:api')
184 }
185 link('second') {
186 from('a')
187 to('b')
188 kind('ts:api')
189 }
177 link('a', 'b', 'ts:api')
178 link('a', 'b', 'ts:api')
190 179 }
191 180 }
192 181 """, "has duplicated link tuple (from='a', to='b', kind='ts:api')");
@@ -207,11 +196,7 class VariantsPluginFunctionalTest {
207 196 role('main') {
208 197 layers('a')
209 198 }
210 link('l1') {
211 from('missing')
212 to('a')
213 kind('ts:api')
214 }
199 link('missing', 'a', 'ts:api')
215 200 }
216 201 }
217 202 """, "references unknown source layer 'missing'");
@@ -232,16 +217,52 class VariantsPluginFunctionalTest {
232 217 role('main') {
233 218 layers('a')
234 219 }
235 link('l1') {
236 from('a')
237 to('missing')
238 kind('ts:api')
239 }
220 link('a', 'missing', 'ts:api')
240 221 }
241 222 }
242 223 """, "references unknown target layer 'missing'");
243 224 }
244 225
226 @Test
227 void failsOnLateLayerMutationAfterFinalize() throws Exception {
228 assertBuildFails("""
229 plugins {
230 id 'org.implab.gradle-variants'
231 }
232
233 variants {
234 layer('a')
235 variant('browser') {
236 role('main') { layers('a') }
237 }
238 }
239
240 afterEvaluate {
241 variants.layer('late')
242 }
243 """, "Variants model is finalized and cannot configure layers");
244 }
245
246 @Test
247 void failsOnLateVariantMutationAfterFinalize() throws Exception {
248 assertBuildFails("""
249 plugins {
250 id 'org.implab.gradle-variants'
251 }
252
253 variants {
254 layer('a')
255 variant('browser') {
256 role('main') { layers('a') }
257 }
258 }
259
260 afterEvaluate {
261 variants.getByName('browser').role('late') { layers('a') }
262 }
263 """, "Variant 'browser' is finalized and cannot configure roles");
264 }
265
245 266 private GradleRunner runner(String... arguments) {
246 267 return GradleRunner.create()
247 268 .withProjectDir(testProjectDir.toFile())
@@ -26,7 +26,7 class VariantsSourcesPluginFunctionalTes
26 26 Path testProjectDir;
27 27
28 28 @Test
29 void materializesVariantSourceSetsAndFiresCallbacks() throws Exception {
29 void registersVariantSourceSetsAndFiresCallbacks() throws Exception {
30 30 writeFile(SETTINGS_FILE, ROOT_NAME);
31 31 writeFile(BUILD_FILE, """
32 32 plugins {
@@ -194,7 +194,7 class VariantsSourcesPluginFunctionalTes
194 194 }
195 195
196 196 @Test
197 void replaysLateBindingsAndCallbacksAfterMaterialization() throws Exception {
197 void replaysLateBindingsAndCallbacksAfterRegistration() throws Exception {
198 198 writeFile(SETTINGS_FILE, ROOT_NAME);
199 199 writeFile(BUILD_FILE, """
200 200 plugins {
@@ -40,13 +40,13 variantSources {
40 40 ## DESCRIPTION
41 41
42 42 `VariantsSourcesPlugin` применяет `VariantsPlugin` и `SourcesPlugin`, затем
43 материализует source sets из модели `variants`.
43 регистрирует source sets из модели `variants`.
44 44
45 Точка запуска materialization:
45 Точка запуска registration:
46 46
47 47 - `variants.whenFinalized(model -> registerSourceSets(...))`
48 48
49 ### materialization
49 ### registration
50 50
51 51 Для каждой usage-связки `variant/role/layer` вычисляется имя source set,
52 52 регистрируется `GenericSourceSet` (если он еще не существует), затем
@@ -63,7 +63,7 variantSources {
63 63
64 64 ### sourceSetNamePattern
65 65
66 `sourceSetNamePattern` определяет naming policy materialized source set.
66 `sourceSetNamePattern` определяет naming policy зарегистрированного source set.
67 67
68 68 Default:
69 69
@@ -139,7 +139,7 Sugar:
139 139
140 140 ## NOTES
141 141
142 - `sourceSetNamePattern` фиксируется при первом чтении в materialization
142 - `sourceSetNamePattern` фиксируется при первом чтении в registration
143 143 (`finalizeValueOnRead`).
144 144 - Closure callbacks используют delegate-first.
145 145 - Для вложенных closure лучше явный параметр (`ctx -> ...`).
@@ -88,6 +88,7 Typed-атрибуты (`Attribute<T> -> Provider<T>`) для передачи параметров в
88 88 ## LIFECYCLE
89 89
90 90 - `VariantsPlugin` вызывает `variants.finalizeModel()` на `afterEvaluate`.
91 - после `finalizeModel()` мутации модели запрещены.
91 92 - `whenFinalized(...)` replayable.
92 93
93 94 ## API
@@ -117,7 +118,7 Typed-атрибуты (`Attribute<T> -> Provider<T>`) для передачи параметров в
117 118 - `BuildVariant` — агрегатная модель варианта.
118 119 - `BuildLayer` — модель слоя.
119 120 - `BuildRole` — модель роли.
120 - `BuildLink` — модель направленной связи.
121 - `LayerLink` — модель направленной связи.
121 122 - `BuildArtifactSlot` — модель артефактного слота.
122 123 - `VariantAttributes` — typed wrapper для variant attributes.
123 124
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now