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