##// END OF EJS Templates
Refactor variantArtifacts to variant-level publications with primary and secondary slots
cin -
r34:5ec65d9e5a34 default
parent child
Show More
@@ -0,0 +1,66
1 package org.implab.gradle.common.sources;
2
3 import org.eclipse.jdt.annotation.NonNullByDefault;
4 import org.gradle.api.Action;
5 import org.gradle.api.attributes.AttributeContainer;
6 import org.gradle.api.attributes.HasConfigurableAttributes;
7 import org.implab.gradle.common.core.lang.Closures;
8
9 import groovy.lang.Closure;
10 import groovy.lang.DelegatesTo;
11
12 @NonNullByDefault
13 public final class OutgoingArtifactSlotPublication {
14 private final String slotName;
15 private final boolean primary;
16 private final VariantArtifactSlot slot;
17 private final ArtifactAssembly assembly;
18 private final HasConfigurableAttributes<?> attributesCarrier;
19
20 OutgoingArtifactSlotPublication(
21 String slotName,
22 boolean primary,
23 VariantArtifactSlot slot,
24 ArtifactAssembly assembly,
25 HasConfigurableAttributes<?> attributesCarrier) {
26 this.slotName = slotName;
27 this.primary = primary;
28 this.slot = slot;
29 this.assembly = assembly;
30 this.attributesCarrier = attributesCarrier;
31 }
32
33 public String slotName() {
34 return slotName;
35 }
36
37 public boolean primary() {
38 return primary;
39 }
40
41 public VariantArtifactSlot slot() {
42 return slot;
43 }
44
45 public ArtifactAssembly assembly() {
46 return assembly;
47 }
48
49 public void configureAssembly(Action<? super ArtifactAssembly> action) {
50 action.execute(assembly);
51 }
52
53 public void configureAssembly(
54 @DelegatesTo(value = ArtifactAssembly.class, strategy = Closure.DELEGATE_FIRST) Closure<?> action) {
55 configureAssembly(Closures.action(action));
56 }
57
58 public void configureArtifactAttributes(Action<? super AttributeContainer> action) {
59 attributesCarrier.attributes(action);
60 }
61
62 public void configureArtifactAttributes(
63 @DelegatesTo(value = AttributeContainer.class, strategy = Closure.DELEGATE_FIRST) Closure<?> action) {
64 configureArtifactAttributes(Closures.action(action));
65 }
66 }
@@ -0,0 +1,311
1 # Variant Artifacts Plugin
2
3 ## NAME
4
5 `VariantsArtifactsPlugin` ΠΈ extension `variantArtifacts`.
6
7 ## SYNOPSIS
8
9 ```groovy
10 import org.gradle.api.attributes.Attribute
11
12 plugins {
13 id 'org.implab.gradle-variants-artifacts'
14 }
15
16 def variantAttr = Attribute.of('test.variant', String)
17 def slotAttr = Attribute.of('test.slot', String)
18
19 variants {
20 layer('main')
21
22 variant('browser') {
23 role('main') { layers('main') }
24 }
25 }
26
27 variantSources {
28 bind('main') {
29 configureSourceSet {
30 declareOutputs('types', 'js', 'resources')
31 }
32 }
33 }
34
35 variantArtifacts {
36 variant('browser') {
37 primarySlot('typesPackage') {
38 fromVariant {
39 output('types')
40 }
41 }
42
43 slot('js') {
44 fromVariant {
45 output('js')
46 }
47 }
48
49 slot('resources') {
50 fromVariant {
51 output('resources')
52 }
53 }
54 }
55
56 whenOutgoingVariant { publication ->
57 publication.configureConfiguration {
58 attributes.attribute(variantAttr, publication.variantName())
59 }
60
61 publication.primarySlot().configureArtifactAttributes {
62 attribute(slotAttr, publication.primarySlot().slotName())
63 }
64
65 publication.requireSlot('js').configureArtifactAttributes {
66 attribute(slotAttr, 'js')
67 }
68
69 publication.requireSlot('resources').configureArtifactAttributes {
70 attribute(slotAttr, 'resources')
71 }
72 }
73 }
74 ```
75
76 ## DESCRIPTION
77
78 `VariantsArtifactsPlugin` примСняСт `VariantsSourcesPlugin`, Π·Π°Ρ‚Π΅ΠΌ строит
79 outgoing publication model ΠΏΠΎΠ²Π΅Ρ€Ρ… `variantSources`.
80
81 ### publication model
82
83 Для ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ `variantArtifacts.variant('<name>')` публикуСтся ΠΎΠ΄ΠΈΠ½ outgoing
84 build variant:
85
86 - primary configuration `<variant>Elements`;
87 - primary artifact slot на самой configuration;
88 - secondary variants Π²Π½ΡƒΡ‚Ρ€ΠΈ `configuration.outgoing.variants` для ΠΎΡΡ‚Π°Π»ΡŒΠ½Ρ‹Ρ… slots.
89
90 ΠŸΡ€ΠΈΠΌΠ΅Ρ€:
91
92 - `browserElements`
93 - primary slot: `typesPackage`
94 - secondary variants: `js`, `resources`
95
96 Π­Ρ‚ΠΎ раздСляСт:
97
98 - graph selection build variant-Π°;
99 - artifact selection Π²Π½ΡƒΡ‚Ρ€ΠΈ ΡƒΠΆΠ΅ Π²Ρ‹Π±Ρ€Π°Π½Π½ΠΎΠ³ΠΎ variant-Π°.
100
101 ### slot bindings
102
103 `slot('<name>')` описываСт, ΠΊΠ°ΠΊΠΈΠ΅ outputs ΠΈΠ· `variantSources` Π²ΠΎΠΉΠ΄ΡƒΡ‚ Π² artifact
104 representation этого slot-Π°.
105
106 Binding rules:
107
108 - `fromVariant { output(...) }`
109 - `fromRole('<role>') { output(...) }`
110 - `fromLayer('<layer>') { output(...) }`
111
112 ΠšΠ°ΠΆΠ΄Ρ‹ΠΉ slot materialize-ится Π² ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½Ρ‹ΠΉ `ArtifactAssembly`:
113
114 - task: `process<Variant><Slot>`;
115 - output dir: `build/variant-artifacts/<variant>/<slot>`.
116
117 ### primary slot
118
119 Primary slot Π·Π°Π΄Π°Π΅Ρ‚ artifact, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ публикуСтся ΠΊΠ°ΠΊ основной artifact
120 configuration `<variant>Elements`.
121
122 Π€ΠΎΡ€ΠΌΡ‹ DSL:
123
124 ```groovy
125 variant('browser') {
126 primarySlot('typesPackage')
127
128 slot('typesPackage') {
129 fromVariant { output('types') }
130 }
131 }
132 ```
133
134 ΠΈΠ»ΠΈ sugar:
135
136 ```groovy
137 variant('browser') {
138 primarySlot('typesPackage') {
139 fromVariant { output('types') }
140 }
141 }
142 ```
143
144 ΠŸΡ€Π°Π²ΠΈΠ»Π°:
145
146 - Ссли slot ΠΎΠ΄ΠΈΠ½, ΠΎΠ½ считаСтся primary нСявно;
147 - Ссли slots нСсколько, `primarySlot(...)` обязатСлСн;
148 - `primarySlot` Π΄ΠΎΠ»ΠΆΠ΅Π½ ΡΡΡ‹Π»Π°Ρ‚ΡŒΡΡ Π½Π° ΡΡƒΡ‰Π΅ΡΡ‚Π²ΡƒΡŽΡ‰ΠΈΠΉ slot.
149
150 ## LIFECYCLE
151
152 - `VariantsArtifactsPlugin` ΠΆΠ΄Π΅Ρ‚ `variants.whenFinalized(...)`;
153 - послС этого Π²Π°Π»ΠΈΠ΄ΠΈΡ€ΡƒΠ΅Ρ‚ `variantArtifacts`;
154 - рСгистрируСт `ArtifactAssembly` ΠΏΠΎ ΠΊΠ°ΠΆΠ΄ΠΎΠΌΡƒ slot;
155 - materialize-ΠΈΡ‚ outgoing publications;
156 - Π²Ρ‹Π·Ρ‹Π²Π°Π΅Ρ‚ `whenOutgoingVariant(...)`;
157 - callbacks replayable.
158
159 ПослС finalize ΠΌΡƒΡ‚Π°Ρ†ΠΈΠΈ `variantArtifacts` Π·Π°ΠΏΡ€Π΅Ρ‰Π΅Π½Ρ‹.
160
161 ## EVENTS
162
163 ### whenOutgoingVariant
164
165 Replayable callback Π½Π° Π³ΠΎΡ‚ΠΎΠ²ΡƒΡŽ outgoing publication variant-Π°.
166
167 ΠŸΠΎΠ΄Ρ…ΠΎΠ΄ΠΈΡ‚ для:
168
169 - настройки ΠΎΠ±Ρ‰ΠΈΡ… attributes build variant-Π° ΠΎΠ΄ΠΈΠ½ Ρ€Π°Π·;
170 - настройки per-slot artifact attributes;
171 - Π΄ΠΎΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ `ArtifactAssembly`.
172
173 ## PAYLOAD TYPES
174
175 ### OutgoingVariantPublication
176
177 Π‘ΠΎΠ΄Π΅Ρ€ΠΆΠΈΡ‚:
178
179 - `variantName()`;
180 - `topologyVariant()`;
181 - `variantArtifact()`;
182 - `configuration()` β€” primary `<variant>Elements`;
183 - `primarySlot()`;
184 - `slots()` β€” всС slot publications;
185 - `secondarySlots()`;
186 - `findSlot(name)`, `requireSlot(name)`.
187
188 Sugar:
189
190 - `configureConfiguration(Action|Closure)`.
191
192 ### OutgoingArtifactSlotPublication
193
194 Π‘ΠΎΠ΄Π΅Ρ€ΠΆΠΈΡ‚:
195
196 - `slotName()`;
197 - `primary()`;
198 - `slot()` β€” модСль `VariantArtifactSlot`;
199 - `assembly()`.
200
201 Sugar:
202
203 - `configureAssembly(Action|Closure)`;
204 - `configureArtifactAttributes(Action|Closure)`.
205
206 `configureArtifactAttributes(...)` ΠΏΠΈΡˆΠ΅Ρ‚ attributes:
207
208 - в `Configuration.attributes` для primary slot;
209 - в `ConfigurationVariant.attributes` для secondary slot.
210
211 ## CONSUMER SIDE
212
213 ### primary resolution
214
215 ΠžΠ±Ρ‹Ρ‡Π½ΠΎΠ΅ inter-project resolution Π²Ρ‹Π±ΠΈΡ€Π°Π΅Ρ‚ primary artifact `<variant>Elements`.
216
217 ΠŸΡ€ΠΈΠΌΠ΅Ρ€:
218
219 ```groovy
220 configurations {
221 compileView {
222 canBeResolved = true
223 canBeConsumed = false
224 canBeDeclared = true
225 attributes {
226 attribute(variantAttr, 'browser')
227 attribute(slotAttr, 'typesPackage')
228 }
229 }
230 }
231
232 dependencies {
233 compileView project(':producer')
234 }
235 ```
236
237 ### artifact selection for secondary slots
238
239 Secondary artifacts Π²Ρ‹Π±ΠΈΡ€Π°ΡŽΡ‚ΡΡ Ρ‡Π΅Ρ€Π΅Π· `artifactView`.
240
241 ```groovy
242 def jsFiles = configurations.compileView.incoming.artifactView {
243 attributes {
244 attribute(slotAttr, 'js')
245 }
246 }.files
247 ```
248
249 Π—Π΄Π΅ΡΡŒ graph variant ΡƒΠΆΠ΅ Π²Ρ‹Π±Ρ€Π°Π½, Π° `artifactView` Π²Ρ‹Π±ΠΈΡ€Π°Π΅Ρ‚ Π½ΡƒΠΆΠ½Ρ‹ΠΉ secondary
250 artifact representation.
251
252 ## VALIDATION
253
254 ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡΠ΅Ρ‚ΡΡ:
255
256 - variant сущСствуСт Π² topology model;
257 - slot bindings Π½Π΅ ΡΡΡ‹Π»Π°ΡŽΡ‚ΡΡ Π½Π° нСизвСстныС role/layer;
258 - ΠΏΡ€ΠΈ Π½Π΅ΡΠΊΠΎΠ»ΡŒΠΊΠΈΡ… slots ΡƒΠΊΠ°Π·Π°Π½ `primarySlot`;
259 - `primarySlot` ссылаСтся Π½Π° ΡΡƒΡ‰Π΅ΡΡ‚Π²ΡƒΡŽΡ‰ΠΈΠΉ slot.
260
261 ## API
262
263 ### VariantArtifactsExtension
264
265 - `variant(String)` β€” ΠΏΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ/ΡΠΎΠ·Π΄Π°Ρ‚ΡŒ variant artifact model;
266 - `variant(String, Action|Closure)` β€” ΡΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ variant artifact;
267 - `getVariants()` β€” ΠΊΠΎΠ½Ρ‚Π΅ΠΉΠ½Π΅Ρ€ variant artifacts;
268 - `findVariant(name)`, `requireVariant(name)`;
269 - `whenOutgoingVariant(...)`.
270
271 ### VariantArtifact
272
273 - `slot(String)` β€” ΠΏΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ/ΡΠΎΠ·Π΄Π°Ρ‚ΡŒ slot;
274 - `slot(String, Action|Closure)` β€” ΡΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ slot;
275 - `primarySlot(String)` β€” Π½Π°Π·Π½Π°Ρ‡ΠΈΡ‚ΡŒ primary slot;
276 - `primarySlot(String, Action|Closure)` β€” sugar: configure slot + mark as primary;
277 - `getSlots()`;
278 - `findSlot(name)`, `requireSlot(name)`;
279 - `findPrimarySlotName()`, `requirePrimarySlotName()`;
280 - `findPrimarySlot()`, `requirePrimarySlot()`.
281
282 ### VariantArtifactSlot
283
284 - `fromVariant(...)`;
285 - `fromRole(String, ...)`;
286 - `fromLayer(String, ...)`.
287
288 ### OutputSelectionSpec
289
290 - `output(name)`;
291 - `output(name, extra...)`.
292
293 ## KEY CLASSES
294
295 - `VariantsArtifactsPlugin` β€” plugin adapter ΠΈ materialization outgoing variants.
296 - `VariantArtifactsExtension` β€” root DSL ΠΈ lifecycle.
297 - `VariantArtifact` β€” outgoing build variant model.
298 - `VariantArtifactSlot` β€” artifact representation slot.
299 - `OutgoingVariantPublication` β€” payload variant-level publication callback.
300 - `OutgoingArtifactSlotPublication` β€” payload per-slot publication callback.
301 - `ArtifactAssembly` β€” assembled files for a slot.
302
303 ## NOTES
304
305 - `common` Π½Π΅ навязываСт Π΄ΠΎΠΌΠ΅Π½Π½ΡƒΡŽ Π»ΠΎΠ³ΠΈΠΊΡƒ Π²Ρ‹Π±ΠΎΡ€Π° primary slot.
306 - `common` Π½Π΅ фиксируСт значСния `usage`, `libraryelements` ΠΈ ΠΏΡ€ΠΎΡ‡ΠΈΡ…
307 slot-specific attributes.
308 - `common` Π½Π΅ ΡΠΌΠ΅ΡˆΠΈΠ²Π°Π΅Ρ‚ эту модСль с ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½Ρ‹ΠΌΠΈ publish осями Π²Ρ€ΠΎΠ΄Π΅ package
309 metadata.
310 - Closure callbacks ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΡŽΡ‚ delegate-first; для Π²Π»ΠΎΠΆΠ΅Π½Π½Ρ‹Ρ… closure ΡƒΠ΄ΠΎΠ±Π½Π΅Π΅
311 явный ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ (`publication -> ...`, `slotPublication -> ...`).
@@ -1,38 +1,89
1 package org.implab.gradle.common.sources;
1 package org.implab.gradle.common.sources;
2
2
3 import org.eclipse.jdt.annotation.NonNullByDefault;
3 import org.eclipse.jdt.annotation.NonNullByDefault;
4 import org.implab.gradle.common.core.lang.Closures;
4 import org.implab.gradle.common.core.lang.Closures;
5 import org.gradle.api.Action;
5 import org.gradle.api.Action;
6 import org.gradle.api.NamedDomainObjectProvider;
7 import org.gradle.api.artifacts.Configuration;
6 import org.gradle.api.artifacts.Configuration;
7 import org.gradle.api.InvalidUserDataException;
8
8
9 import groovy.lang.Closure;
9 import groovy.lang.Closure;
10 import groovy.lang.DelegatesTo;
10 import groovy.lang.DelegatesTo;
11
11
12 @NonNullByDefault
12 @NonNullByDefault
13 public record OutgoingVariantPublication(
13 public final class OutgoingVariantPublication {
14 String variantName,
14 private final String variantName;
15 String slotName,
15 private final BuildVariant topologyVariant;
16 BuildVariant topologyVariant,
16 private final VariantArtifact variantArtifact;
17 VariantArtifact variantArtifact,
17 private final Configuration configuration;
18 VariantArtifactSlot slot,
18 private final OutgoingArtifactSlotPublication primarySlot;
19 ArtifactAssembly assembly,
19 private final java.util.List<OutgoingArtifactSlotPublication> slots;
20 NamedDomainObjectProvider<? extends Configuration> configuration) {
20
21 public OutgoingVariantPublication(
22 String variantName,
23 BuildVariant topologyVariant,
24 VariantArtifact variantArtifact,
25 Configuration configuration,
26 OutgoingArtifactSlotPublication primarySlot,
27 java.util.List<OutgoingArtifactSlotPublication> slots) {
28 this.variantName = variantName;
29 this.topologyVariant = topologyVariant;
30 this.variantArtifact = variantArtifact;
31 this.configuration = configuration;
32 this.primarySlot = primarySlot;
33 this.slots = java.util.List.copyOf(slots);
34 }
35
36 public String variantName() {
37 return variantName;
38 }
39
40 public BuildVariant topologyVariant() {
41 return topologyVariant;
42 }
43
44 public VariantArtifact variantArtifact() {
45 return variantArtifact;
46 }
47
48 public Configuration configuration() {
49 return configuration;
50 }
51
52 public OutgoingArtifactSlotPublication primarySlot() {
53 return primarySlot;
54 }
55
56 public java.util.List<OutgoingArtifactSlotPublication> slots() {
57 return slots;
58 }
59
60 public java.util.List<OutgoingArtifactSlotPublication> secondarySlots() {
61 return slots.stream()
62 .filter(slotPublication -> !slotPublication.primary())
63 .toList();
64 }
65
66 public java.util.Optional<OutgoingArtifactSlotPublication> findSlot(String slotName) {
67 var normalizedSlotName = VariantArtifact.normalize(slotName, "slot name must not be null or blank");
68 return slots.stream()
69 .filter(slotPublication -> normalizedSlotName.equals(slotPublication.slotName()))
70 .findFirst();
71 }
72
73 public OutgoingArtifactSlotPublication requireSlot(String slotName) {
74 var normalizedSlotName = VariantArtifact.normalize(slotName, "slot name must not be null or blank");
75 return findSlot(normalizedSlotName)
76 .orElseThrow(() -> new InvalidUserDataException(
77 "Outgoing publication for variant '" + variantName + "' doesn't declare slot '"
78 + normalizedSlotName + "'"));
79 }
80
21 public void configureConfiguration(Action<? super Configuration> action) {
81 public void configureConfiguration(Action<? super Configuration> action) {
22 configuration.configure(action);
82 action.execute(configuration);
23 }
83 }
24
84
25 public void configureConfiguration(
85 public void configureConfiguration(
26 @DelegatesTo(value = Configuration.class, strategy = Closure.DELEGATE_FIRST) Closure<?> action) {
86 @DelegatesTo(value = Configuration.class, strategy = Closure.DELEGATE_FIRST) Closure<?> action) {
27 configureConfiguration(Closures.action(action));
87 configureConfiguration(Closures.action(action));
28 }
88 }
29
30 public void configureAssembly(Action<? super ArtifactAssembly> action) {
31 action.execute(assembly);
32 }
33
34 public void configureAssembly(
35 @DelegatesTo(value = ArtifactAssembly.class, strategy = Closure.DELEGATE_FIRST) Closure<?> action) {
36 configureAssembly(Closures.action(action));
37 }
38 }
89 }
@@ -1,94 +1,135
1 package org.implab.gradle.common.sources;
1 package org.implab.gradle.common.sources;
2
2
3 import java.util.Optional;
3 import java.util.Optional;
4
4
5 import javax.inject.Inject;
5 import javax.inject.Inject;
6
6
7 import org.eclipse.jdt.annotation.NonNullByDefault;
7 import org.eclipse.jdt.annotation.NonNullByDefault;
8 import org.gradle.api.Action;
8 import org.gradle.api.Action;
9 import org.gradle.api.InvalidUserDataException;
9 import org.gradle.api.InvalidUserDataException;
10 import org.gradle.api.Named;
10 import org.gradle.api.Named;
11 import org.gradle.api.NamedDomainObjectContainer;
11 import org.gradle.api.NamedDomainObjectContainer;
12 import org.implab.gradle.common.core.lang.Closures;
12 import org.implab.gradle.common.core.lang.Closures;
13
13
14 import groovy.lang.Closure;
14 import groovy.lang.Closure;
15 import groovy.lang.DelegatesTo;
15 import groovy.lang.DelegatesTo;
16
16
17 @NonNullByDefault
17 @NonNullByDefault
18 public class VariantArtifact implements Named {
18 public class VariantArtifact implements Named {
19 private final String name;
19 private final String name;
20 private final NamedDomainObjectContainer<VariantArtifactSlot> slots;
20 private final NamedDomainObjectContainer<VariantArtifactSlot> slots;
21 private String primarySlotName;
21 private boolean finalized;
22 private boolean finalized;
22
23
23 @Inject
24 @Inject
24 public VariantArtifact(String name, NamedDomainObjectContainer<VariantArtifactSlot> slots) {
25 public VariantArtifact(String name, NamedDomainObjectContainer<VariantArtifactSlot> slots) {
25 this.name = normalize(name, "variant artifact name must not be null or blank");
26 this.name = normalize(name, "variant artifact name must not be null or blank");
26 this.slots = slots;
27 this.slots = slots;
27
28
28 slots.all(slot -> {
29 slots.all(slot -> {
29 if (finalized)
30 if (finalized)
30 throw new InvalidUserDataException(
31 throw new InvalidUserDataException(
31 "Variant artifact '" + this.name + "' is finalized and cannot add slot '" + slot.getName() + "'");
32 "Variant artifact '" + this.name + "' is finalized and cannot add slot '" + slot.getName() + "'");
32 });
33 });
33 }
34 }
34
35
35 @Override
36 @Override
36 public String getName() {
37 public String getName() {
37 return name;
38 return name;
38 }
39 }
39
40
40 public NamedDomainObjectContainer<VariantArtifactSlot> getSlots() {
41 public NamedDomainObjectContainer<VariantArtifactSlot> getSlots() {
41 return slots;
42 return slots;
42 }
43 }
43
44
44 public VariantArtifactSlot slot(String name) {
45 public VariantArtifactSlot slot(String name) {
45 return slot(name, slot -> {
46 return slot(name, slot -> {
46 });
47 });
47 }
48 }
48
49
49 public VariantArtifactSlot slot(String name, Action<? super VariantArtifactSlot> configure) {
50 public VariantArtifactSlot slot(String name, Action<? super VariantArtifactSlot> configure) {
50 ensureMutable("configure slots");
51 ensureMutable("configure slots");
51 var slot = slots.maybeCreate(normalize(name, "slot name must not be null or blank"));
52 var slot = slots.maybeCreate(normalize(name, "slot name must not be null or blank"));
52 configure.execute(slot);
53 configure.execute(slot);
53 return slot;
54 return slot;
54 }
55 }
55
56
56 public VariantArtifactSlot slot(
57 public VariantArtifactSlot slot(
57 String name,
58 String name,
58 @DelegatesTo(value = VariantArtifactSlot.class, strategy = Closure.DELEGATE_FIRST) Closure<?> configure) {
59 @DelegatesTo(value = VariantArtifactSlot.class, strategy = Closure.DELEGATE_FIRST) Closure<?> configure) {
59 return slot(name, Closures.action(configure));
60 return slot(name, Closures.action(configure));
60 }
61 }
61
62
62 public Optional<VariantArtifactSlot> findSlot(String slotName) {
63 public Optional<VariantArtifactSlot> findSlot(String slotName) {
63 return Optional.ofNullable(slots.findByName(normalize(slotName, "slot name must not be null or blank")));
64 return Optional.ofNullable(slots.findByName(normalize(slotName, "slot name must not be null or blank")));
64 }
65 }
65
66
67 public void primarySlot(String slotName) {
68 ensureMutable("configure primary slot");
69 primarySlotName = normalize(slotName, "primary slot name must not be null or blank");
70 }
71
72 public VariantArtifactSlot primarySlot(String slotName, Action<? super VariantArtifactSlot> configure) {
73 ensureMutable("configure primary slot");
74 var slot = slot(slotName, configure);
75 primarySlot(slot.getName());
76 return slot;
77 }
78
79 public VariantArtifactSlot primarySlot(
80 String slotName,
81 @DelegatesTo(value = VariantArtifactSlot.class, strategy = Closure.DELEGATE_FIRST) Closure<?> configure) {
82 return primarySlot(slotName, Closures.action(configure));
83 }
84
85 public Optional<String> findPrimarySlotName() {
86 return Optional.ofNullable(primarySlotName)
87 .or(() -> slots.getNames().size() == 1 ? Optional.of(slots.iterator().next().getName()) : Optional.empty());
88 }
89
90 public String requirePrimarySlotName() {
91 return findPrimarySlotName()
92 .orElseThrow(() -> new InvalidUserDataException(
93 "Variant artifact '" + name + "' must declare primary slot because it has multiple slots"));
94 }
95
96 public Optional<VariantArtifactSlot> findPrimarySlot() {
97 return findPrimarySlotName().flatMap(this::findSlot);
98 }
99
100 public VariantArtifactSlot requirePrimarySlot() {
101 var resolvedPrimarySlotName = requirePrimarySlotName();
102 return findSlot(resolvedPrimarySlotName)
103 .orElseThrow(() -> new InvalidUserDataException(
104 "Variant artifact '" + name + "' declares unknown primary slot '" + resolvedPrimarySlotName + "'"));
105 }
106
66 public VariantArtifactSlot requireSlot(String slotName) {
107 public VariantArtifactSlot requireSlot(String slotName) {
67 var normalizedSlotName = normalize(slotName, "slot name must not be null or blank");
108 var normalizedSlotName = normalize(slotName, "slot name must not be null or blank");
68 return Optional.ofNullable(slots.findByName(normalizedSlotName))
109 return Optional.ofNullable(slots.findByName(normalizedSlotName))
69 .orElseThrow(() -> new InvalidUserDataException(
110 .orElseThrow(() -> new InvalidUserDataException(
70 "Variant artifact '" + name + "' doesn't declare slot '" + normalizedSlotName + "'"));
111 "Variant artifact '" + name + "' doesn't declare slot '" + normalizedSlotName + "'"));
71 }
112 }
72
113
73 void finalizeModel() {
114 void finalizeModel() {
74 if (finalized)
115 if (finalized)
75 return;
116 return;
76
117
77 for (var slot : slots)
118 for (var slot : slots)
78 slot.finalizeModel();
119 slot.finalizeModel();
79
120
80 finalized = true;
121 finalized = true;
81 }
122 }
82
123
83 static String normalize(String value, String message) {
124 static String normalize(String value, String message) {
84 return Optional.ofNullable(value)
125 return Optional.ofNullable(value)
85 .map(String::trim)
126 .map(String::trim)
86 .filter(trimmed -> !trimmed.isEmpty())
127 .filter(trimmed -> !trimmed.isEmpty())
87 .orElseThrow(() -> new InvalidUserDataException(message));
128 .orElseThrow(() -> new InvalidUserDataException(message));
88 }
129 }
89
130
90 private void ensureMutable(String operation) {
131 private void ensureMutable(String operation) {
91 if (finalized)
132 if (finalized)
92 throw new InvalidUserDataException("Variant artifact '" + name + "' is finalized and cannot " + operation);
133 throw new InvalidUserDataException("Variant artifact '" + name + "' is finalized and cannot " + operation);
93 }
134 }
94 }
135 }
@@ -1,169 +1,182
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.LinkedHashSet;
4 import java.util.LinkedHashSet;
5 import java.util.List;
5 import java.util.List;
6 import java.util.Optional;
6 import java.util.Optional;
7
7
8 import javax.inject.Inject;
8 import javax.inject.Inject;
9
9
10 import org.eclipse.jdt.annotation.NonNullByDefault;
10 import org.eclipse.jdt.annotation.NonNullByDefault;
11 import org.gradle.api.Action;
11 import org.gradle.api.Action;
12 import org.gradle.api.InvalidUserDataException;
12 import org.gradle.api.InvalidUserDataException;
13 import org.gradle.api.NamedDomainObjectContainer;
13 import org.gradle.api.NamedDomainObjectContainer;
14 import org.gradle.api.model.ObjectFactory;
14 import org.gradle.api.model.ObjectFactory;
15 import org.implab.gradle.common.core.lang.Closures;
15 import org.implab.gradle.common.core.lang.Closures;
16
16
17 import groovy.lang.Closure;
17 import groovy.lang.Closure;
18 import groovy.lang.DelegatesTo;
18 import groovy.lang.DelegatesTo;
19
19
20 @NonNullByDefault
20 @NonNullByDefault
21 public abstract class VariantArtifactsExtension {
21 public abstract class VariantArtifactsExtension {
22 private final NamedDomainObjectContainer<VariantArtifact> variants;
22 private final NamedDomainObjectContainer<VariantArtifact> variants;
23 private final ObjectFactory objects;
23 private final ObjectFactory objects;
24 private final List<Action<? super OutgoingVariantPublication>> outgoingVariantActions = new ArrayList<>();
24 private final List<Action<? super OutgoingVariantPublication>> outgoingVariantActions = new ArrayList<>();
25 private final List<OutgoingVariantPublication> outgoingVariants = new ArrayList<>();
25 private final List<OutgoingVariantPublication> outgoingVariants = new ArrayList<>();
26 private boolean finalized;
26 private boolean finalized;
27
27
28 @Inject
28 @Inject
29 public VariantArtifactsExtension(ObjectFactory objects) {
29 public VariantArtifactsExtension(ObjectFactory objects) {
30 this.objects = objects;
30 this.objects = objects;
31 variants = objects.domainObjectContainer(VariantArtifact.class, this::newVariantArtifact);
31 variants = objects.domainObjectContainer(VariantArtifact.class, this::newVariantArtifact);
32
32
33 variants.all(variant -> {
33 variants.all(variant -> {
34 if (finalized)
34 if (finalized)
35 throw new InvalidUserDataException(
35 throw new InvalidUserDataException(
36 "variantArtifacts model is finalized and cannot add variant '" + variant.getName() + "'");
36 "variantArtifacts model is finalized and cannot add variant '" + variant.getName() + "'");
37 });
37 });
38 }
38 }
39
39
40 public NamedDomainObjectContainer<VariantArtifact> getVariants() {
40 public NamedDomainObjectContainer<VariantArtifact> getVariants() {
41 return variants;
41 return variants;
42 }
42 }
43
43
44 public VariantArtifact variant(String name) {
44 public VariantArtifact variant(String name) {
45 return variant(name, variant -> {
45 return variant(name, variant -> {
46 });
46 });
47 }
47 }
48
48
49 public VariantArtifact variant(String name, Action<? super VariantArtifact> configure) {
49 public VariantArtifact variant(String name, Action<? super VariantArtifact> configure) {
50 ensureMutable("configure variants");
50 ensureMutable("configure variants");
51 var variant = variants.maybeCreate(VariantArtifact.normalize(name, "variant name must not be null or blank"));
51 var variant = variants.maybeCreate(VariantArtifact.normalize(name, "variant name must not be null or blank"));
52 configure.execute(variant);
52 configure.execute(variant);
53 return variant;
53 return variant;
54 }
54 }
55
55
56 public VariantArtifact variant(
56 public VariantArtifact variant(
57 String name,
57 String name,
58 @DelegatesTo(value = VariantArtifact.class, strategy = Closure.DELEGATE_FIRST) Closure<?> configure) {
58 @DelegatesTo(value = VariantArtifact.class, strategy = Closure.DELEGATE_FIRST) Closure<?> configure) {
59 return variant(name, Closures.action(configure));
59 return variant(name, Closures.action(configure));
60 }
60 }
61
61
62 public Optional<VariantArtifact> findVariant(String variantName) {
62 public Optional<VariantArtifact> findVariant(String variantName) {
63 return Optional
63 return Optional
64 .ofNullable(variants.findByName(VariantArtifact.normalize(variantName, "variant name must not be null or blank")));
64 .ofNullable(variants.findByName(VariantArtifact.normalize(variantName, "variant name must not be null or blank")));
65 }
65 }
66
66
67 public VariantArtifact requireVariant(String variantName) {
67 public VariantArtifact requireVariant(String variantName) {
68 var normalizedVariantName = VariantArtifact.normalize(variantName, "variant name must not be null or blank");
68 var normalizedVariantName = VariantArtifact.normalize(variantName, "variant name must not be null or blank");
69 return findVariant(normalizedVariantName)
69 return findVariant(normalizedVariantName)
70 .orElseThrow(() -> new InvalidUserDataException(
70 .orElseThrow(() -> new InvalidUserDataException(
71 "Variant artifacts do not declare variant '" + normalizedVariantName + "'"));
71 "Variant artifacts do not declare variant '" + normalizedVariantName + "'"));
72 }
72 }
73
73
74 public void whenOutgoingVariant(Action<? super OutgoingVariantPublication> action) {
74 public void whenOutgoingVariant(Action<? super OutgoingVariantPublication> action) {
75 outgoingVariantActions.add(action);
75 outgoingVariantActions.add(action);
76 for (var publication : outgoingVariants)
76 for (var publication : outgoingVariants)
77 action.execute(publication);
77 action.execute(publication);
78 }
78 }
79
79
80 public void whenOutgoingVariant(
80 public void whenOutgoingVariant(
81 @DelegatesTo(value = OutgoingVariantPublication.class, strategy = Closure.DELEGATE_FIRST) Closure<?> action) {
81 @DelegatesTo(value = OutgoingVariantPublication.class, strategy = Closure.DELEGATE_FIRST) Closure<?> action) {
82 whenOutgoingVariant(Closures.action(action));
82 whenOutgoingVariant(Closures.action(action));
83 }
83 }
84
84
85 public boolean isFinalized() {
85 public boolean isFinalized() {
86 return finalized;
86 return finalized;
87 }
87 }
88
88
89 void finalizeModel(BuildVariantsExtension topology) {
89 void finalizeModel(BuildVariantsExtension topology) {
90 if (finalized)
90 if (finalized)
91 return;
91 return;
92
92
93 validate(topology);
93 validate(topology);
94
94
95 for (var variant : variants)
95 for (var variant : variants)
96 variant.finalizeModel();
96 variant.finalizeModel();
97
97
98 finalized = true;
98 finalized = true;
99 }
99 }
100
100
101 void notifyOutgoingVariant(OutgoingVariantPublication publication) {
101 void notifyOutgoingVariant(OutgoingVariantPublication publication) {
102 outgoingVariants.add(publication);
102 outgoingVariants.add(publication);
103 for (var action : outgoingVariantActions)
103 for (var action : outgoingVariantActions)
104 action.execute(publication);
104 action.execute(publication);
105 }
105 }
106
106
107 private VariantArtifact newVariantArtifact(String name) {
107 private VariantArtifact newVariantArtifact(String name) {
108 return objects.newInstance(VariantArtifact.class, name, objects.domainObjectContainer(VariantArtifactSlot.class));
108 return objects.newInstance(VariantArtifact.class, name, objects.domainObjectContainer(VariantArtifactSlot.class));
109 }
109 }
110
110
111 private void validate(BuildVariantsExtension topology) {
111 private void validate(BuildVariantsExtension topology) {
112 var errors = new ArrayList<String>();
112 var errors = new ArrayList<String>();
113
113
114 for (var variantArtifact : variants) {
114 for (var variantArtifact : variants) {
115 var topologyVariant = topology.find(variantArtifact.getName());
115 var topologyVariant = topology.find(variantArtifact.getName());
116 if (topologyVariant.isEmpty()) {
116 if (topologyVariant.isEmpty()) {
117 errors.add("Variant artifact '" + variantArtifact.getName() + "' references unknown variant '"
117 errors.add("Variant artifact '" + variantArtifact.getName() + "' references unknown variant '"
118 + variantArtifact.getName() + "'");
118 + variantArtifact.getName() + "'");
119 continue;
119 continue;
120 }
120 }
121
121
122 validateVariantArtifact(variantArtifact, topologyVariant.get(), errors);
122 validateVariantArtifact(variantArtifact, topologyVariant.get(), errors);
123 }
123 }
124
124
125 if (!errors.isEmpty()) {
125 if (!errors.isEmpty()) {
126 var message = new StringBuilder("Invalid variantArtifacts model:");
126 var message = new StringBuilder("Invalid variantArtifacts model:");
127 for (var error : errors)
127 for (var error : errors)
128 message.append("\n - ").append(error);
128 message.append("\n - ").append(error);
129
129
130 throw new InvalidUserDataException(message.toString());
130 throw new InvalidUserDataException(message.toString());
131 }
131 }
132 }
132 }
133
133
134 private static void validateVariantArtifact(VariantArtifact variantArtifact, BuildVariant topologyVariant, List<String> errors) {
134 private static void validateVariantArtifact(VariantArtifact variantArtifact, BuildVariant topologyVariant, List<String> errors) {
135 var roleNames = new LinkedHashSet<String>();
135 var roleNames = new LinkedHashSet<String>();
136 var layerNames = new LinkedHashSet<String>();
136 var layerNames = new LinkedHashSet<String>();
137
137
138 for (var role : topologyVariant.getRoles()) {
138 for (var role : topologyVariant.getRoles()) {
139 roleNames.add(role.getName());
139 roleNames.add(role.getName());
140 layerNames.addAll(role.getLayers().getOrElse(List.of()));
140 layerNames.addAll(role.getLayers().getOrElse(List.of()));
141 }
141 }
142
142
143 if (!variantArtifact.getSlots().isEmpty()) {
144 if (variantArtifact.findPrimarySlotName().isEmpty()) {
145 errors.add("Variant artifact '" + variantArtifact.getName()
146 + "' must declare primary slot because it has multiple slots");
147 } else {
148 var primarySlotName = variantArtifact.requirePrimarySlotName();
149 if (variantArtifact.findSlot(primarySlotName).isEmpty()) {
150 errors.add("Variant artifact '" + variantArtifact.getName()
151 + "' declares unknown primary slot '" + primarySlotName + "'");
152 }
153 }
154 }
155
143 for (var slot : variantArtifact.getSlots()) {
156 for (var slot : variantArtifact.getSlots()) {
144 for (var rule : slot.bindingRules()) {
157 for (var rule : slot.bindingRules()) {
145 switch (rule.selector().kind()) {
158 switch (rule.selector().kind()) {
146 case VARIANT -> {
159 case VARIANT -> {
147 }
160 }
148 case ROLE -> {
161 case ROLE -> {
149 if (!roleNames.contains(rule.selector().value())) {
162 if (!roleNames.contains(rule.selector().value())) {
150 errors.add("Variant artifact '" + variantArtifact.getName() + "', slot '" + slot.getName()
163 errors.add("Variant artifact '" + variantArtifact.getName() + "', slot '" + slot.getName()
151 + "' references unknown role '" + rule.selector().value() + "'");
164 + "' references unknown role '" + rule.selector().value() + "'");
152 }
165 }
153 }
166 }
154 case LAYER -> {
167 case LAYER -> {
155 if (!layerNames.contains(rule.selector().value())) {
168 if (!layerNames.contains(rule.selector().value())) {
156 errors.add("Variant artifact '" + variantArtifact.getName() + "', slot '" + slot.getName()
169 errors.add("Variant artifact '" + variantArtifact.getName() + "', slot '" + slot.getName()
157 + "' references unknown layer '" + rule.selector().value() + "'");
170 + "' references unknown layer '" + rule.selector().value() + "'");
158 }
171 }
159 }
172 }
160 }
173 }
161 }
174 }
162 }
175 }
163 }
176 }
164
177
165 private void ensureMutable(String operation) {
178 private void ensureMutable(String operation) {
166 if (finalized)
179 if (finalized)
167 throw new InvalidUserDataException("variantArtifacts model is finalized and cannot " + operation);
180 throw new InvalidUserDataException("variantArtifacts model is finalized and cannot " + operation);
168 }
181 }
169 }
182 }
@@ -1,96 +1,180
1 package org.implab.gradle.common.sources;
1 package org.implab.gradle.common.sources;
2
2
3 import java.util.LinkedHashMap;
4 import java.util.List;
5 import java.util.ArrayList;
6 import java.util.stream.Collectors;
7 import java.util.stream.Stream;
8
3 import org.gradle.api.GradleException;
9 import org.gradle.api.GradleException;
4 import org.gradle.api.Plugin;
10 import org.gradle.api.Plugin;
5 import org.gradle.api.Project;
11 import org.gradle.api.Project;
6 import org.gradle.api.artifacts.Configuration;
12 import org.gradle.api.artifacts.Configuration;
13 import org.gradle.api.artifacts.ConfigurationPublications;
14 import org.gradle.api.artifacts.ConfigurationVariant;
7 import org.gradle.api.logging.Logger;
15 import org.gradle.api.logging.Logger;
8 import org.gradle.api.logging.Logging;
16 import org.gradle.api.logging.Logging;
9 import org.implab.gradle.common.core.lang.Strings;
17 import org.implab.gradle.common.core.lang.Strings;
10
18
11 public abstract class VariantsArtifactsPlugin implements Plugin<Project> {
19 public abstract class VariantsArtifactsPlugin implements Plugin<Project> {
12 private static final Logger logger = Logging.getLogger(VariantsArtifactsPlugin.class);
20 private static final Logger logger = Logging.getLogger(VariantsArtifactsPlugin.class);
13 public static final String VARIANT_ARTIFACTS_EXTENSION_NAME = "variantArtifacts";
21 public static final String VARIANT_ARTIFACTS_EXTENSION_NAME = "variantArtifacts";
14
22
15 @Override
23 @Override
16 public void apply(Project target) {
24 public void apply(Project target) {
17 logger.debug("Registering '{}' extension on project '{}'", VARIANT_ARTIFACTS_EXTENSION_NAME, target.getPath());
25 logger.debug("Registering '{}' extension on project '{}'", VARIANT_ARTIFACTS_EXTENSION_NAME, target.getPath());
18
26
19 target.getPluginManager().apply(VariantsSourcesPlugin.class);
27 target.getPluginManager().apply(VariantsSourcesPlugin.class);
20
28
21 var variants = VariantsPlugin.getVariantsExtension(target);
29 var variants = VariantsPlugin.getVariantsExtension(target);
22 var variantSources = target.getExtensions().getByType(VariantSourcesExtension.class);
30 var variantSources = target.getExtensions().getByType(VariantSourcesExtension.class);
23 var variantArtifacts = target.getExtensions()
31 var variantArtifacts = target.getExtensions()
24 .create(VARIANT_ARTIFACTS_EXTENSION_NAME, VariantArtifactsExtension.class);
32 .create(VARIANT_ARTIFACTS_EXTENSION_NAME, VariantArtifactsExtension.class);
25 var variantArtifactsResolver = new VariantArtifactsResolver(target.getObjects());
33 var variantArtifactsResolver = new VariantArtifactsResolver(target.getObjects());
26 var artifactAssemblies = new ArtifactAssemblyRegistry(target.getObjects(), target.getTasks());
34 var artifactAssemblies = new ArtifactAssemblyRegistry(target.getObjects(), target.getTasks());
27
35
28 variantSources.whenBound(variantArtifactsResolver::recordBinding);
36 variantSources.whenBound(variantArtifactsResolver::recordBinding);
29
37
30 variants.whenFinalized(model -> {
38 variants.whenFinalized(model -> {
31 logger.debug("Finalizing variantArtifacts model on project '{}'", target.getPath());
39 logger.debug("Finalizing variantArtifacts model on project '{}'", target.getPath());
32 variantArtifacts.finalizeModel(model);
40 variantArtifacts.finalizeModel(model);
33 materializeOutgoingVariants(target, model, variantArtifacts, variantArtifactsResolver, artifactAssemblies);
41 materializeOutgoingVariants(target, model, variantArtifacts, variantArtifactsResolver, artifactAssemblies);
34 logger.debug("variantArtifacts model finalized on project '{}'", target.getPath());
42 logger.debug("variantArtifacts model finalized on project '{}'", target.getPath());
35 });
43 });
36 }
44 }
37
45
38 public static VariantArtifactsExtension getVariantArtifactsExtension(Project target) {
46 public static VariantArtifactsExtension getVariantArtifactsExtension(Project target) {
39 var extension = target.getExtensions().findByType(VariantArtifactsExtension.class);
47 var extension = target.getExtensions().findByType(VariantArtifactsExtension.class);
40
48
41 if (extension == null) {
49 if (extension == null) {
42 logger.error("variantArtifacts extension '{}' isn't found on project '{}'",
50 logger.error("variantArtifacts extension '{}' isn't found on project '{}'",
43 VARIANT_ARTIFACTS_EXTENSION_NAME,
51 VARIANT_ARTIFACTS_EXTENSION_NAME,
44 target.getPath());
52 target.getPath());
45 throw new GradleException("variantArtifacts extension isn't found");
53 throw new GradleException("variantArtifacts extension isn't found");
46 }
54 }
47
55
48 logger.debug("Resolved '{}' extension on project '{}'", VARIANT_ARTIFACTS_EXTENSION_NAME, target.getPath());
56 logger.debug("Resolved '{}' extension on project '{}'", VARIANT_ARTIFACTS_EXTENSION_NAME, target.getPath());
49
57
50 return extension;
58 return extension;
51 }
59 }
52
60
53 private static void materializeOutgoingVariants(
61 private static void materializeOutgoingVariants(
54 Project project,
62 Project project,
55 BuildVariantsExtension topology,
63 BuildVariantsExtension topology,
56 VariantArtifactsExtension variantArtifacts,
64 VariantArtifactsExtension variantArtifacts,
57 VariantArtifactsResolver variantArtifactsResolver,
65 VariantArtifactsResolver variantArtifactsResolver,
58 ArtifactAssemblyRegistry artifactAssemblies) {
66 ArtifactAssemblyRegistry artifactAssemblies) {
59 for (var variantArtifact : variantArtifacts.getVariants()) {
67 variantArtifacts.getVariants().stream()
60 var topologyVariant = topology.require(variantArtifact.getName());
68 .filter(variantArtifact -> !variantArtifact.getSlots().isEmpty())
61 for (var slot : variantArtifact.getSlots()) {
69 .forEach(variantArtifact -> materializeOutgoingVariant(
62 var assembly = artifactAssemblies.register(
70 project,
63 variantArtifact.getName() + Strings.capitalize(slot.getName()),
71 topology.require(variantArtifact.getName()),
64 "process" + Strings.capitalize(variantArtifact.getName()) + Strings.capitalize(slot.getName()),
65 project.getLayout().getBuildDirectory()
66 .dir("variant-artifacts/" + variantArtifact.getName() + "/" + slot.getName()),
67 files -> files.from(variantArtifactsResolver.files(variantArtifact.getName(), slot)));
68 var configuration = createOutgoingConfiguration(project, variantArtifact.getName(), slot.getName(), assembly);
69
70 variantArtifacts.notifyOutgoingVariant(new OutgoingVariantPublication(
71 variantArtifact.getName(),
72 slot.getName(),
73 topologyVariant,
74 variantArtifact,
72 variantArtifact,
75 slot,
73 variantArtifactsResolver,
76 assembly,
74 artifactAssemblies,
77 configuration));
75 variantArtifacts));
78 }
79 }
80 }
76 }
81
77
82 private static org.gradle.api.NamedDomainObjectProvider<? extends Configuration> createOutgoingConfiguration(
78 private static void materializeOutgoingVariant(
79 Project project,
80 BuildVariant topologyVariant,
81 VariantArtifact variantArtifact,
82 VariantArtifactsResolver variantArtifactsResolver,
83 ArtifactAssemblyRegistry artifactAssemblies,
84 VariantArtifactsExtension variantArtifacts) {
85 var assemblies = variantArtifact.getSlots().stream()
86 .collect(Collectors.toMap(
87 VariantArtifactSlot::getName,
88 slot -> registerAssembly(project, variantArtifactsResolver, artifactAssemblies, variantArtifact, slot),
89 (left, right) -> left,
90 LinkedHashMap::new));
91
92 var primarySlot = variantArtifact.requirePrimarySlot();
93 var configuration = createOutgoingConfiguration(project, variantArtifact.getName(), primarySlot.getName());
94 var primaryAssembly = assemblies.get(primarySlot.getName());
95 publishPrimaryArtifact(configuration, primaryAssembly);
96 var primaryPublication = new OutgoingArtifactSlotPublication(
97 primarySlot.getName(),
98 true,
99 primarySlot,
100 primaryAssembly,
101 configuration);
102 var secondarySlots = variantArtifact.getSlots().stream()
103 .filter(slot -> !slot.getName().equals(primarySlot.getName()))
104 .map(slot -> new SecondarySlot(slot, assemblies.get(slot.getName())))
105 .toList();
106 var secondaryPublications = new ArrayList<OutgoingArtifactSlotPublication>(secondarySlots.size());
107 secondarySlots.forEach(secondarySlot -> {
108 var secondaryVariant = configuration.getOutgoing().getVariants().create(secondarySlot.slot().getName());
109 publishSecondaryArtifact(secondaryVariant, secondarySlot.assembly());
110 secondaryPublications.add(new OutgoingArtifactSlotPublication(
111 secondarySlot.slot().getName(),
112 false,
113 secondarySlot.slot(),
114 secondarySlot.assembly(),
115 secondaryVariant));
116 });
117
118 var slotPublications = Stream.concat(
119 Stream.of(primaryPublication),
120 secondaryPublications.stream())
121 .toList();
122
123 variantArtifacts.notifyOutgoingVariant(new OutgoingVariantPublication(
124 variantArtifact.getName(),
125 topologyVariant,
126 variantArtifact,
127 configuration,
128 primaryPublication,
129 slotPublications));
130 }
131
132 private static ArtifactAssembly registerAssembly(
133 Project project,
134 VariantArtifactsResolver variantArtifactsResolver,
135 ArtifactAssemblyRegistry artifactAssemblies,
136 VariantArtifact variantArtifact,
137 VariantArtifactSlot slot) {
138 return artifactAssemblies.register(
139 variantArtifact.getName() + Strings.capitalize(slot.getName()),
140 "process" + Strings.capitalize(variantArtifact.getName()) + Strings.capitalize(slot.getName()),
141 project.getLayout().getBuildDirectory()
142 .dir("variant-artifacts/" + variantArtifact.getName() + "/" + slot.getName()),
143 files -> files.from(variantArtifactsResolver.files(variantArtifact.getName(), slot)));
144 }
145
146 private static Configuration createOutgoingConfiguration(
83 Project project,
147 Project project,
84 String variantName,
148 String variantName,
85 String slotName,
149 String primarySlotName) {
86 ArtifactAssembly assembly) {
150 var configName = variantName + "Elements";
87 var configName = variantName + Strings.capitalize(slotName) + "Elements";
88 return project.getConfigurations().consumable(configName, config -> {
151 return project.getConfigurations().consumable(configName, config -> {
89 config.setVisible(true);
152 config.setVisible(true);
90 config.setDescription("Consumable assembled artifacts for variant '" + variantName + "', slot '" + slotName + "'");
153 config.setDescription("Consumable assembled artifacts for variant '" + variantName
91 config.getOutgoing().artifact(assembly.getOutput().getSingleFile(), published -> {
154 + "' with primary slot '" + primarySlotName + "'");
92 published.builtBy(assembly.getOutput().getBuildDependencies());
155 }).get();
93 });
156 }
157
158 private static void publishPrimaryArtifact(Configuration configuration, ArtifactAssembly assembly) {
159 publishArtifact(configuration.getOutgoing(), assembly);
160 }
161
162 private static void publishSecondaryArtifact(ConfigurationVariant variant, ArtifactAssembly assembly) {
163 publishArtifact(variant, assembly);
164 }
165
166 private static void publishArtifact(ConfigurationPublications outgoing, ArtifactAssembly assembly) {
167 outgoing.artifact(assembly.getOutput().getSingleFile(), published -> {
168 published.builtBy(assembly.getOutput().getBuildDependencies());
94 });
169 });
95 }
170 }
171
172 private static void publishArtifact(ConfigurationVariant variant, ArtifactAssembly assembly) {
173 variant.artifact(assembly.getOutput().getSingleFile(), published -> {
174 published.builtBy(assembly.getOutput().getBuildDependencies());
175 });
176 }
177
178 private record SecondarySlot(VariantArtifactSlot slot, ArtifactAssembly assembly) {
179 }
96 }
180 }
@@ -1,294 +1,503
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 import java.util.stream.Collectors;
12
13
13 import org.gradle.testkit.runner.BuildResult;
14 import org.gradle.testkit.runner.BuildResult;
14 import org.gradle.testkit.runner.GradleRunner;
15 import org.gradle.testkit.runner.GradleRunner;
15 import org.gradle.testkit.runner.TaskOutcome;
16 import org.gradle.testkit.runner.TaskOutcome;
16 import org.gradle.testkit.runner.UnexpectedBuildFailure;
17 import org.gradle.testkit.runner.UnexpectedBuildFailure;
17 import org.junit.jupiter.api.Test;
18 import org.junit.jupiter.api.Test;
18 import org.junit.jupiter.api.io.TempDir;
19 import org.junit.jupiter.api.io.TempDir;
19
20
20 class VariantsArtifactsPluginFunctionalTest {
21 class VariantsArtifactsPluginFunctionalTest {
21 private static final String SETTINGS_FILE = "settings.gradle";
22 private static final String SETTINGS_FILE = "settings.gradle";
22 private static final String BUILD_FILE = "build.gradle";
23 private static final String BUILD_FILE = "build.gradle";
23 private static final String ROOT_NAME = "rootProject.name = 'variants-artifacts-fixture'\n";
24 private static final String ROOT_NAME = "rootProject.name = 'variants-artifacts-fixture'\n";
24
25
25 @TempDir
26 @TempDir
26 Path testProjectDir;
27 Path testProjectDir;
27
28
28 @Test
29 @Test
29 void materializesVariantArtifactsAndInvokesOutgoingHooks() throws Exception {
30 void materializesVariantArtifactsAndInvokesOutgoingHooks() throws Exception {
30 writeFile(SETTINGS_FILE, ROOT_NAME);
31 writeFile(SETTINGS_FILE, ROOT_NAME);
31 writeFile("inputs/base.js", "console.log('base')\n");
32 writeFile("inputs/base.js", "console.log('base')\n");
32 writeFile("inputs/amd.js", "console.log('amd')\n");
33 writeFile("inputs/amd.js", "console.log('amd')\n");
33 writeFile("inputs/mainJs.txt", "mainJs marker\n");
34 writeFile("inputs/mainJs.txt", "mainJs marker\n");
34 writeFile("inputs/amdJs.txt", "amdJs marker\n");
35 writeFile("inputs/amdJs.txt", "amdJs marker\n");
35 writeFile(BUILD_FILE, """
36 writeFile(BUILD_FILE, """
36 import org.gradle.api.attributes.Attribute
37 import org.gradle.api.attributes.Attribute
37
38
38 plugins {
39 plugins {
39 id 'org.implab.gradle-variants-artifacts'
40 id 'org.implab.gradle-variants-artifacts'
40 }
41 }
41
42
42 variants {
43 variants {
43 layer('mainBase')
44 layer('mainBase')
44 layer('mainAmd')
45 layer('mainAmd')
45
46
46 variant('browser') {
47 variant('browser') {
47 role('main') {
48 role('main') {
48 layers('mainBase', 'mainAmd')
49 layers('mainBase', 'mainAmd')
49 }
50 }
50 }
51 }
51 }
52 }
52
53
53 variantSources {
54 variantSources {
54 bind('mainBase') {
55 bind('mainBase') {
55 configureSourceSet {
56 configureSourceSet {
56 declareOutputs('js')
57 declareOutputs('js')
57 }
58 }
58 }
59 }
59
60
60 bind('mainAmd') {
61 bind('mainAmd') {
61 configureSourceSet {
62 configureSourceSet {
62 declareOutputs('js')
63 declareOutputs('js')
63 }
64 }
64 }
65 }
65
66
66 whenBound { ctx ->
67 whenBound { ctx ->
67 if (ctx.sourceSetName() == 'browserMainBase') {
68 if (ctx.sourceSetName() == 'browserMainBase') {
68 ctx.configureSourceSet {
69 ctx.configureSourceSet {
69 registerOutput('js', layout.projectDirectory.file('inputs/base.js'))
70 registerOutput('js', layout.projectDirectory.file('inputs/base.js'))
70 }
71 }
71 }
72 }
72
73
73 if (ctx.sourceSetName() == 'browserMainAmd') {
74 if (ctx.sourceSetName() == 'browserMainAmd') {
74 ctx.configureSourceSet {
75 ctx.configureSourceSet {
75 registerOutput('js', layout.projectDirectory.file('inputs/amd.js'))
76 registerOutput('js', layout.projectDirectory.file('inputs/amd.js'))
76 }
77 }
77 }
78 }
78 }
79 }
79 }
80 }
80
81
81 variantArtifacts {
82 variantArtifacts {
82 variant('browser') {
83 variant('browser') {
83 slot('mainJs') {
84 primarySlot('mainJs') {
84 fromRole('main') {
85 fromRole('main') {
85 output('js')
86 output('js')
86 }
87 }
87 }
88 }
88
89
89 slot('amdJs') {
90 slot('amdJs') {
90 fromLayer('mainAmd') {
91 fromLayer('mainAmd') {
91 output('js')
92 output('js')
92 }
93 }
93 }
94 }
94 }
95 }
95
96
96 whenOutgoingVariant { publication ->
97 whenOutgoingVariant { publication ->
97 publication.configureAssembly {
98 publication.slots().each { slotPublication ->
98 sources.from(layout.projectDirectory.file("inputs/${publication.slotName()}.txt"))
99 slotPublication.configureAssembly {
99 }
100 sources.from(layout.projectDirectory.file("inputs/${slotPublication.slotName()}.txt"))
101 }
100
102
101 publication.configureConfiguration {
103 slotPublication.configureArtifactAttributes {
102 attributes.attribute(Attribute.of('test.slot', String), publication.slotName())
104 attribute(Attribute.of('test.slot', String), slotPublication.slotName())
105 }
103 }
106 }
104 }
107 }
105 }
108 }
106
109
107 tasks.register('probe') {
110 tasks.register('probe') {
108 dependsOn 'processBrowserMainJs', 'processBrowserAmdJs'
111 dependsOn 'processBrowserMainJs', 'processBrowserAmdJs'
109
112
110 doLast {
113 doLast {
111 def mainDir = layout.buildDirectory.dir('variant-artifacts/browser/mainJs').get().asFile
114 def mainDir = layout.buildDirectory.dir('variant-artifacts/browser/mainJs').get().asFile
112 def amdDir = layout.buildDirectory.dir('variant-artifacts/browser/amdJs').get().asFile
115 def amdDir = layout.buildDirectory.dir('variant-artifacts/browser/amdJs').get().asFile
113
116
114 assert new File(mainDir, 'base.js').exists()
117 assert new File(mainDir, 'base.js').exists()
115 assert new File(mainDir, 'amd.js').exists()
118 assert new File(mainDir, 'amd.js').exists()
116 assert new File(mainDir, 'mainJs.txt').exists()
119 assert new File(mainDir, 'mainJs.txt').exists()
117
120
118 assert !new File(amdDir, 'base.js').exists()
121 assert !new File(amdDir, 'base.js').exists()
119 assert new File(amdDir, 'amd.js').exists()
122 assert new File(amdDir, 'amd.js').exists()
120 assert new File(amdDir, 'amdJs.txt').exists()
123 assert new File(amdDir, 'amdJs.txt').exists()
121
124
122 def mainElements = configurations.getByName('browserMainJsElements')
125 def elements = configurations.getByName('browserElements')
123 def attr = mainElements.attributes.getAttribute(Attribute.of('test.slot', String))
126 def primaryAttr = elements.attributes.getAttribute(Attribute.of('test.slot', String))
127 def amdVariant = elements.outgoing.variants.getByName('amdJs')
128 def amdAttr = amdVariant.attributes.getAttribute(Attribute.of('test.slot', String))
124
129
125 println('mainAttr=' + attr)
130 println('primarySlot=' + variantArtifacts.requireVariant('browser').requirePrimarySlotName())
126 println('configurations=' + [mainElements.name, configurations.getByName('browserAmdJsElements').name].sort().join(','))
131 println('primaryAttr=' + primaryAttr)
132 println('amdAttr=' + amdAttr)
133 println('configurations=' + configurations.matching { it.name == 'browserElements' }.collect { it.name }.join(','))
134 println('secondaryVariants=' + elements.outgoing.variants.collect { it.name }.sort().join(','))
127 }
135 }
128 }
136 }
129 """);
137 """);
130
138
131 BuildResult result = runner("probe").build();
139 BuildResult result = runner("probe").build();
132
140
133 assertTrue(result.getOutput().contains("mainAttr=mainJs"));
141 assertTrue(result.getOutput().contains("primarySlot=mainJs"));
134 assertTrue(result.getOutput().contains("configurations=browserAmdJsElements,browserMainJsElements"));
142 assertTrue(result.getOutput().contains("primaryAttr=mainJs"));
143 assertTrue(result.getOutput().contains("amdAttr=amdJs"));
144 assertTrue(result.getOutput().contains("configurations=browserElements"));
145 assertTrue(result.getOutput().contains("secondaryVariants=amdJs"));
135 assertTrue(result.task(":probe").getOutcome() == TaskOutcome.SUCCESS);
146 assertTrue(result.task(":probe").getOutcome() == TaskOutcome.SUCCESS);
136 }
147 }
137
148
138 @Test
149 @Test
150 void allowsSingleSlotVariantWithoutExplicitPrimarySlot() throws Exception {
151 writeFile(SETTINGS_FILE, ROOT_NAME);
152 writeFile(BUILD_FILE, """
153 plugins {
154 id 'org.implab.gradle-variants-artifacts'
155 }
156
157 variants {
158 layer('main')
159
160 variant('browser') {
161 role('main') {
162 layers('main')
163 }
164 }
165 }
166
167 variantArtifacts {
168 variant('browser') {
169 slot('typesPackage') {
170 fromVariant {
171 output('types')
172 }
173 }
174 }
175 }
176
177 tasks.register('probe') {
178 doLast {
179 println('primary=' + variantArtifacts.requireVariant('browser').requirePrimarySlotName())
180 }
181 }
182 """);
183
184 BuildResult result = runner("probe").build();
185 assertTrue(result.getOutput().contains("primary=typesPackage"));
186 }
187
188 @Test
139 void failsOnUnknownVariantReference() throws Exception {
189 void failsOnUnknownVariantReference() throws Exception {
140 assertBuildFails("""
190 assertBuildFails("""
141 plugins {
191 plugins {
142 id 'org.implab.gradle-variants-artifacts'
192 id 'org.implab.gradle-variants-artifacts'
143 }
193 }
144
194
145 variants {
195 variants {
146 layer('main')
196 layer('main')
147 }
197 }
148
198
149 variantArtifacts {
199 variantArtifacts {
150 variant('browser') {
200 variant('browser') {
151 slot('mainJs') {
201 slot('mainJs') {
152 fromVariant {
202 fromVariant {
153 output('js')
203 output('js')
154 }
204 }
155 }
205 }
156 }
206 }
157 }
207 }
158 """, "Variant artifact 'browser' references unknown variant 'browser'");
208 """, "Variant artifact 'browser' references unknown variant 'browser'");
159 }
209 }
160
210
161 @Test
211 @Test
162 void failsOnUnknownRoleReference() throws Exception {
212 void failsOnUnknownRoleReference() throws Exception {
163 assertBuildFails("""
213 assertBuildFails("""
164 plugins {
214 plugins {
165 id 'org.implab.gradle-variants-artifacts'
215 id 'org.implab.gradle-variants-artifacts'
166 }
216 }
167
217
168 variants {
218 variants {
169 layer('main')
219 layer('main')
170
220
171 variant('browser') {
221 variant('browser') {
172 role('main') {
222 role('main') {
173 layers('main')
223 layers('main')
174 }
224 }
175 }
225 }
176 }
226 }
177
227
178 variantArtifacts {
228 variantArtifacts {
179 variant('browser') {
229 variant('browser') {
180 slot('mainJs') {
230 slot('mainJs') {
181 fromRole('test') {
231 fromRole('test') {
182 output('js')
232 output('js')
183 }
233 }
184 }
234 }
185 }
235 }
186 }
236 }
187 """, "Variant artifact 'browser', slot 'mainJs' references unknown role 'test'");
237 """, "Variant artifact 'browser', slot 'mainJs' references unknown role 'test'");
188 }
238 }
189
239
190 @Test
240 @Test
241 void failsWhenPrimarySlotIsMissingForMultipleSlots() throws Exception {
242 assertBuildFails("""
243 plugins {
244 id 'org.implab.gradle-variants-artifacts'
245 }
246
247 variants {
248 layer('main')
249
250 variant('browser') {
251 role('main') {
252 layers('main')
253 }
254 }
255 }
256
257 variantArtifacts {
258 variant('browser') {
259 slot('typesPackage') {
260 fromVariant {
261 output('types')
262 }
263 }
264
265 slot('js') {
266 fromVariant {
267 output('js')
268 }
269 }
270 }
271 }
272 """, "Variant artifact 'browser' must declare primary slot because it has multiple slots");
273 }
274
275 @Test
191 void failsOnLayerReferenceOutsideVariantTopology() throws Exception {
276 void failsOnLayerReferenceOutsideVariantTopology() throws Exception {
192 assertBuildFails("""
277 assertBuildFails("""
193 plugins {
278 plugins {
194 id 'org.implab.gradle-variants-artifacts'
279 id 'org.implab.gradle-variants-artifacts'
195 }
280 }
196
281
197 variants {
282 variants {
198 layer('mainBase')
283 layer('mainBase')
199 layer('extra')
284 layer('extra')
200
285
201 variant('browser') {
286 variant('browser') {
202 role('main') {
287 role('main') {
203 layers('mainBase')
288 layers('mainBase')
204 }
289 }
205 }
290 }
206 }
291 }
207
292
208 variantArtifacts {
293 variantArtifacts {
209 variant('browser') {
294 variant('browser') {
210 slot('extraJs') {
295 slot('extraJs') {
211 fromLayer('extra') {
296 fromLayer('extra') {
212 output('js')
297 output('js')
213 }
298 }
214 }
299 }
215 }
300 }
216 }
301 }
217 """, "Variant artifact 'browser', slot 'extraJs' references unknown layer 'extra'");
302 """, "Variant artifact 'browser', slot 'extraJs' references unknown layer 'extra'");
218 }
303 }
219
304
220 @Test
305 @Test
221 void failsOnLateMutationAfterFinalize() throws Exception {
306 void failsOnLateMutationAfterFinalize() throws Exception {
222 assertBuildFails("""
307 assertBuildFails("""
223 plugins {
308 plugins {
224 id 'org.implab.gradle-variants-artifacts'
309 id 'org.implab.gradle-variants-artifacts'
225 }
310 }
226
311
227 variants {
312 variants {
228 layer('main')
313 layer('main')
229
314
230 variant('browser') {
315 variant('browser') {
231 role('main') {
316 role('main') {
232 layers('main')
317 layers('main')
233 }
318 }
234 }
319 }
235 }
320 }
236
321
237 afterEvaluate {
322 afterEvaluate {
238 variantArtifacts.variant('late') {
323 variantArtifacts.variant('late') {
239 slot('js') {
324 slot('js') {
240 fromVariant {
325 fromVariant {
241 output('js')
326 output('js')
242 }
327 }
243 }
328 }
244 }
329 }
245 }
330 }
246 """, "variantArtifacts model is finalized and cannot configure variants");
331 """, "variantArtifacts model is finalized and cannot configure variants");
247 }
332 }
248
333
334 @Test
335 void preservesPrimaryResolutionAndAllowsSecondaryArtifactSelection() throws Exception {
336 writeFile(SETTINGS_FILE, """
337 rootProject.name = 'variants-artifacts-fixture'
338 include 'producer', 'consumer'
339 """);
340 writeFile("producer/inputs/types.d.ts", "export type Foo = string\n");
341 writeFile("producer/inputs/index.js", "export const foo = 'bar'\n");
342 var buildscriptClasspath = pluginClasspath().stream()
343 .map(File::getAbsolutePath)
344 .map(path -> "'" + path.replace("\\", "\\\\") + "'")
345 .collect(Collectors.joining(", "));
346 writeFile(BUILD_FILE, """
347 buildscript {
348 dependencies {
349 classpath files(%s)
350 }
351 }
352
353 import org.gradle.api.attributes.Attribute
354
355 def variantAttr = Attribute.of('test.variant', String)
356 def slotAttr = Attribute.of('test.slot', String)
357
358 subprojects {
359 apply plugin: 'org.implab.gradle-variants-artifacts'
360 }
361
362 project(':producer') {
363 variants {
364 layer('main')
365
366 variant('browser') {
367 role('main') {
368 layers('main')
369 }
370 }
371 }
372
373 variantSources {
374 bind('main') {
375 configureSourceSet {
376 declareOutputs('types', 'js')
377 }
378 }
379
380 whenBound { ctx ->
381 ctx.configureSourceSet {
382 registerOutput('types', layout.projectDirectory.file('inputs/types.d.ts'))
383 registerOutput('js', layout.projectDirectory.file('inputs/index.js'))
384 }
385 }
386 }
387
388 variantArtifacts {
389 variant('browser') {
390 primarySlot('typesPackage') {
391 fromVariant {
392 output('types')
393 }
394 }
395
396 slot('js') {
397 fromVariant {
398 output('js')
399 }
400 }
401 }
402
403 whenOutgoingVariant { publication ->
404 publication.configureConfiguration {
405 attributes.attribute(variantAttr, publication.variantName())
406 }
407
408 publication.primarySlot().configureArtifactAttributes {
409 attribute(slotAttr, publication.primarySlot().slotName())
410 }
411
412 publication.requireSlot('js').configureArtifactAttributes {
413 attribute(slotAttr, 'js')
414 }
415 }
416 }
417 }
418
419 project(':consumer') {
420 configurations {
421 compileView {
422 canBeResolved = true
423 canBeConsumed = false
424 canBeDeclared = true
425 attributes {
426 attribute(variantAttr, 'browser')
427 attribute(slotAttr, 'typesPackage')
428 }
429 }
430 }
431
432 dependencies {
433 compileView project(':producer')
434 }
435
436 tasks.register('probe') {
437 doLast {
438 def compileFiles = configurations.compileView.files.collect { it.name }.sort().join(',')
439 def jsFiles = configurations.compileView.incoming.artifactView {
440 attributes {
441 attribute(slotAttr, 'js')
442 }
443 }.files.files.collect { it.name }.sort().join(',')
444
445 println('compileFiles=' + compileFiles)
446 println('jsFiles=' + jsFiles)
447 }
448 }
449 }
450 """.formatted(buildscriptClasspath));
451
452 BuildResult result = runner(":consumer:probe").build();
453
454 assertTrue(result.getOutput().contains("compileFiles=typesPackage"));
455 assertTrue(result.getOutput().contains("jsFiles=js"));
456 }
457
249 private GradleRunner runner(String... arguments) {
458 private GradleRunner runner(String... arguments) {
250 return GradleRunner.create()
459 return GradleRunner.create()
251 .withProjectDir(testProjectDir.toFile())
460 .withProjectDir(testProjectDir.toFile())
252 .withPluginClasspath(pluginClasspath())
461 .withPluginClasspath(pluginClasspath())
253 .withArguments(arguments)
462 .withArguments(arguments)
254 .forwardOutput();
463 .forwardOutput();
255 }
464 }
256
465
257 private void assertBuildFails(String buildScript, String expectedError) throws Exception {
466 private void assertBuildFails(String buildScript, String expectedError) throws Exception {
258 writeFile(SETTINGS_FILE, ROOT_NAME);
467 writeFile(SETTINGS_FILE, ROOT_NAME);
259 writeFile(BUILD_FILE, buildScript);
468 writeFile(BUILD_FILE, buildScript);
260
469
261 var ex = assertThrows(UnexpectedBuildFailure.class, () -> runner("help").build());
470 var ex = assertThrows(UnexpectedBuildFailure.class, () -> runner("help").build());
262 var output = ex.getBuildResult().getOutput();
471 var output = ex.getBuildResult().getOutput();
263
472
264 assertTrue(output.contains(expectedError), () -> "Expected [" + expectedError + "] in output:\n" + output);
473 assertTrue(output.contains(expectedError), () -> "Expected [" + expectedError + "] in output:\n" + output);
265 }
474 }
266
475
267 private static List<File> pluginClasspath() {
476 private static List<File> pluginClasspath() {
268 try {
477 try {
269 var classesDir = Path.of(VariantsArtifactsPlugin.class
478 var classesDir = Path.of(VariantsArtifactsPlugin.class
270 .getProtectionDomain()
479 .getProtectionDomain()
271 .getCodeSource()
480 .getCodeSource()
272 .getLocation()
481 .getLocation()
273 .toURI());
482 .toURI());
274
483
275 var markerResource = VariantsArtifactsPlugin.class.getClassLoader()
484 var markerResource = VariantsArtifactsPlugin.class.getClassLoader()
276 .getResource("META-INF/gradle-plugins/org.implab.gradle-variants-artifacts.properties");
485 .getResource("META-INF/gradle-plugins/org.implab.gradle-variants-artifacts.properties");
277
486
278 assertNotNull(markerResource, "Plugin marker resource is missing from test classpath");
487 assertNotNull(markerResource, "Plugin marker resource is missing from test classpath");
279
488
280 var markerPath = Path.of(markerResource.toURI());
489 var markerPath = Path.of(markerResource.toURI());
281 var resourcesDir = markerPath.getParent().getParent().getParent();
490 var resourcesDir = markerPath.getParent().getParent().getParent();
282
491
283 return List.of(classesDir.toFile(), resourcesDir.toFile());
492 return List.of(classesDir.toFile(), resourcesDir.toFile());
284 } catch (Exception e) {
493 } catch (Exception e) {
285 throw new RuntimeException("Unable to build plugin classpath for test", e);
494 throw new RuntimeException("Unable to build plugin classpath for test", e);
286 }
495 }
287 }
496 }
288
497
289 private void writeFile(String relativePath, String content) throws IOException {
498 private void writeFile(String relativePath, String content) throws IOException {
290 Path path = testProjectDir.resolve(relativePath);
499 Path path = testProjectDir.resolve(relativePath);
291 Files.createDirectories(path.getParent());
500 Files.createDirectories(path.getParent());
292 Files.writeString(path, content);
501 Files.writeString(path, content);
293 }
502 }
294 }
503 }
General Comments 0
You need to be logged in to leave comments. Login now