##// END OF EJS Templates
Variants validation + fail-fast
cin -
r28:1a0b4caf9976 default
parent child
Show More
@@ -1,291 +1,301
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.ArrayList;
4 import java.util.Collection;
4 import java.util.Collection;
5 import java.util.Collections;
5 import java.util.Collections;
6 import java.util.LinkedHashMap;
6 import java.util.LinkedHashMap;
7 import java.util.LinkedHashSet;
7 import java.util.LinkedHashSet;
8 import java.util.List;
8 import java.util.List;
9 import java.util.Set;
9 import java.util.Set;
10
10
11 import javax.inject.Inject;
11 import javax.inject.Inject;
12
12
13 import org.implab.gradle.common.core.lang.Closures;
13 import org.implab.gradle.common.core.lang.Closures;
14 import org.gradle.api.Action;
14 import org.gradle.api.Action;
15 import org.gradle.api.InvalidUserDataException;
15 import org.gradle.api.InvalidUserDataException;
16 import org.gradle.api.Named;
16 import org.gradle.api.Named;
17 import org.gradle.api.model.ObjectFactory;
17 import org.gradle.api.model.ObjectFactory;
18 import org.gradle.api.provider.Provider;
18 import org.gradle.api.provider.Provider;
19 import org.gradle.api.provider.ProviderFactory;
19 import org.gradle.api.provider.ProviderFactory;
20 import org.gradle.api.attributes.Attribute;
20 import org.gradle.api.attributes.Attribute;
21
21
22 import groovy.lang.Closure;
22 import groovy.lang.Closure;
23
23
24 public abstract class BuildVariant implements Named {
24 public abstract class BuildVariant implements Named {
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 private boolean finalized;
28
28
29 /**
29 /**
30 * Variant aggregate parts.
30 * Variant aggregate parts.
31 */
31 */
32 private final VariantAttributes attributes;
32 private final VariantAttributes attributes;
33 private final LinkedHashMap<String, BuildRole> roles = new LinkedHashMap<>();
33 private final LinkedHashMap<String, BuildRole> roles = new LinkedHashMap<>();
34 private final List<LayerLink> links = new ArrayList<>();
34 private final List<LayerLink> links = new ArrayList<>();
35 private final LinkedHashMap<String, BuildArtifactSlot> artifactSlots = new LinkedHashMap<>();
35 private final LinkedHashMap<String, BuildArtifactSlot> artifactSlots = new LinkedHashMap<>();
36
36
37 @Inject
37 @Inject
38 public BuildVariant(String name, ObjectFactory objects, ProviderFactory providers) {
38 public BuildVariant(String name, ObjectFactory objects, ProviderFactory providers) {
39 this.name = name;
39 this.name = name;
40 this.objects = objects;
40 this.objects = objects;
41 attributes = new VariantAttributes(providers);
41 attributes = new VariantAttributes(providers);
42 }
42 }
43
43
44 @Override
44 @Override
45 public String getName() {
45 public String getName() {
46 return name;
46 return name;
47 }
47 }
48
48
49 /**
49 /**
50 * Generic variant attributes interpreted by adapters.
50 * Generic variant attributes interpreted by adapters.
51 */
51 */
52 public VariantAttributes getAttributes() {
52 public VariantAttributes getAttributes() {
53 return attributes;
53 return attributes;
54 }
54 }
55
55
56 public void attributes(Action<? super AttributesSpec> action) {
56 public void attributes(Action<? super AttributesSpec> action) {
57 ensureMutable("configure attributes");
57 ensureMutable("configure attributes");
58 action.execute(new AttributesSpec(attributes));
58 action.execute(new AttributesSpec(attributes));
59 }
59 }
60
60
61 public void attributes(Closure<?> configure) {
61 public void attributes(Closure<?> configure) {
62 attributes(Closures.action(configure));
62 attributes(Closures.action(configure));
63 }
63 }
64
64
65 public <T> void attribute(Attribute<T> key, T value) {
65 public <T> void attribute(Attribute<T> key, T value) {
66 ensureMutable("set attributes");
66 ensureMutable("set attributes");
67 attributes.attribute(key, value);
67 attributes.attribute(key, value);
68 }
68 }
69
69
70 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");
71 ensureMutable("set attributes");
72 attributes.attributeProvider(key, value);
72 attributes.attributeProvider(key, value);
73 }
73 }
74
74
75 public Collection<BuildRole> getRoles() {
75 public Collection<BuildRole> getRoles() {
76 return Collections.unmodifiableCollection(roles.values());
76 return Collections.unmodifiableCollection(roles.values());
77 }
77 }
78
78
79 public void roles(Action<? super RolesSpec> action) {
79 public void roles(Action<? super RolesSpec> action) {
80 ensureMutable("configure roles");
80 ensureMutable("configure roles");
81 action.execute(new RolesSpec());
81 action.execute(new RolesSpec());
82 }
82 }
83
83
84 public void roles(Closure<?> configure) {
84 public void roles(Closure<?> configure) {
85 roles(Closures.action(configure));
85 roles(Closures.action(configure));
86 }
86 }
87
87
88 public BuildRole role(String name, Action<? super BuildRole> configure) {
88 public BuildRole role(String name, Action<? super BuildRole> configure) {
89 ensureMutable("configure roles");
89 ensureMutable("configure roles");
90 var role = roles.computeIfAbsent(name, this::newRole);
90 var role = roles.computeIfAbsent(name, this::newRole);
91 configure.execute(role);
91 configure.execute(role);
92 return role;
92 return role;
93 }
93 }
94
94
95 public BuildRole role(String name, Closure<?> configure) {
95 public BuildRole role(String name, Closure<?> configure) {
96 return role(name, Closures.action(configure));
96 return role(name, Closures.action(configure));
97 }
97 }
98
98
99 public BuildRole role(String name) {
99 public BuildRole role(String name) {
100 return role(name, r -> {
100 return role(name, r -> {
101 });
101 });
102 }
102 }
103
103
104 public BuildRole getRoleByName(String name) {
104 public BuildRole getRoleByName(String name) {
105 return roles.get(name);
105 return roles.get(name);
106 }
106 }
107
107
108 public Collection<LayerLink> getLinks() {
108 public Collection<LayerLink> getLinks() {
109 return Collections.unmodifiableList(links);
109 return Collections.unmodifiableList(links);
110 }
110 }
111
111
112 public void links(Action<? super LinksSpec> action) {
112 public void links(Action<? super LinksSpec> action) {
113 ensureMutable("configure links");
113 ensureMutable("configure links");
114 action.execute(new LinksSpec());
114 action.execute(new LinksSpec());
115 }
115 }
116
116
117 public void links(Closure<?> configure) {
117 public void links(Closure<?> configure) {
118 links(Closures.action(configure));
118 links(Closures.action(configure));
119 }
119 }
120
120
121 public LayerLink link(String from, String to, String kind) {
121 public LayerLink link(String from, String to, String kind) {
122 ensureMutable("add links");
122 ensureMutable("add links");
123 var link = new LayerLink(from, to, kind);
123 var link = new LayerLink(
124 requireLinkValue("from", from),
125 requireLinkValue("to", to),
126 requireLinkValue("kind", kind));
124 links.add(link);
127 links.add(link);
125 return link;
128 return link;
126 }
129 }
127
130
128 public Collection<BuildArtifactSlot> getArtifactSlots() {
131 public Collection<BuildArtifactSlot> getArtifactSlots() {
129 return Collections.unmodifiableCollection(artifactSlots.values());
132 return Collections.unmodifiableCollection(artifactSlots.values());
130 }
133 }
131
134
132 public void artifactSlots(Action<? super ArtifactSlotsSpec> action) {
135 public void artifactSlots(Action<? super ArtifactSlotsSpec> action) {
133 ensureMutable("configure artifact slots");
136 ensureMutable("configure artifact slots");
134 action.execute(new ArtifactSlotsSpec());
137 action.execute(new ArtifactSlotsSpec());
135 }
138 }
136
139
137 public void artifactSlots(Closure<?> configure) {
140 public void artifactSlots(Closure<?> configure) {
138 artifactSlots(Closures.action(configure));
141 artifactSlots(Closures.action(configure));
139 }
142 }
140
143
141 public BuildArtifactSlot artifactSlot(String name) {
144 public BuildArtifactSlot artifactSlot(String name) {
142 return artifactSlot(name, it -> {
145 return artifactSlot(name, it -> {
143 });
146 });
144 }
147 }
145
148
146 public BuildArtifactSlot artifactSlot(String name, Action<? super BuildArtifactSlot> configure) {
149 public BuildArtifactSlot artifactSlot(String name, Action<? super BuildArtifactSlot> configure) {
147 ensureMutable("configure artifact slots");
150 ensureMutable("configure artifact slots");
148 var slot = artifactSlots.computeIfAbsent(name, this::newArtifactSlot);
151 var slot = artifactSlots.computeIfAbsent(name, this::newArtifactSlot);
149 configure.execute(slot);
152 configure.execute(slot);
150 return slot;
153 return slot;
151 }
154 }
152
155
153 public BuildArtifactSlot artifactSlot(String name, Closure<?> configure) {
156 public BuildArtifactSlot artifactSlot(String name, Closure<?> configure) {
154 return artifactSlot(name, Closures.action(configure));
157 return artifactSlot(name, Closures.action(configure));
155 }
158 }
156
159
157 public BuildArtifactSlot getArtifactSlotByName(String name) {
160 public BuildArtifactSlot getArtifactSlotByName(String name) {
158 return artifactSlots.get(name);
161 return artifactSlots.get(name);
159 }
162 }
160
163
161 Set<String> declaredLayerNames() {
164 Set<String> declaredLayerNames() {
162 var result = new LinkedHashSet<String>();
165 var result = new LinkedHashSet<String>();
163
166
164 for (var role : roles.values())
167 for (var role : roles.values())
165 result.addAll(role.getLayers().getOrElse(java.util.List.of()));
168 result.addAll(role.getLayers().getOrElse(java.util.List.of()));
166
169
167 return result;
170 return result;
168 }
171 }
169
172
170 void finalizeModel() {
173 void finalizeModel() {
171 if (finalized)
174 if (finalized)
172 return;
175 return;
173
176
174 for (var role : roles.values())
177 for (var role : roles.values())
175 role.finalizeModel();
178 role.finalizeModel();
176
179
177 attributes.finalizeModel();
180 attributes.finalizeModel();
178 finalized = true;
181 finalized = true;
179 }
182 }
180
183
181 private BuildRole newRole(String roleName) {
184 private BuildRole newRole(String roleName) {
182 return objects.newInstance(BuildRole.class, roleName);
185 return objects.newInstance(BuildRole.class, roleName);
183 }
186 }
184
187
185 private BuildArtifactSlot newArtifactSlot(String slotName) {
188 private BuildArtifactSlot newArtifactSlot(String slotName) {
186 return objects.newInstance(BuildArtifactSlot.class, slotName);
189 return objects.newInstance(BuildArtifactSlot.class, slotName);
187 }
190 }
188
191
189 private void ensureMutable(String operation) {
192 private void ensureMutable(String operation) {
190 if (finalized)
193 if (finalized)
191 throw new InvalidUserDataException("Variant '" + name + "' is finalized and cannot " + operation);
194 throw new InvalidUserDataException("Variant '" + name + "' is finalized and cannot " + operation);
192 }
195 }
193
196
197 private static String requireLinkValue(String field, String value) {
198 if (value == null || value.trim().isEmpty())
199 throw new InvalidUserDataException("Link '" + field + "' must not be null or blank");
200
201 return value.trim();
202 }
203
194 public final class RolesSpec {
204 public final class RolesSpec {
195 public BuildRole role(String name, Action<? super BuildRole> configure) {
205 public BuildRole role(String name, Action<? super BuildRole> configure) {
196 return BuildVariant.this.role(name, configure);
206 return BuildVariant.this.role(name, configure);
197 }
207 }
198
208
199 public BuildRole role(String name, Closure<?> configure) {
209 public BuildRole role(String name, Closure<?> configure) {
200 return BuildVariant.this.role(name, configure);
210 return BuildVariant.this.role(name, configure);
201 }
211 }
202
212
203 public BuildRole role(String name) {
213 public BuildRole role(String name) {
204 return BuildVariant.this.role(name);
214 return BuildVariant.this.role(name);
205 }
215 }
206
216
207 public Collection<BuildRole> getAll() {
217 public Collection<BuildRole> getAll() {
208 return BuildVariant.this.getRoles();
218 return BuildVariant.this.getRoles();
209 }
219 }
210
220
211 public BuildRole getByName(String name) {
221 public BuildRole getByName(String name) {
212 return BuildVariant.this.getRoleByName(name);
222 return BuildVariant.this.getRoleByName(name);
213 }
223 }
214 }
224 }
215
225
216 public final class LinksSpec {
226 public final class LinksSpec {
217 public LayerLink link(String from, String to, String kind) {
227 public LayerLink link(String from, String to, String kind) {
218 return BuildVariant.this.link(from, to, kind);
228 return BuildVariant.this.link(from, to, kind);
219 }
229 }
220
230
221 public Collection<LayerLink> getAll() {
231 public Collection<LayerLink> getAll() {
222 return BuildVariant.this.getLinks();
232 return BuildVariant.this.getLinks();
223 }
233 }
224 }
234 }
225
235
226 public final class ArtifactSlotsSpec {
236 public final class ArtifactSlotsSpec {
227 public BuildArtifactSlot artifactSlot(String name, Action<? super BuildArtifactSlot> configure) {
237 public BuildArtifactSlot artifactSlot(String name, Action<? super BuildArtifactSlot> configure) {
228 return BuildVariant.this.artifactSlot(name, configure);
238 return BuildVariant.this.artifactSlot(name, configure);
229 }
239 }
230
240
231 public BuildArtifactSlot artifactSlot(String name, Closure<?> configure) {
241 public BuildArtifactSlot artifactSlot(String name, Closure<?> configure) {
232 return BuildVariant.this.artifactSlot(name, configure);
242 return BuildVariant.this.artifactSlot(name, configure);
233 }
243 }
234
244
235 public BuildArtifactSlot artifactSlot(String name) {
245 public BuildArtifactSlot artifactSlot(String name) {
236 return BuildVariant.this.artifactSlot(name);
246 return BuildVariant.this.artifactSlot(name);
237 }
247 }
238
248
239 public Collection<BuildArtifactSlot> getAll() {
249 public Collection<BuildArtifactSlot> getAll() {
240 return BuildVariant.this.getArtifactSlots();
250 return BuildVariant.this.getArtifactSlots();
241 }
251 }
242
252
243 public BuildArtifactSlot getByName(String name) {
253 public BuildArtifactSlot getByName(String name) {
244 return BuildVariant.this.getArtifactSlotByName(name);
254 return BuildVariant.this.getArtifactSlotByName(name);
245 }
255 }
246 }
256 }
247
257
248 public static final class AttributesSpec {
258 public static final class AttributesSpec {
249 private final VariantAttributes attributes;
259 private final VariantAttributes attributes;
250
260
251 AttributesSpec(VariantAttributes attributes) {
261 AttributesSpec(VariantAttributes attributes) {
252 this.attributes = attributes;
262 this.attributes = attributes;
253 }
263 }
254
264
255 public <T> void attribute(Attribute<T> key, T value) {
265 public <T> void attribute(Attribute<T> key, T value) {
256 attributes.attribute(key, value);
266 attributes.attribute(key, value);
257 }
267 }
258
268
259 public <T> void attributeProvider(Attribute<T> key, Provider<? extends T> value) {
269 public <T> void attributeProvider(Attribute<T> key, Provider<? extends T> value) {
260 attributes.attributeProvider(key, value);
270 attributes.attributeProvider(key, value);
261 }
271 }
262
272
263 public void string(String name, String value) {
273 public void string(String name, String value) {
264 attribute(Attribute.of(name, String.class), value);
274 attribute(Attribute.of(name, String.class), value);
265 }
275 }
266
276
267 public void string(String name, Provider<? extends String> value) {
277 public void string(String name, Provider<? extends String> value) {
268 attributeProvider(Attribute.of(name, String.class), value);
278 attributeProvider(Attribute.of(name, String.class), value);
269 }
279 }
270
280
271 public void bool(String name, boolean value) {
281 public void bool(String name, boolean value) {
272 attribute(Attribute.of(name, Boolean.class), value);
282 attribute(Attribute.of(name, Boolean.class), value);
273 }
283 }
274
284
275 public void bool(String name, Provider<? extends Boolean> value) {
285 public void bool(String name, Provider<? extends Boolean> value) {
276 attributeProvider(Attribute.of(name, Boolean.class), value);
286 attributeProvider(Attribute.of(name, Boolean.class), value);
277 }
287 }
278
288
279 public void integer(String name, int value) {
289 public void integer(String name, int value) {
280 attribute(Attribute.of(name, Integer.class), value);
290 attribute(Attribute.of(name, Integer.class), value);
281 }
291 }
282
292
283 public void integer(String name, Provider<? extends Integer> value) {
293 public void integer(String name, Provider<? extends Integer> value) {
284 attributeProvider(Attribute.of(name, Integer.class), value);
294 attributeProvider(Attribute.of(name, Integer.class), value);
285 }
295 }
286
296
287 public VariantAttributes asAttributes() {
297 public VariantAttributes asAttributes() {
288 return attributes;
298 return attributes;
289 }
299 }
290 }
300 }
291 }
301 }
@@ -1,295 +1,346
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.ArrayList;
4 import java.util.Collection;
4 import java.util.Collection;
5 import java.util.Collections;
5 import java.util.Collections;
6 import java.util.HashMap;
6 import java.util.HashMap;
7 import java.util.HashSet;
7 import java.util.HashSet;
8 import java.util.LinkedHashMap;
8 import java.util.LinkedHashMap;
9 import java.util.LinkedHashSet;
9 import java.util.LinkedHashSet;
10 import java.util.List;
10 import java.util.List;
11 import java.util.Map;
11 import java.util.Map;
12 import java.util.Set;
12 import java.util.Set;
13
13
14 import javax.inject.Inject;
14 import javax.inject.Inject;
15
15
16 import org.implab.gradle.common.core.lang.Closures;
16 import org.implab.gradle.common.core.lang.Closures;
17 import org.gradle.api.Action;
17 import org.gradle.api.Action;
18 import org.gradle.api.InvalidUserDataException;
18 import org.gradle.api.InvalidUserDataException;
19 import org.gradle.api.NamedDomainObjectContainer;
19 import org.gradle.api.NamedDomainObjectContainer;
20 import org.gradle.api.model.ObjectFactory;
20 import org.gradle.api.model.ObjectFactory;
21
21
22 import groovy.lang.Closure;
22 import groovy.lang.Closure;
23
23
24 public abstract class BuildVariantsExtension {
24 public abstract class BuildVariantsExtension {
25 private final NamedDomainObjectContainer<BuildLayer> layers;
25 private final NamedDomainObjectContainer<BuildLayer> layers;
26 private final NamedDomainObjectContainer<BuildVariant> variants;
26 private final NamedDomainObjectContainer<BuildVariant> variants;
27 private final List<Action<? super BuildVariantsExtension>> finalizedActions = new ArrayList<>();
27 private final List<Action<? super BuildVariantsExtension>> finalizedActions = new ArrayList<>();
28 private boolean finalized;
28 private boolean finalized;
29
29
30 @Inject
30 @Inject
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
34
35 layers.all(layer -> {
35 layers.all(layer -> {
36 if (finalized)
36 if (finalized)
37 throw new InvalidUserDataException(
37 throw new InvalidUserDataException(
38 "Variants model is finalized and cannot add layer '" + layer.getName() + "'");
38 "Variants model is finalized and cannot add layer '" + layer.getName() + "'");
39 });
39 });
40
40
41 variants.all(variant -> {
41 variants.all(variant -> {
42 if (finalized)
42 if (finalized)
43 throw new InvalidUserDataException(
43 throw new InvalidUserDataException(
44 "Variants model is finalized and cannot add variant '" + variant.getName() + "'");
44 "Variants model is finalized and cannot add variant '" + variant.getName() + "'");
45 });
45 });
46 }
46 }
47
47
48 public NamedDomainObjectContainer<BuildLayer> getLayers() {
48 public NamedDomainObjectContainer<BuildLayer> getLayers() {
49 return layers;
49 return layers;
50 }
50 }
51
51
52 public NamedDomainObjectContainer<BuildVariant> getVariants() {
52 public NamedDomainObjectContainer<BuildVariant> getVariants() {
53 return variants;
53 return variants;
54 }
54 }
55
55
56 public void layers(Action<? super NamedDomainObjectContainer<BuildLayer>> action) {
56 public void layers(Action<? super NamedDomainObjectContainer<BuildLayer>> action) {
57 ensureMutable("configure layers");
57 ensureMutable("configure layers");
58 action.execute(layers);
58 action.execute(layers);
59 }
59 }
60
60
61 public void layers(Closure<?> configure) {
61 public void layers(Closure<?> configure) {
62 layers(Closures.action(configure));
62 layers(Closures.action(configure));
63 }
63 }
64
64
65 public void variants(Action<? super NamedDomainObjectContainer<BuildVariant>> action) {
65 public void variants(Action<? super NamedDomainObjectContainer<BuildVariant>> action) {
66 ensureMutable("configure variants");
66 ensureMutable("configure variants");
67 action.execute(variants);
67 action.execute(variants);
68 }
68 }
69
69
70 public void variants(Closure<?> configure) {
70 public void variants(Closure<?> configure) {
71 variants(Closures.action(configure));
71 variants(Closures.action(configure));
72 }
72 }
73
73
74 public BuildLayer layer(String name, Action<? super BuildLayer> configure) {
74 public BuildLayer layer(String name, Action<? super BuildLayer> configure) {
75 ensureMutable("configure layers");
75 ensureMutable("configure layers");
76 var layer = layers.maybeCreate(name);
76 var layer = layers.maybeCreate(name);
77 configure.execute(layer);
77 configure.execute(layer);
78 return layer;
78 return layer;
79 }
79 }
80
80
81 public BuildLayer layer(String name, Closure<?> configure) {
81 public BuildLayer layer(String name, Closure<?> configure) {
82 return layer(name, Closures.action(configure));
82 return layer(name, Closures.action(configure));
83 }
83 }
84
84
85 public BuildLayer layer(String name) {
85 public BuildLayer layer(String name) {
86 return layer(name, it -> {
86 return layer(name, it -> {
87 });
87 });
88 }
88 }
89
89
90 public BuildVariant variant(String name, Action<? super BuildVariant> configure) {
90 public BuildVariant variant(String name, Action<? super BuildVariant> configure) {
91 ensureMutable("configure variants");
91 ensureMutable("configure variants");
92 var variant = variants.maybeCreate(name);
92 var variant = variants.maybeCreate(name);
93 configure.execute(variant);
93 configure.execute(variant);
94 return variant;
94 return variant;
95 }
95 }
96
96
97 public BuildVariant variant(String name, Closure<?> configure) {
97 public BuildVariant variant(String name, Closure<?> configure) {
98 return variant(name, Closures.action(configure));
98 return variant(name, Closures.action(configure));
99 }
99 }
100
100
101 public BuildVariant variant(String name) {
101 public BuildVariant variant(String name) {
102 return variant(name, it -> {
102 return variant(name, it -> {
103 });
103 });
104 }
104 }
105
105
106 public void all(Action<? super BuildVariant> action) {
106 public void all(Action<? super BuildVariant> action) {
107 variants.all(action);
107 variants.all(action);
108 }
108 }
109
109
110 public void all(Closure<?> configure) {
110 public void all(Closure<?> configure) {
111 all(Closures.action(configure));
111 all(Closures.action(configure));
112 }
112 }
113
113
114 public Collection<BuildVariant> getAll() {
114 public Collection<BuildVariant> getAll() {
115 var all = new ArrayList<BuildVariant>();
115 var all = new ArrayList<BuildVariant>();
116 variants.forEach(all::add);
116 variants.forEach(all::add);
117 return Collections.unmodifiableList(all);
117 return Collections.unmodifiableList(all);
118 }
118 }
119
119
120 public BuildVariant getByName(String name) {
120 public BuildVariant getByName(String name) {
121 return variants.findByName(name);
121 return variants.findByName(name);
122 }
122 }
123
123
124 public void whenFinalized(Action<? super BuildVariantsExtension> action) {
124 public void whenFinalized(Action<? super BuildVariantsExtension> action) {
125 if (finalized) {
125 if (finalized) {
126 action.execute(this);
126 action.execute(this);
127 return;
127 return;
128 }
128 }
129 finalizedActions.add(action);
129 finalizedActions.add(action);
130 }
130 }
131
131
132 public void whenFinalized(Closure<?> configure) {
132 public void whenFinalized(Closure<?> configure) {
133 whenFinalized(Closures.action(configure));
133 whenFinalized(Closures.action(configure));
134 }
134 }
135
135
136 public boolean isFinalized() {
136 public boolean isFinalized() {
137 return finalized;
137 return finalized;
138 }
138 }
139
139
140 public void finalizeModel() {
140 public void finalizeModel() {
141 if (finalized)
141 if (finalized)
142 return;
142 return;
143
143
144 validate();
144 validate();
145
145
146 for (var variant : variants)
146 for (var variant : variants)
147 variant.finalizeModel();
147 variant.finalizeModel();
148
148
149 finalized = true;
149 finalized = true;
150
150
151 var actions = new ArrayList<>(finalizedActions);
151 var actions = new ArrayList<>(finalizedActions);
152 finalizedActions.clear();
152 finalizedActions.clear();
153 for (var action : actions)
153 for (var action : actions)
154 action.execute(this);
154 action.execute(this);
155 }
155 }
156
156
157 public void validate() {
157 public void validate() {
158 var errors = new ArrayList<String>();
158 var errors = new ArrayList<String>();
159
159
160 var layersByName = new LinkedHashMap<String, BuildLayer>();
160 var layersByName = new LinkedHashMap<String, BuildLayer>();
161 for (var layer : layers)
161 for (var layer : layers) {
162 layersByName.put(layer.getName(), layer);
162 var layerName = normalize(layer.getName());
163 if (layerName == null) {
164 errors.add("Layer name must not be blank");
165 continue;
166 }
167
168 var previous = layersByName.putIfAbsent(layerName, layer);
169 if (previous != null) {
170 errors.add("Layer '" + layerName + "' is declared more than once");
171 }
172 }
163
173
164 for (var variant : variants)
174 for (var variant : variants)
165 validateVariant(variant, layersByName, errors);
175 validateVariant(variant, layersByName, errors);
166
176
167 if (!errors.isEmpty()) {
177 if (!errors.isEmpty()) {
168 var message = new StringBuilder("Invalid variants model:");
178 var message = new StringBuilder("Invalid variants model:");
169 for (var error : errors)
179 for (var error : errors)
170 message.append("\n - ").append(error);
180 message.append("\n - ").append(error);
171
181
172 throw new InvalidUserDataException(message.toString());
182 throw new InvalidUserDataException(message.toString());
173 }
183 }
174 }
184 }
175
185
176 private static void validateVariant(BuildVariant variant, Map<String, BuildLayer> layersByName, List<String> errors) {
186 private static void validateVariant(BuildVariant variant, Map<String, BuildLayer> layersByName, List<String> errors) {
187 var variantName = normalize(variant.getName());
188 if (variantName == null) {
189 errors.add("Variant name must not be blank");
190 return;
191 }
192
193 validateRoleAndArtifactNames(variant, errors);
177 var variantLayers = validateRoleMappings(variant, layersByName, errors);
194 var variantLayers = validateRoleMappings(variant, layersByName, errors);
178 validateLinks(variant, variantLayers, errors);
195 validateLinks(variant, variantLayers, errors);
179 }
196 }
180
197
198 private static void validateRoleAndArtifactNames(BuildVariant variant, List<String> errors) {
199 var roleNames = new LinkedHashSet<String>();
200 for (var role : variant.getRoles()) {
201 var roleName = normalize(role.getName());
202 if (roleName == null) {
203 errors.add("Variant '" + variant.getName() + "' contains blank role name");
204 continue;
205 }
206 if (!roleNames.add(roleName)) {
207 errors.add("Variant '" + variant.getName() + "' contains duplicated role name '" + roleName + "'");
208 }
209 }
210
211 var slotNames = new LinkedHashSet<String>();
212 for (var slot : variant.getArtifactSlots()) {
213 var slotName = normalize(slot.getName());
214 if (slotName == null) {
215 errors.add("Variant '" + variant.getName() + "' contains blank artifact slot name");
216 continue;
217 }
218 if (!slotNames.add(slotName)) {
219 errors.add("Variant '" + variant.getName() + "' contains duplicated artifact slot name '" + slotName + "'");
220 }
221 }
222 }
223
181 private static Set<String> validateRoleMappings(BuildVariant variant, Map<String, BuildLayer> layersByName,
224 private static Set<String> validateRoleMappings(BuildVariant variant, Map<String, BuildLayer> layersByName,
182 List<String> errors) {
225 List<String> errors) {
183 var variantLayers = new LinkedHashSet<String>();
226 var variantLayers = new LinkedHashSet<String>();
184
227
185 for (var role : variant.getRoles()) {
228 for (var role : variant.getRoles()) {
229 var seenLayers = new LinkedHashSet<String>();
186 for (var layerName : role.getLayers().getOrElse(List.of())) {
230 for (var layerName : role.getLayers().getOrElse(List.of())) {
187 if (isBlank(layerName)) {
231 var normalizedLayerName = normalize(layerName);
232 if (normalizedLayerName == null) {
188 errors.add("Variant '" + variant.getName() + "', role '" + role.getName() + "' contains blank layer name");
233 errors.add("Variant '" + variant.getName() + "', role '" + role.getName() + "' contains blank layer name");
189 continue;
234 continue;
190 }
235 }
191
236
192 var layer = layersByName.get(layerName);
237 var layer = layersByName.get(normalizedLayerName);
193 if (layer == null) {
238 if (layer == null) {
194 errors.add("Variant '" + variant.getName() + "' references unknown layer '" + layerName + "'");
239 errors.add("Variant '" + variant.getName() + "' references unknown layer '" + normalizedLayerName + "'");
195 continue;
240 continue;
196 }
241 }
197
242
198 variantLayers.add(layerName);
243 if (!seenLayers.add(normalizedLayerName)) {
244 errors.add("Variant '" + variant.getName() + "', role '" + role.getName()
245 + "' contains duplicated layer reference '" + normalizedLayerName + "'");
246 continue;
247 }
248
249 variantLayers.add(normalizedLayerName);
199 }
250 }
200 }
251 }
201
252
202 return variantLayers;
253 return variantLayers;
203 }
254 }
204
255
205 private static void validateLinks(BuildVariant variant, Set<String> variantLayers, List<String> errors) {
256 private static void validateLinks(BuildVariant variant, Set<String> variantLayers, List<String> errors) {
206 var seenLinks = new HashSet<String>();
257 var seenLinks = new HashSet<String>();
207 var edgesByKind = new HashMap<String, Map<String, Set<String>>>();
258 var edgesByKind = new HashMap<String, Map<String, Set<String>>>();
208
259
209 for (var link : variant.getLinks()) {
260 for (var link : variant.getLinks()) {
210 var from = normalize(link.from());
261 var from = normalize(link.from());
211 var to = normalize(link.to());
262 var to = normalize(link.to());
212 var kind = normalize(link.kind());
263 var kind = normalize(link.kind());
213
264
214 if (from == null || to == null || kind == null) {
265 if (from == null || to == null || kind == null) {
215 errors.add("Variant '" + variant.getName() + "' has incomplete link (from/to/kind are required)");
266 errors.add("Variant '" + variant.getName() + "' has incomplete link (from/to/kind are required)");
216 continue;
267 continue;
217 }
268 }
218
269
219 if (!variantLayers.contains(from)) {
270 if (!variantLayers.contains(from)) {
220 errors.add("Variant '" + variant.getName() + "' link references unknown source layer '"
271 errors.add("Variant '" + variant.getName() + "' link references unknown source layer '"
221 + from + "'");
272 + from + "'");
222 continue;
273 continue;
223 }
274 }
224
275
225 if (!variantLayers.contains(to)) {
276 if (!variantLayers.contains(to)) {
226 errors.add("Variant '" + variant.getName() + "' link references unknown target layer '"
277 errors.add("Variant '" + variant.getName() + "' link references unknown target layer '"
227 + to + "'");
278 + to + "'");
228 continue;
279 continue;
229 }
280 }
230
281
231 var linkKey = from + "\u0000" + to + "\u0000" + kind;
282 var linkKey = from + "\u0000" + to + "\u0000" + kind;
232 if (!seenLinks.add(linkKey)) {
283 if (!seenLinks.add(linkKey)) {
233 errors.add("Variant '" + variant.getName() + "' has duplicated link tuple (from='" + from
284 errors.add("Variant '" + variant.getName() + "' has duplicated link tuple (from='" + from
234 + "', to='" + to + "', kind='" + kind + "')");
285 + "', to='" + to + "', kind='" + kind + "')");
235 }
286 }
236
287
237 edgesByKind
288 edgesByKind
238 .computeIfAbsent(kind, x -> new LinkedHashMap<>())
289 .computeIfAbsent(kind, x -> new LinkedHashMap<>())
239 .computeIfAbsent(from, x -> new LinkedHashSet<>())
290 .computeIfAbsent(from, x -> new LinkedHashSet<>())
240 .add(to);
291 .add(to);
241 }
292 }
242
293
243 for (var entry : edgesByKind.entrySet()) {
294 for (var entry : edgesByKind.entrySet()) {
244 if (hasCycle(variantLayers, entry.getValue())) {
295 if (hasCycle(variantLayers, entry.getValue())) {
245 errors.add("Variant '" + variant.getName() + "' contains cycle in links with kind '" + entry.getKey() + "'");
296 errors.add("Variant '" + variant.getName() + "' contains cycle in links with kind '" + entry.getKey() + "'");
246 }
297 }
247 }
298 }
248 }
299 }
249
300
250 private static boolean hasCycle(Set<String> nodes, Map<String, Set<String>> edges) {
301 private static boolean hasCycle(Set<String> nodes, Map<String, Set<String>> edges) {
251 var state = new HashMap<String, Integer>();
302 var state = new HashMap<String, Integer>();
252
303
253 for (var node : nodes) {
304 for (var node : nodes) {
254 if (dfs(node, state, edges))
305 if (dfs(node, state, edges))
255 return true;
306 return true;
256 }
307 }
257
308
258 return false;
309 return false;
259 }
310 }
260
311
261 private static boolean dfs(String node, Map<String, Integer> state, Map<String, Set<String>> edges) {
312 private static boolean dfs(String node, Map<String, Integer> state, Map<String, Set<String>> edges) {
262 var current = state.getOrDefault(node, 0);
313 var current = state.getOrDefault(node, 0);
263 if (current == 1)
314 if (current == 1)
264 return true;
315 return true;
265 if (current == 2)
316 if (current == 2)
266 return false;
317 return false;
267
318
268 state.put(node, 1);
319 state.put(node, 1);
269
320
270 for (var next : edges.getOrDefault(node, Set.of())) {
321 for (var next : edges.getOrDefault(node, Set.of())) {
271 if (dfs(next, state, edges))
322 if (dfs(next, state, edges))
272 return true;
323 return true;
273 }
324 }
274
325
275 state.put(node, 2);
326 state.put(node, 2);
276 return false;
327 return false;
277 }
328 }
278
329
279 private static String normalize(String value) {
330 private static String normalize(String value) {
280 if (value == null)
331 if (value == null)
281 return null;
332 return null;
282
333
283 var trimmed = value.trim();
334 var trimmed = value.trim();
284 return trimmed.isEmpty() ? null : trimmed;
335 return trimmed.isEmpty() ? null : trimmed;
285 }
336 }
286
337
287 private static boolean isBlank(String value) {
338 private static boolean isBlank(String value) {
288 return normalize(value) == null;
339 return normalize(value) == null;
289 }
340 }
290
341
291 private void ensureMutable(String operation) {
342 private void ensureMutable(String operation) {
292 if (finalized)
343 if (finalized)
293 throw new InvalidUserDataException("Variants model is finalized and cannot " + operation);
344 throw new InvalidUserDataException("Variants model is finalized and cannot " + operation);
294 }
345 }
295 }
346 }
@@ -1,304 +1,311
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.ArrayList;
4 import java.util.LinkedHashMap;
4 import java.util.LinkedHashMap;
5 import java.util.List;
5 import java.util.List;
6 import java.util.regex.Matcher;
6 import java.util.regex.Matcher;
7 import java.util.regex.Pattern;
7 import java.util.regex.Pattern;
8 import java.util.stream.Stream;
8 import java.util.stream.Stream;
9
9
10 import javax.inject.Inject;
10 import javax.inject.Inject;
11
11
12 import org.implab.gradle.common.core.lang.Closures;
12 import org.implab.gradle.common.core.lang.Closures;
13 import org.implab.gradle.common.core.lang.Strings;
13 import org.implab.gradle.common.core.lang.Strings;
14 import org.eclipse.jdt.annotation.NonNullByDefault;
14 import org.eclipse.jdt.annotation.NonNullByDefault;
15 import org.gradle.api.Action;
15 import org.gradle.api.Action;
16 import org.gradle.api.InvalidUserDataException;
16 import org.gradle.api.InvalidUserDataException;
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;
20 import org.gradle.api.logging.Logger;
21 import org.gradle.api.logging.Logging;
21 import org.gradle.api.logging.Logging;
22
22
23 import groovy.lang.Closure;
23 import groovy.lang.Closure;
24 import groovy.lang.DelegatesTo;
24 import groovy.lang.DelegatesTo;
25
25
26 /**
26 /**
27 * Adapter extension that registers source sets for variant/layer pairs.
27 * Adapter extension that registers source sets for variant/layer pairs.
28 */
28 */
29 @NonNullByDefault
29 @NonNullByDefault
30 public abstract class VariantSourcesExtension {
30 public abstract class VariantSourcesExtension {
31 private static final Logger logger = Logging.getLogger(VariantSourcesExtension.class);
31 private static final Logger logger = Logging.getLogger(VariantSourcesExtension.class);
32 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_.-]");
33 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]*)\\}");
34
34
35 private final ObjectFactory objects;
35 private final ObjectFactory objects;
36 private final NamedDomainObjectContainer<BuildLayerBinding> bindings;
36 private final NamedDomainObjectContainer<BuildLayerBinding> bindings;
37 private final List<Action<? super SourceSetContext>> registeredActions = new ArrayList<>();
37 private final List<Action<? super SourceSetContext>> registeredActions = new ArrayList<>();
38 private final List<Action<? super SourceSetContext>> boundActions = new ArrayList<>();
38 private final List<Action<? super SourceSetContext>> boundActions = new ArrayList<>();
39 private final List<SourceSetContext> registeredContexts = new ArrayList<>();
39 private final List<SourceSetContext> registeredContexts = new ArrayList<>();
40 private final List<SourceSetContext> boundContexts = new ArrayList<>();
40 private final List<SourceSetContext> boundContexts = new ArrayList<>();
41 private final LinkedHashMap<String, NamedDomainObjectProvider<GenericSourceSet>> sourceSetsByName = new LinkedHashMap<>();
41 private final LinkedHashMap<String, NamedDomainObjectProvider<GenericSourceSet>> sourceSetsByName = new LinkedHashMap<>();
42 private final LinkedHashMap<String, String> sourceSetLayersByName = new LinkedHashMap<>();
42 private final LinkedHashMap<String, String> sourceSetLayersByName = new LinkedHashMap<>();
43 private boolean sourceSetsRegistered;
43
44
44 @Inject
45 @Inject
45 public VariantSourcesExtension(ObjectFactory objects) {
46 public VariantSourcesExtension(ObjectFactory objects) {
46 this.objects = objects;
47 this.objects = objects;
47 bindings = objects.domainObjectContainer(BuildLayerBinding.class);
48 bindings = objects.domainObjectContainer(BuildLayerBinding.class);
48 }
49 }
49
50
50 public NamedDomainObjectContainer<BuildLayerBinding> getBindings() {
51 public NamedDomainObjectContainer<BuildLayerBinding> getBindings() {
51 return bindings;
52 return bindings;
52 }
53 }
53
54
54 public void bindings(Action<? super NamedDomainObjectContainer<BuildLayerBinding>> action) {
55 public void bindings(Action<? super NamedDomainObjectContainer<BuildLayerBinding>> action) {
55 action.execute(bindings);
56 action.execute(bindings);
56 }
57 }
57
58
58 public void bindings(
59 public void bindings(
59 @DelegatesTo(value = NamedDomainObjectContainer.class, strategy = Closure.DELEGATE_FIRST) Closure<?> action) {
60 @DelegatesTo(value = NamedDomainObjectContainer.class, strategy = Closure.DELEGATE_FIRST) Closure<?> action) {
60 bindings(Closures.action(action));
61 bindings(Closures.action(action));
61 }
62 }
62
63
63 public BuildLayerBinding bind(String layer) {
64 public BuildLayerBinding bind(String layer) {
64 return bindings.maybeCreate(normalize(layer));
65 return bindings.maybeCreate(normalize(layer));
65 }
66 }
66
67
67 /**
68 /**
68 * Configures per-layer binding.
69 * Configures per-layer binding.
69 */
70 */
70 public BuildLayerBinding bind(String layer, Action<? super BuildLayerBinding> configure) {
71 public BuildLayerBinding bind(String layer, Action<? super BuildLayerBinding> configure) {
71 var binding = bind(layer);
72 var binding = bind(layer);
72 configure.execute(binding);
73 configure.execute(binding);
73 return binding;
74 return binding;
74 }
75 }
75
76
76 public BuildLayerBinding bind(String layer,
77 public BuildLayerBinding bind(String layer,
77 @DelegatesTo(value = BuildLayerBinding.class, strategy = Closure.DELEGATE_FIRST) Closure<?> configure) {
78 @DelegatesTo(value = BuildLayerBinding.class, strategy = Closure.DELEGATE_FIRST) Closure<?> configure) {
78 return bind(layer, Closures.action(configure));
79 return bind(layer, Closures.action(configure));
79 }
80 }
80
81
81 /**
82 /**
82 * Global callback fired for each registered source-set context.
83 * Global callback fired for each registered source-set context.
83 * Already emitted contexts are delivered immediately (replay).
84 * Already emitted contexts are delivered immediately (replay).
84 * For simple callbacks you can use delegate-only style
85 * For simple callbacks you can use delegate-only style
85 * (for example {@code whenRegistered { sourceSetName() }}).
86 * (for example {@code whenRegistered { sourceSetName() }}).
86 * For nested closures prefer explicit parameter
87 * For nested closures prefer explicit parameter
87 * ({@code whenRegistered { ctx -> ... }}).
88 * ({@code whenRegistered { ctx -> ... }}).
88 */
89 */
89 public void whenRegistered(Action<? super SourceSetContext> action) {
90 public void whenRegistered(Action<? super SourceSetContext> action) {
90 registeredActions.add(action);
91 registeredActions.add(action);
91 for (var context : registeredContexts)
92 for (var context : registeredContexts)
92 action.execute(context);
93 action.execute(context);
93 }
94 }
94
95
95 public void whenRegistered(
96 public void whenRegistered(
96 @DelegatesTo(value = SourceSetContext.class, strategy = Closure.DELEGATE_FIRST) Closure<?> action) {
97 @DelegatesTo(value = SourceSetContext.class, strategy = Closure.DELEGATE_FIRST) Closure<?> action) {
97 whenRegistered(Closures.action(action));
98 whenRegistered(Closures.action(action));
98 }
99 }
99
100
100 public void whenRegistered(String variantName, Action<? super SourceSetContext> action) {
101 public void whenRegistered(String variantName, Action<? super SourceSetContext> action) {
101 var normalizedVariantName = normalize(variantName, "variantName must not be null or blank");
102 var normalizedVariantName = normalize(variantName, "variantName must not be null or blank");
102 whenRegistered(filterByVariant(normalizedVariantName, action));
103 whenRegistered(filterByVariant(normalizedVariantName, action));
103 }
104 }
104
105
105 public void whenRegistered(String variantName,
106 public void whenRegistered(String variantName,
106 @DelegatesTo(value = SourceSetContext.class, strategy = Closure.DELEGATE_FIRST) Closure<?> action) {
107 @DelegatesTo(value = SourceSetContext.class, strategy = Closure.DELEGATE_FIRST) Closure<?> action) {
107 whenRegistered(variantName, Closures.action(action));
108 whenRegistered(variantName, Closures.action(action));
108 }
109 }
109
110
110 /**
111 /**
111 * Global callback fired for every resolved variant/role/layer usage.
112 * Global callback fired for every resolved variant/role/layer usage.
112 * Already emitted contexts are delivered immediately (replay).
113 * Already emitted contexts are delivered immediately (replay).
113 * For simple callbacks you can use delegate-only style
114 * For simple callbacks you can use delegate-only style
114 * (for example {@code whenBound { variantName() }}).
115 * (for example {@code whenBound { variantName() }}).
115 * For nested closures prefer explicit parameter
116 * For nested closures prefer explicit parameter
116 * ({@code whenBound { ctx -> ... }}).
117 * ({@code whenBound { ctx -> ... }}).
117 */
118 */
118 public void whenBound(Action<? super SourceSetContext> action) {
119 public void whenBound(Action<? super SourceSetContext> action) {
119 boundActions.add(action);
120 boundActions.add(action);
120 for (var context : boundContexts)
121 for (var context : boundContexts)
121 action.execute(context);
122 action.execute(context);
122 }
123 }
123
124
124 public void whenBound(
125 public void whenBound(
125 @DelegatesTo(value = SourceSetContext.class, strategy = Closure.DELEGATE_FIRST) Closure<?> action) {
126 @DelegatesTo(value = SourceSetContext.class, strategy = Closure.DELEGATE_FIRST) Closure<?> action) {
126 whenBound(Closures.action(action));
127 whenBound(Closures.action(action));
127 }
128 }
128
129
129 public void whenBound(String variantName, Action<? super SourceSetContext> action) {
130 public void whenBound(String variantName, Action<? super SourceSetContext> action) {
130 var normalizedVariantName = normalize(variantName, "variantName must not be null or blank");
131 var normalizedVariantName = normalize(variantName, "variantName must not be null or blank");
131 whenBound(filterByVariant(normalizedVariantName, action));
132 whenBound(filterByVariant(normalizedVariantName, action));
132 }
133 }
133
134
134 public void whenBound(String variantName,
135 public void whenBound(String variantName,
135 @DelegatesTo(value = SourceSetContext.class, strategy = Closure.DELEGATE_FIRST) Closure<?> action) {
136 @DelegatesTo(value = SourceSetContext.class, strategy = Closure.DELEGATE_FIRST) Closure<?> action) {
136 whenBound(variantName, Closures.action(action));
137 whenBound(variantName, Closures.action(action));
137 }
138 }
138
139
139 void registerSourceSets(BuildVariantsExtension variants, NamedDomainObjectContainer<GenericSourceSet> sources) {
140 void registerSourceSets(BuildVariantsExtension variants, NamedDomainObjectContainer<GenericSourceSet> sources) {
141 if (sourceSetsRegistered) {
142 throw new InvalidUserDataException("variantSources source sets are already registered");
143 }
144
140 validateBindings(variants);
145 validateBindings(variants);
141
146
142 var usages = layerUsages(variants).toList();
147 var usages = layerUsages(variants).toList();
143 var registeredBefore = registeredContexts.size();
148 var registeredBefore = registeredContexts.size();
144 var boundBefore = boundContexts.size();
149 var boundBefore = boundContexts.size();
145
150
146 logger.debug(
151 logger.debug(
147 "Starting variant source-set registration (variants={}, layers={}, bindings={}, usages={})",
152 "Starting variant source-set registration (variants={}, layers={}, bindings={}, usages={})",
148 variants.getVariants().size(),
153 variants.getVariants().size(),
149 variants.getLayers().size(),
154 variants.getLayers().size(),
150 bindings.size(),
155 bindings.size(),
151 usages.size());
156 usages.size());
152
157
153 usages.forEach(usage -> registerLayerUsage(usage, sources));
158 usages.forEach(usage -> registerLayerUsage(usage, sources));
154
159
155 logger.debug(
160 logger.debug(
156 "Completed variant source-set registration (newSourceSets={}, newBounds={}, totalSourceSets={})",
161 "Completed variant source-set registration (newSourceSets={}, newBounds={}, totalSourceSets={})",
157 registeredContexts.size() - registeredBefore,
162 registeredContexts.size() - registeredBefore,
158 boundContexts.size() - boundBefore,
163 boundContexts.size() - boundBefore,
159 sourceSetsByName.size());
164 sourceSetsByName.size());
165
166 sourceSetsRegistered = true;
160 }
167 }
161
168
162 private Stream<LayerUsage> layerUsages(BuildVariantsExtension variants) {
169 private Stream<LayerUsage> layerUsages(BuildVariantsExtension variants) {
163 return variants.getVariants().stream()
170 return variants.getVariants().stream()
164 .flatMap(variant -> variant.getRoles().stream()
171 .flatMap(variant -> variant.getRoles().stream()
165 .flatMap(role -> role.getLayers().getOrElse(List.of()).stream()
172 .flatMap(role -> role.getLayers().getOrElse(List.of()).stream()
166 .map(layerName -> new LayerUsage(
173 .map(layerName -> new LayerUsage(
167 variant.getName(),
174 variant.getName(),
168 role.getName(),
175 role.getName(),
169 normalize(layerName)))));
176 normalize(layerName)))));
170 }
177 }
171
178
172 private void registerLayerUsage(LayerUsage usage, NamedDomainObjectContainer<GenericSourceSet> sources) {
179 private void registerLayerUsage(LayerUsage usage, NamedDomainObjectContainer<GenericSourceSet> sources) {
173 var resolvedBinding = bind(usage.layerName());
180 var resolvedBinding = bind(usage.layerName());
174 var sourceSetNamePattern = resolvedBinding.getSourceSetNamePattern();
181 var sourceSetNamePattern = resolvedBinding.getSourceSetNamePattern();
175 sourceSetNamePattern.finalizeValueOnRead();
182 sourceSetNamePattern.finalizeValueOnRead();
176
183
177 var sourceSetName = sourceSetName(usage, sourceSetNamePattern.get());
184 var sourceSetName = sourceSetName(usage, sourceSetNamePattern.get());
178
185
179 ensureSourceSetNameBoundToSingleLayer(sourceSetName, usage.layerName());
186 ensureSourceSetNameBoundToSingleLayer(sourceSetName, usage.layerName());
180 var isNewSourceSet = !sourceSetsByName.containsKey(sourceSetName);
187 var isNewSourceSet = !sourceSetsByName.containsKey(sourceSetName);
181 var sourceSet = sourceSetsByName.computeIfAbsent(sourceSetName,
188 var sourceSet = sourceSetsByName.computeIfAbsent(sourceSetName,
182 name -> sources.register(name));
189 name -> sources.register(name));
183
190
184 var context = new SourceSetContext(
191 var context = new SourceSetContext(
185 usage.variantName(),
192 usage.variantName(),
186 usage.roleName(),
193 usage.roleName(),
187 usage.layerName(),
194 usage.layerName(),
188 sourceSetName,
195 sourceSetName,
189 sourceSet);
196 sourceSet);
190
197
191 if (isNewSourceSet) {
198 if (isNewSourceSet) {
192 resolvedBinding.notifyRegistered(context);
199 resolvedBinding.notifyRegistered(context);
193 notifyRegistered(context);
200 notifyRegistered(context);
194 }
201 }
195
202
196 resolvedBinding.notifyBound(context);
203 resolvedBinding.notifyBound(context);
197 notifyBound(context);
204 notifyBound(context);
198 }
205 }
199
206
200 private void notifyRegistered(SourceSetContext context) {
207 private void notifyRegistered(SourceSetContext context) {
201 registeredContexts.add(context);
208 registeredContexts.add(context);
202 for (var action : registeredActions)
209 for (var action : registeredActions)
203 action.execute(context);
210 action.execute(context);
204 }
211 }
205
212
206 private void notifyBound(SourceSetContext context) {
213 private void notifyBound(SourceSetContext context) {
207 boundContexts.add(context);
214 boundContexts.add(context);
208 for (var action : boundActions)
215 for (var action : boundActions)
209 action.execute(context);
216 action.execute(context);
210 }
217 }
211
218
212 private static Action<? super SourceSetContext> filterByVariant(String variantName,
219 private static Action<? super SourceSetContext> filterByVariant(String variantName,
213 Action<? super SourceSetContext> action) {
220 Action<? super SourceSetContext> action) {
214 return context -> {
221 return context -> {
215 if (variantName.equals(context.variantName()))
222 if (variantName.equals(context.variantName()))
216 action.execute(context);
223 action.execute(context);
217 };
224 };
218 }
225 }
219
226
220 private void ensureSourceSetNameBoundToSingleLayer(String sourceSetName, String layerName) {
227 private void ensureSourceSetNameBoundToSingleLayer(String sourceSetName, String layerName) {
221 var existingLayer = sourceSetLayersByName.putIfAbsent(sourceSetName, layerName);
228 var existingLayer = sourceSetLayersByName.putIfAbsent(sourceSetName, layerName);
222 if (existingLayer != null && !existingLayer.equals(layerName)) {
229 if (existingLayer != null && !existingLayer.equals(layerName)) {
223 throw new InvalidUserDataException("Source set '" + sourceSetName + "' is resolved from multiple layers: '"
230 throw new InvalidUserDataException("Source set '" + sourceSetName + "' is resolved from multiple layers: '"
224 + existingLayer + "' and '" + layerName + "'");
231 + existingLayer + "' and '" + layerName + "'");
225 }
232 }
226 }
233 }
227
234
228 private void validateBindings(BuildVariantsExtension variants) {
235 private void validateBindings(BuildVariantsExtension variants) {
229 var knownLayerNames = new java.util.LinkedHashSet<String>();
236 var knownLayerNames = new java.util.LinkedHashSet<String>();
230 for (var layer : variants.getLayers())
237 for (var layer : variants.getLayers())
231 knownLayerNames.add(layer.getName());
238 knownLayerNames.add(layer.getName());
232
239
233 var errors = new ArrayList<String>();
240 var errors = new ArrayList<String>();
234 for (var binding : bindings) {
241 for (var binding : bindings) {
235 if (!knownLayerNames.contains(binding.getName())) {
242 if (!knownLayerNames.contains(binding.getName())) {
236 errors.add("Layer binding '" + binding.getName() + "' references unknown layer");
243 errors.add("Layer binding '" + binding.getName() + "' references unknown layer");
237 }
244 }
238 }
245 }
239
246
240 if (!errors.isEmpty()) {
247 if (!errors.isEmpty()) {
241 var message = new StringBuilder("Invalid variantSources model:");
248 var message = new StringBuilder("Invalid variantSources model:");
242 for (var error : errors)
249 for (var error : errors)
243 message.append("\n - ").append(error);
250 message.append("\n - ").append(error);
244 throw new InvalidUserDataException(message.toString());
251 throw new InvalidUserDataException(message.toString());
245 }
252 }
246 }
253 }
247
254
248 private static String sourceSetName(LayerUsage usage, String pattern) {
255 private static String sourceSetName(LayerUsage usage, String pattern) {
249 var normalizedPattern = normalize(pattern, "sourceSetNamePattern must not be null or blank");
256 var normalizedPattern = normalize(pattern, "sourceSetNamePattern must not be null or blank");
250 var resolved = resolveSourceSetNamePattern(normalizedPattern, usage);
257 var resolved = resolveSourceSetNamePattern(normalizedPattern, usage);
251 var result = sanitize(resolved);
258 var result = sanitize(resolved);
252
259
253 if (result.isEmpty())
260 if (result.isEmpty())
254 throw new InvalidUserDataException("sourceSetNamePattern '" + pattern + "' resolved to empty source set name");
261 throw new InvalidUserDataException("sourceSetNamePattern '" + pattern + "' resolved to empty source set name");
255
262
256 return result;
263 return result;
257 }
264 }
258
265
259 private static String resolveSourceSetNamePattern(String pattern, LayerUsage usage) {
266 private static String resolveSourceSetNamePattern(String pattern, LayerUsage usage) {
260 var matcher = SOURCE_SET_NAME_TOKEN.matcher(pattern);
267 var matcher = SOURCE_SET_NAME_TOKEN.matcher(pattern);
261 var output = new StringBuffer();
268 var output = new StringBuffer();
262
269
263 while (matcher.find()) {
270 while (matcher.find()) {
264 var token = matcher.group(1);
271 var token = matcher.group(1);
265 matcher.appendReplacement(output, Matcher.quoteReplacement(tokenValue(token, usage)));
272 matcher.appendReplacement(output, Matcher.quoteReplacement(tokenValue(token, usage)));
266 }
273 }
267 matcher.appendTail(output);
274 matcher.appendTail(output);
268
275
269 return output.toString();
276 return output.toString();
270 }
277 }
271
278
272 private static String tokenValue(String token, LayerUsage usage) {
279 private static String tokenValue(String token, LayerUsage usage) {
273 return switch (token) {
280 return switch (token) {
274 case "variant" -> sanitize(usage.variantName());
281 case "variant" -> sanitize(usage.variantName());
275 case "variantCap" -> Strings.capitalize(sanitize(usage.variantName()));
282 case "variantCap" -> Strings.capitalize(sanitize(usage.variantName()));
276 case "role" -> sanitize(usage.roleName());
283 case "role" -> sanitize(usage.roleName());
277 case "roleCap" -> Strings.capitalize(sanitize(usage.roleName()));
284 case "roleCap" -> Strings.capitalize(sanitize(usage.roleName()));
278 case "layer" -> sanitize(usage.layerName());
285 case "layer" -> sanitize(usage.layerName());
279 case "layerCap" -> Strings.capitalize(sanitize(usage.layerName()));
286 case "layerCap" -> Strings.capitalize(sanitize(usage.layerName()));
280 default -> throw new InvalidUserDataException(
287 default -> throw new InvalidUserDataException(
281 "sourceSetNamePattern contains unsupported token '{" + token + "}'");
288 "sourceSetNamePattern contains unsupported token '{" + token + "}'");
282 };
289 };
283 }
290 }
284
291
285 private static String sanitize(String value) {
292 private static String sanitize(String value) {
286 return INVALID_NAME_CHAR.matcher(value).replaceAll("_");
293 return INVALID_NAME_CHAR.matcher(value).replaceAll("_");
287 }
294 }
288
295
289 private static String normalize(String value) {
296 private static String normalize(String value) {
290 return normalize(value, "Value must not be null or blank");
297 return normalize(value, "Value must not be null or blank");
291 }
298 }
292
299
293 private static String normalize(String value, String errorMessage) {
300 private static String normalize(String value, String errorMessage) {
294 if (value == null)
301 if (value == null)
295 throw new InvalidUserDataException(errorMessage);
302 throw new InvalidUserDataException(errorMessage);
296 var trimmed = value.trim();
303 var trimmed = value.trim();
297 if (trimmed.isEmpty())
304 if (trimmed.isEmpty())
298 throw new InvalidUserDataException(errorMessage);
305 throw new InvalidUserDataException(errorMessage);
299 return trimmed;
306 return trimmed;
300 }
307 }
301
308
302 private record LayerUsage(String variantName, String roleName, String layerName) {
309 private record LayerUsage(String variantName, String roleName, String layerName) {
303 }
310 }
304 }
311 }
@@ -1,311 +1,330
1 package org.implab.gradle.common.sources;
1 package org.implab.gradle.common.sources;
2
2
3 import static org.junit.jupiter.api.Assertions.assertNotNull;
3 import static org.junit.jupiter.api.Assertions.assertNotNull;
4 import static org.junit.jupiter.api.Assertions.assertThrows;
4 import static org.junit.jupiter.api.Assertions.assertThrows;
5 import static org.junit.jupiter.api.Assertions.assertTrue;
5 import static org.junit.jupiter.api.Assertions.assertTrue;
6
6
7 import java.io.File;
7 import java.io.File;
8 import java.io.IOException;
8 import java.io.IOException;
9 import java.nio.file.Files;
9 import java.nio.file.Files;
10 import java.nio.file.Path;
10 import java.nio.file.Path;
11 import java.util.List;
11 import java.util.List;
12
12
13 import org.gradle.testkit.runner.BuildResult;
13 import org.gradle.testkit.runner.BuildResult;
14 import org.gradle.testkit.runner.GradleRunner;
14 import org.gradle.testkit.runner.GradleRunner;
15 import org.gradle.testkit.runner.TaskOutcome;
15 import org.gradle.testkit.runner.TaskOutcome;
16 import org.gradle.testkit.runner.UnexpectedBuildFailure;
16 import org.gradle.testkit.runner.UnexpectedBuildFailure;
17 import org.junit.jupiter.api.Test;
17 import org.junit.jupiter.api.Test;
18 import org.junit.jupiter.api.io.TempDir;
18 import org.junit.jupiter.api.io.TempDir;
19
19
20 class VariantsPluginFunctionalTest {
20 class VariantsPluginFunctionalTest {
21 private static final String SETTINGS_FILE = "settings.gradle";
21 private static final String SETTINGS_FILE = "settings.gradle";
22 private static final String BUILD_FILE = "build.gradle";
22 private static final String BUILD_FILE = "build.gradle";
23 private static final String ROOT_NAME = "rootProject.name = 'variants-fixture'\n";
23 private static final String ROOT_NAME = "rootProject.name = 'variants-fixture'\n";
24
24
25 @TempDir
25 @TempDir
26 Path testProjectDir;
26 Path testProjectDir;
27
27
28 @Test
28 @Test
29 void configuresVariantModelWithDsl() throws Exception {
29 void configuresVariantModelWithDsl() 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 {
33 id 'org.implab.gradle-variants'
33 id 'org.implab.gradle-variants'
34 }
34 }
35
35
36 variants {
36 variants {
37 layer('mainBase') {
37 layer('mainBase') {
38 }
38 }
39
39
40 layer('mainAmd') {
40 layer('mainAmd') {
41 }
41 }
42
42
43 variant('browser') {
43 variant('browser') {
44 attributes {
44 attributes {
45 string('jsRuntime', 'browser')
45 string('jsRuntime', 'browser')
46 string('jsModule', 'amd')
46 string('jsModule', 'amd')
47 }
47 }
48 role('main') {
48 role('main') {
49 layers('mainBase', 'mainAmd')
49 layers('mainBase', 'mainAmd')
50 }
50 }
51 link('mainBase', 'mainAmd', 'ts:api')
51 link('mainBase', 'mainAmd', 'ts:api')
52 artifactSlot('mainCompiled')
52 artifactSlot('mainCompiled')
53 }
53 }
54 }
54 }
55
55
56 tasks.register('probe') {
56 tasks.register('probe') {
57 doLast {
57 doLast {
58 def browser = variants.getByName('browser')
58 def browser = variants.getByName('browser')
59 println('attributes=' + browser.attributes.size())
59 println('attributes=' + browser.attributes.size())
60 println('roles=' + browser.roles.size())
60 println('roles=' + browser.roles.size())
61 println('links=' + browser.links.size())
61 println('links=' + browser.links.size())
62 println('slots=' + browser.artifactSlots.size())
62 println('slots=' + browser.artifactSlots.size())
63 }
63 }
64 }
64 }
65 """);
65 """);
66
66
67 BuildResult result = runner("probe").build();
67 BuildResult result = runner("probe").build();
68
68
69 assertTrue(result.getOutput().contains("attributes=2"));
69 assertTrue(result.getOutput().contains("attributes=2"));
70 assertTrue(result.getOutput().contains("roles=1"));
70 assertTrue(result.getOutput().contains("roles=1"));
71 assertTrue(result.getOutput().contains("links=1"));
71 assertTrue(result.getOutput().contains("links=1"));
72 assertTrue(result.getOutput().contains("slots=1"));
72 assertTrue(result.getOutput().contains("slots=1"));
73 assertTrue(result.task(":probe").getOutcome() == TaskOutcome.SUCCESS);
73 assertTrue(result.task(":probe").getOutcome() == TaskOutcome.SUCCESS);
74 }
74 }
75
75
76 @Test
76 @Test
77 void failsOnUnknownLayerReference() throws Exception {
77 void failsOnUnknownLayerReference() throws Exception {
78 assertBuildFails("""
78 assertBuildFails("""
79 plugins {
79 plugins {
80 id 'org.implab.gradle-variants'
80 id 'org.implab.gradle-variants'
81 }
81 }
82
82
83 variants {
83 variants {
84 layer('mainBase') {
84 layer('mainBase') {
85 }
85 }
86
86
87 variant('browser') {
87 variant('browser') {
88 role('main') {
88 role('main') {
89 layers('mainBase', 'missingLayer')
89 layers('mainBase', 'missingLayer')
90 }
90 }
91 }
91 }
92 }
92 }
93 """, "references unknown layer 'missingLayer'");
93 """, "references unknown layer 'missingLayer'");
94 }
94 }
95
95
96 @Test
96 @Test
97 void failsOnCycleInLinksByKind() throws Exception {
97 void failsOnCycleInLinksByKind() throws Exception {
98 assertBuildFails("""
98 assertBuildFails("""
99 plugins {
99 plugins {
100 id 'org.implab.gradle-variants'
100 id 'org.implab.gradle-variants'
101 }
101 }
102
102
103 variants {
103 variants {
104 layer('a')
104 layer('a')
105 layer('b')
105 layer('b')
106
106
107 variant('browser') {
107 variant('browser') {
108 role('main') {
108 role('main') {
109 layers('a', 'b')
109 layers('a', 'b')
110 }
110 }
111 link('a', 'b', 'ts:api')
111 link('a', 'b', 'ts:api')
112 link('b', 'a', 'ts:api')
112 link('b', 'a', 'ts:api')
113 }
113 }
114 }
114 }
115 """, "contains cycle in links with kind 'ts:api'");
115 """, "contains cycle in links with kind 'ts:api'");
116 }
116 }
117
117
118 @Test
118 @Test
119 void allowsUsingLayerFromDifferentVariantRole() throws Exception {
119 void allowsUsingLayerFromDifferentVariantRole() throws Exception {
120 writeFile(SETTINGS_FILE, ROOT_NAME);
120 writeFile(SETTINGS_FILE, ROOT_NAME);
121 writeFile(BUILD_FILE, """
121 writeFile(BUILD_FILE, """
122 plugins {
122 plugins {
123 id 'org.implab.gradle-variants'
123 id 'org.implab.gradle-variants'
124 }
124 }
125
125
126 variants {
126 variants {
127 layer('mainBase')
127 layer('mainBase')
128
128
129 variant('browser') {
129 variant('browser') {
130 role('test') {
130 role('test') {
131 layers('mainBase')
131 layers('mainBase')
132 }
132 }
133 }
133 }
134 }
134 }
135 """);
135 """);
136
136
137 BuildResult result = runner("help").build();
137 BuildResult result = runner("help").build();
138 assertTrue(result.getOutput().contains("BUILD SUCCESSFUL"));
138 assertTrue(result.getOutput().contains("BUILD SUCCESSFUL"));
139 }
139 }
140
140
141 @Test
141 @Test
142 void failsOnIncompleteLink() throws Exception {
142 void failsOnIncompleteLink() throws Exception {
143 assertBuildFails("""
143 assertBuildFails("""
144 plugins {
144 plugins {
145 id 'org.implab.gradle-variants'
145 id 'org.implab.gradle-variants'
146 }
146 }
147
147
148 variants {
148 variants {
149 layer('a')
149 layer('a')
150 layer('b')
150 layer('b')
151
151
152 variant('browser') {
152 variant('browser') {
153 role('main') {
153 role('main') {
154 layers('a', 'b')
154 layers('a', 'b')
155 }
155 }
156 link('a', 'b', null)
156 link('a', 'b', null)
157 }
157 }
158 }
158 }
159 """, "has incomplete link (from/to/kind are required)");
159 """, "Link 'kind' must not be null or blank");
160 }
160 }
161
161
162 @Test
162 @Test
163 void failsOnDuplicatedLinkTuple() throws Exception {
163 void failsOnDuplicatedLinkTuple() throws Exception {
164 assertBuildFails("""
164 assertBuildFails("""
165 plugins {
165 plugins {
166 id 'org.implab.gradle-variants'
166 id 'org.implab.gradle-variants'
167 }
167 }
168
168
169 variants {
169 variants {
170 layer('a')
170 layer('a')
171 layer('b')
171 layer('b')
172
172
173 variant('browser') {
173 variant('browser') {
174 role('main') {
174 role('main') {
175 layers('a', 'b')
175 layers('a', 'b')
176 }
176 }
177 link('a', 'b', 'ts:api')
177 link('a', 'b', 'ts:api')
178 link('a', 'b', 'ts:api')
178 link('a', 'b', 'ts:api')
179 }
179 }
180 }
180 }
181 """, "has duplicated link tuple (from='a', to='b', kind='ts:api')");
181 """, "has duplicated link tuple (from='a', to='b', kind='ts:api')");
182 }
182 }
183
183
184 @Test
184 @Test
185 void failsOnUnknownSourceLayerInLink() throws Exception {
185 void failsOnUnknownSourceLayerInLink() throws Exception {
186 assertBuildFails("""
186 assertBuildFails("""
187 plugins {
187 plugins {
188 id 'org.implab.gradle-variants'
188 id 'org.implab.gradle-variants'
189 }
189 }
190
190
191 variants {
191 variants {
192 layer('a') {
192 layer('a') {
193 }
193 }
194
194
195 variant('browser') {
195 variant('browser') {
196 role('main') {
196 role('main') {
197 layers('a')
197 layers('a')
198 }
198 }
199 link('missing', 'a', 'ts:api')
199 link('missing', 'a', 'ts:api')
200 }
200 }
201 }
201 }
202 """, "references unknown source layer 'missing'");
202 """, "references unknown source layer 'missing'");
203 }
203 }
204
204
205 @Test
205 @Test
206 void failsOnUnknownTargetLayerInLink() throws Exception {
206 void failsOnUnknownTargetLayerInLink() throws Exception {
207 assertBuildFails("""
207 assertBuildFails("""
208 plugins {
208 plugins {
209 id 'org.implab.gradle-variants'
209 id 'org.implab.gradle-variants'
210 }
210 }
211
211
212 variants {
212 variants {
213 layer('a') {
213 layer('a') {
214 }
214 }
215
215
216 variant('browser') {
216 variant('browser') {
217 role('main') {
217 role('main') {
218 layers('a')
218 layers('a')
219 }
219 }
220 link('a', 'missing', 'ts:api')
220 link('a', 'missing', 'ts:api')
221 }
221 }
222 }
222 }
223 """, "references unknown target layer 'missing'");
223 """, "references unknown target layer 'missing'");
224 }
224 }
225
225
226 @Test
226 @Test
227 void failsOnDuplicatedLayerReferenceInRole() throws Exception {
228 assertBuildFails("""
229 plugins {
230 id 'org.implab.gradle-variants'
231 }
232
233 variants {
234 layer('a')
235
236 variant('browser') {
237 role('main') {
238 layers('a', 'a')
239 }
240 }
241 }
242 """, "contains duplicated layer reference 'a'");
243 }
244
245 @Test
227 void failsOnLateLayerMutationAfterFinalize() throws Exception {
246 void failsOnLateLayerMutationAfterFinalize() throws Exception {
228 assertBuildFails("""
247 assertBuildFails("""
229 plugins {
248 plugins {
230 id 'org.implab.gradle-variants'
249 id 'org.implab.gradle-variants'
231 }
250 }
232
251
233 variants {
252 variants {
234 layer('a')
253 layer('a')
235 variant('browser') {
254 variant('browser') {
236 role('main') { layers('a') }
255 role('main') { layers('a') }
237 }
256 }
238 }
257 }
239
258
240 afterEvaluate {
259 afterEvaluate {
241 variants.layer('late')
260 variants.layer('late')
242 }
261 }
243 """, "Variants model is finalized and cannot configure layers");
262 """, "Variants model is finalized and cannot configure layers");
244 }
263 }
245
264
246 @Test
265 @Test
247 void failsOnLateVariantMutationAfterFinalize() throws Exception {
266 void failsOnLateVariantMutationAfterFinalize() throws Exception {
248 assertBuildFails("""
267 assertBuildFails("""
249 plugins {
268 plugins {
250 id 'org.implab.gradle-variants'
269 id 'org.implab.gradle-variants'
251 }
270 }
252
271
253 variants {
272 variants {
254 layer('a')
273 layer('a')
255 variant('browser') {
274 variant('browser') {
256 role('main') { layers('a') }
275 role('main') { layers('a') }
257 }
276 }
258 }
277 }
259
278
260 afterEvaluate {
279 afterEvaluate {
261 variants.getByName('browser').role('late') { layers('a') }
280 variants.getByName('browser').role('late') { layers('a') }
262 }
281 }
263 """, "Variant 'browser' is finalized and cannot configure roles");
282 """, "Variant 'browser' is finalized and cannot configure roles");
264 }
283 }
265
284
266 private GradleRunner runner(String... arguments) {
285 private GradleRunner runner(String... arguments) {
267 return GradleRunner.create()
286 return GradleRunner.create()
268 .withProjectDir(testProjectDir.toFile())
287 .withProjectDir(testProjectDir.toFile())
269 .withPluginClasspath(pluginClasspath())
288 .withPluginClasspath(pluginClasspath())
270 .withArguments(arguments)
289 .withArguments(arguments)
271 .forwardOutput();
290 .forwardOutput();
272 }
291 }
273
292
274 private void assertBuildFails(String buildScript, String expectedError) throws Exception {
293 private void assertBuildFails(String buildScript, String expectedError) throws Exception {
275 writeFile(SETTINGS_FILE, ROOT_NAME);
294 writeFile(SETTINGS_FILE, ROOT_NAME);
276 writeFile(BUILD_FILE, buildScript);
295 writeFile(BUILD_FILE, buildScript);
277
296
278 var ex = assertThrows(UnexpectedBuildFailure.class, () -> runner("help").build());
297 var ex = assertThrows(UnexpectedBuildFailure.class, () -> runner("help").build());
279 var output = ex.getBuildResult().getOutput();
298 var output = ex.getBuildResult().getOutput();
280
299
281 assertTrue(output.contains(expectedError), () -> "Expected [" + expectedError + "] in output:\n" + output);
300 assertTrue(output.contains(expectedError), () -> "Expected [" + expectedError + "] in output:\n" + output);
282 }
301 }
283
302
284 private static List<File> pluginClasspath() {
303 private static List<File> pluginClasspath() {
285 try {
304 try {
286 var classesDir = Path.of(BuildVariant.class
305 var classesDir = Path.of(BuildVariant.class
287 .getProtectionDomain()
306 .getProtectionDomain()
288 .getCodeSource()
307 .getCodeSource()
289 .getLocation()
308 .getLocation()
290 .toURI());
309 .toURI());
291
310
292 var markerResource = VariantsPlugin.class.getClassLoader()
311 var markerResource = VariantsPlugin.class.getClassLoader()
293 .getResource("META-INF/gradle-plugins/org.implab.gradle-variants.properties");
312 .getResource("META-INF/gradle-plugins/org.implab.gradle-variants.properties");
294
313
295 assertNotNull(markerResource, "Plugin marker resource is missing from test classpath");
314 assertNotNull(markerResource, "Plugin marker resource is missing from test classpath");
296
315
297 var markerPath = Path.of(markerResource.toURI());
316 var markerPath = Path.of(markerResource.toURI());
298 var resourcesDir = markerPath.getParent().getParent().getParent();
317 var resourcesDir = markerPath.getParent().getParent().getParent();
299
318
300 return List.of(classesDir.toFile(), resourcesDir.toFile());
319 return List.of(classesDir.toFile(), resourcesDir.toFile());
301 } catch (Exception e) {
320 } catch (Exception e) {
302 throw new RuntimeException("Unable to build plugin classpath for test", e);
321 throw new RuntimeException("Unable to build plugin classpath for test", e);
303 }
322 }
304 }
323 }
305
324
306 private void writeFile(String relativePath, String content) throws IOException {
325 private void writeFile(String relativePath, String content) throws IOException {
307 Path path = testProjectDir.resolve(relativePath);
326 Path path = testProjectDir.resolve(relativePath);
308 Files.createDirectories(path.getParent());
327 Files.createDirectories(path.getParent());
309 Files.writeString(path, content);
328 Files.writeString(path, content);
310 }
329 }
311 }
330 }
General Comments 0
You need to be logged in to leave comments. Login now