##// 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 -> ...`).
@@ -3,36 +3,87 package org.implab.gradle.common.sources
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 private final String variantName;
15 private final BuildVariant topologyVariant;
16 private final VariantArtifact variantArtifact;
17 private final Configuration configuration;
18 private final OutgoingArtifactSlotPublication primarySlot;
19 private final java.util.List<OutgoingArtifactSlotPublication> slots;
20
21 public OutgoingVariantPublication(
14 String variantName,
22 String variantName,
15 String slotName,
16 BuildVariant topologyVariant,
23 BuildVariant topologyVariant,
17 VariantArtifact variantArtifact,
24 VariantArtifact variantArtifact,
18 VariantArtifactSlot slot,
25 Configuration configuration,
19 ArtifactAssembly assembly,
26 OutgoingArtifactSlotPublication primarySlot,
20 NamedDomainObjectProvider<? extends Configuration> configuration) {
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 }
89 }
33
34 public void configureAssembly(
35 @DelegatesTo(value = ArtifactAssembly.class, strategy = Closure.DELEGATE_FIRST) Closure<?> action) {
36 configureAssembly(Closures.action(action));
37 }
38 }
@@ -18,6 +18,7 import groovy.lang.DelegatesTo;
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
@@ -63,6 +64,46 public class VariantArtifact implements
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))
@@ -140,6 +140,19 public abstract class VariantArtifactsEx
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()) {
@@ -1,9 +1,17
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;
@@ -56,41 +64,117 public abstract class VariantsArtifactsP
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,
71 topology.require(variantArtifact.getName()),
72 variantArtifact,
73 variantArtifactsResolver,
74 artifactAssemblies,
75 variantArtifacts));
76 }
77
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(
63 variantArtifact.getName() + Strings.capitalize(slot.getName()),
139 variantArtifact.getName() + Strings.capitalize(slot.getName()),
64 "process" + Strings.capitalize(variantArtifact.getName()) + Strings.capitalize(slot.getName()),
140 "process" + Strings.capitalize(variantArtifact.getName()) + Strings.capitalize(slot.getName()),
65 project.getLayout().getBuildDirectory()
141 project.getLayout().getBuildDirectory()
66 .dir("variant-artifacts/" + variantArtifact.getName() + "/" + slot.getName()),
142 .dir("variant-artifacts/" + variantArtifact.getName() + "/" + slot.getName()),
67 files -> files.from(variantArtifactsResolver.files(variantArtifact.getName(), slot)));
143 files -> files.from(variantArtifactsResolver.files(variantArtifact.getName(), slot)));
68 var configuration = createOutgoingConfiguration(project, variantArtifact.getName(), slot.getName(), assembly);
144 }
69
145
70 variantArtifacts.notifyOutgoingVariant(new OutgoingVariantPublication(
146 private static Configuration createOutgoingConfiguration(
71 variantArtifact.getName(),
147 Project project,
72 slot.getName(),
148 String variantName,
73 topologyVariant,
149 String primarySlotName) {
74 variantArtifact,
150 var configName = variantName + "Elements";
75 slot,
151 return project.getConfigurations().consumable(configName, config -> {
76 assembly,
152 config.setVisible(true);
77 configuration));
153 config.setDescription("Consumable assembled artifacts for variant '" + variantName
154 + "' with primary slot '" + primarySlotName + "'");
155 }).get();
78 }
156 }
79 }
157
158 private static void publishPrimaryArtifact(Configuration configuration, ArtifactAssembly assembly) {
159 publishArtifact(configuration.getOutgoing(), assembly);
80 }
160 }
81
161
82 private static org.gradle.api.NamedDomainObjectProvider<? extends Configuration> createOutgoingConfiguration(
162 private static void publishSecondaryArtifact(ConfigurationVariant variant, ArtifactAssembly assembly) {
83 Project project,
163 publishArtifact(variant, assembly);
84 String variantName,
164 }
85 String slotName,
165
86 ArtifactAssembly assembly) {
166 private static void publishArtifact(ConfigurationPublications outgoing, ArtifactAssembly assembly) {
87 var configName = variantName + Strings.capitalize(slotName) + "Elements";
167 outgoing.artifact(assembly.getOutput().getSingleFile(), published -> {
88 return project.getConfigurations().consumable(configName, config -> {
89 config.setVisible(true);
90 config.setDescription("Consumable assembled artifacts for variant '" + variantName + "', slot '" + slotName + "'");
91 config.getOutgoing().artifact(assembly.getOutput().getSingleFile(), published -> {
92 published.builtBy(assembly.getOutput().getBuildDependencies());
168 published.builtBy(assembly.getOutput().getBuildDependencies());
93 });
169 });
170 }
171
172 private static void publishArtifact(ConfigurationVariant variant, ArtifactAssembly assembly) {
173 variant.artifact(assembly.getOutput().getSingleFile(), published -> {
174 published.builtBy(assembly.getOutput().getBuildDependencies());
94 });
175 });
95 }
176 }
177
178 private record SecondarySlot(VariantArtifactSlot slot, ArtifactAssembly assembly) {
96 }
179 }
180 }
@@ -9,6 +9,7 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;
@@ -80,7 +81,7 class VariantsArtifactsPluginFunctionalT
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 }
@@ -94,12 +95,14 class VariantsArtifactsPluginFunctionalT
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 {
100 sources.from(layout.projectDirectory.file("inputs/${slotPublication.slotName()}.txt"))
99 }
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 }
@@ -119,23 +122,70 class VariantsArtifactsPluginFunctionalT
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 {
@@ -188,6 +238,41 class VariantsArtifactsPluginFunctionalT
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 {
@@ -246,6 +331,130 class VariantsArtifactsPluginFunctionalT
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())
General Comments 0
You need to be logged in to leave comments. Login now