##// END OF EJS Templates
Refine variant artifacts publication lifecycle- Remove assembly task access from outgoing slot publication spec- Keep whenOutgoingSlot focused on publication attributes only- Decouple materialization policy handler from artifact assemblies- Drop eager afterEvaluate outgoing configuration realization- Add reference coverage for lazy Gradle outgoing variants- Exercise primary and secondary artifact resolution without forced realization- Keep slot body customization in ArtifactAssemblySpec
cin -
r52:3939ecb6e9a4 default
parent child
Show More
@@ -0,0 +1,14
1 package org.implab.gradle.internal;
2
3 import org.gradle.api.Named;
4 import org.gradle.api.NamedDomainObjectContainer;
5 import org.gradle.api.model.ObjectFactory;
6
7 public final class IdentityContainerFactory {
8 private IdentityContainerFactory() {
9 }
10
11 public static <T extends Named> NamedDomainObjectContainer<T> create(ObjectFactory objectFactory, Class<T> type) {
12 return objectFactory.domainObjectContainer(type, name -> objectFactory.named(type, name));
13 }
14 }
@@ -0,0 +1,1
1 implementation-class=org.implab.gradle.variants.SourcesPlugin
@@ -0,0 +1,86
1 package org.implab.gradle.variants;
2
3 import static org.junit.jupiter.api.Assertions.assertEquals;
4 import static org.junit.jupiter.api.Assertions.assertNotNull;
5 import static org.junit.jupiter.api.Assertions.assertTrue;
6
7 import java.io.IOException;
8 import java.util.Properties;
9
10 import org.gradle.testkit.runner.BuildResult;
11 import org.junit.jupiter.api.Test;
12
13 class PluginMarkerFunctionalTest extends AbstractFunctionalTest {
14
15 @Test
16 void pluginMarkersPointToCurrentImplementations() throws Exception {
17 assertMarker("org.implab.gradle-variants.properties", VariantsPlugin.class);
18 assertMarker("org.implab.gradle-sources.properties", SourcesPlugin.class);
19 assertMarker("org.implab.gradle-variants-sources.properties", VariantSourcesPlugin.class);
20 assertMarker("org.implab.gradle-variants-artifacts.properties", VariantArtifactsPlugin.class);
21 }
22
23 @Test
24 void pluginIdsApplyCurrentExtensions() throws Exception {
25 writeSettings("variants-plugin-ids");
26 writeBuildFile("""
27 apply plugin: 'org.implab.gradle-variants'
28 apply plugin: 'org.implab.gradle-sources'
29 apply plugin: 'org.implab.gradle-variants-sources'
30 apply plugin: 'org.implab.gradle-variants-artifacts'
31
32 tasks.register('probe') {
33 doLast {
34 println("variantsExtension=" + (project.extensions.findByName('variants') != null))
35 println("sourcesExtension=" + (project.extensions.findByName('sources') != null))
36 println("variantSourcesExtension=" + (project.extensions.findByName('variantSources') != null))
37 println("variantArtifactsExtension=" + (project.extensions.findByName('variantArtifacts') != null))
38 }
39 }
40 """);
41
42 BuildResult result = runner("probe").build();
43
44 assertTrue(result.getOutput().contains("variantsExtension=true"));
45 assertTrue(result.getOutput().contains("sourcesExtension=true"));
46 assertTrue(result.getOutput().contains("variantSourcesExtension=true"));
47 assertTrue(result.getOutput().contains("variantArtifactsExtension=true"));
48 }
49
50 @Test
51 void sourcesPluginSupportsStandaloneSourceSetWorkflow() throws Exception {
52 writeSettings("sources-plugin-standalone");
53 writeFile("inputs/main.js", "console.log('main')\n");
54 writeBuildFile("""
55 apply plugin: 'org.implab.gradle-sources'
56
57 sources.create('main') {
58 declareOutputs('js')
59 registerOutput('js', layout.projectDirectory.file('inputs/main.js'))
60 }
61
62 tasks.register('probe') {
63 doLast {
64 def main = sources.named('main').get()
65 println("sourceSet=" + main.name)
66 println("jsFiles=" + main.output('js').files.collect { it.name }.sort().join(','))
67 }
68 }
69 """);
70
71 BuildResult result = runner("probe").build();
72
73 assertTrue(result.getOutput().contains("sourceSet=main"));
74 assertTrue(result.getOutput().contains("jsFiles=main.js"));
75 }
76
77 private static void assertMarker(String markerName, Class<?> implementationClass) throws IOException {
78 var resourceName = "META-INF/gradle-plugins/" + markerName;
79 var resource = PluginMarkerFunctionalTest.class.getClassLoader().getResourceAsStream(resourceName);
80 assertNotNull(resource, "Missing plugin marker " + resourceName);
81
82 var properties = new Properties();
83 properties.load(resource);
84 assertEquals(implementationClass.getName(), properties.getProperty("implementation-class"));
85 }
86 }
This diff has been collapsed as it changes many lines, (563 lines changed) Show them Hide them
@@ -0,0 +1,563
1 package org.implab.gradle.variants;
2
3 import static org.junit.jupiter.api.Assertions.assertTrue;
4
5 import org.gradle.testkit.runner.BuildResult;
6 import org.gradle.testkit.runner.TaskOutcome;
7 import org.junit.jupiter.api.Test;
8
9 class VariantArtifactsPluginFunctionalTest extends AbstractFunctionalTest {
10
11 @Test
12 void gradleReferenceLazyOutgoingConfigurationAllowsSecondaryArtifactSelection() throws Exception {
13 writeFile("settings.gradle", """
14 rootProject.name = 'gradle-reference-outgoing-resolution'
15 include 'producer', 'consumer'
16 """);
17 writeFile("producer/inputs/typesPackage", "types\n");
18 writeFile("producer/inputs/js", "js\n");
19 writeBuildFile("""
20 import org.gradle.api.attributes.Attribute
21
22 def variantAttr = Attribute.of('test.variant', String)
23 def slotAttr = Attribute.of('test.slot', String)
24
25 project(':producer') {
26 def browserElements = configurations.consumable('browserElements')
27
28 println('reference: registered browserElements provider')
29
30 browserElements.configure { configuration ->
31 println('reference: configuring browserElements')
32
33 configuration.attributes.attribute(variantAttr, 'browser')
34 configuration.outgoing.attributes.attribute(slotAttr, 'typesPackage')
35 configuration.outgoing.artifact(layout.projectDirectory.file('inputs/typesPackage'))
36
37 configuration.outgoing.variants.create('js') { secondary ->
38 println('reference: creating js outgoing variant')
39
40 secondary.attributes.attribute(slotAttr, 'js')
41 secondary.artifact(layout.projectDirectory.file('inputs/js'))
42 }
43 }
44 }
45
46 project(':consumer') {
47 configurations {
48 compileView {
49 canBeResolved = true
50 canBeConsumed = false
51 canBeDeclared = true
52 attributes {
53 attribute(variantAttr, 'browser')
54 attribute(slotAttr, 'typesPackage')
55 }
56 }
57 }
58
59 dependencies {
60 compileView project(':producer')
61 }
62
63 tasks.register('probe') {
64 doLast {
65 println('reference: resolving primary files')
66
67 def compileFiles = configurations.compileView.files.collect { it.name }.sort().join(',')
68
69 println('reference: resolving secondary files')
70
71 def jsFiles = configurations.compileView.incoming.artifactView {
72 attributes {
73 attribute(slotAttr, 'js')
74 }
75 }.files.files.collect { it.name }.sort().join(',')
76
77 println('compileFiles=' + compileFiles)
78 println('jsFiles=' + jsFiles)
79 }
80 }
81 }
82 """);
83
84 BuildResult result = runner(":consumer:probe").build();
85 var output = result.getOutput();
86 var registered = output.indexOf("reference: registered browserElements provider");
87 var resolvingPrimary = output.indexOf("reference: resolving primary files");
88 var configuring = output.indexOf("reference: configuring browserElements");
89 var creatingSecondary = output.indexOf("reference: creating js outgoing variant");
90 var resolvingSecondary = output.indexOf("reference: resolving secondary files");
91
92 assertTrue(registered >= 0);
93 assertTrue(resolvingPrimary >= 0);
94 assertTrue(configuring >= 0);
95 assertTrue(creatingSecondary >= 0);
96 assertTrue(resolvingSecondary >= 0);
97 assertTrue(registered < resolvingPrimary);
98 assertTrue(resolvingPrimary < configuring);
99 assertTrue(configuring < creatingSecondary);
100 assertTrue(creatingSecondary < resolvingSecondary);
101 assertTrue(output.contains("compileFiles=typesPackage"));
102 assertTrue(output.contains("jsFiles=js"));
103 }
104
105 @Test
106 void materializesPrimaryAndSecondarySlotsAndInvokesOutgoingHooks() throws Exception {
107 writeSettings("variant-artifacts-slots");
108 writeFile("inputs/base.js", "console.log('base')\n");
109 writeFile("inputs/amd.js", "console.log('amd')\n");
110 writeFile("inputs/mainJs.txt", "mainJs marker\n");
111 writeFile("inputs/amdJs.txt", "amdJs marker\n");
112 writeBuildFile("""
113 import org.gradle.api.attributes.Attribute
114
115 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
116
117 def variantAttr = Attribute.of('test.variant', String)
118 def slotAttr = Attribute.of('test.slot', String)
119
120 variants.layers.create('mainBase')
121 variants.layers.create('mainAmd')
122 variants.roles.create('main')
123 variants.roles.create('test')
124 variants.variant('browser') {
125 role('main') {
126 layers('mainBase', 'mainAmd')
127 }
128 }
129
130 variantSources {
131 layer('mainBase') {
132 declareOutputs('js')
133 registerOutput('js', layout.projectDirectory.file('inputs/base.js'))
134 }
135 layer('mainAmd') {
136 declareOutputs('js')
137 registerOutput('js', layout.projectDirectory.file('inputs/amd.js'))
138 }
139 }
140
141 variantArtifacts {
142 variant('browser') {
143 primarySlot('mainJs') {
144 fromRole('main') {
145 output('js')
146 }
147 from(layout.projectDirectory.file('inputs/mainJs.txt'))
148 }
149 slot('amdJs') {
150 fromLayer('mainAmd') {
151 output('js')
152 }
153 from(layout.projectDirectory.file('inputs/amdJs.txt'))
154 }
155 }
156
157 whenOutgoingConfiguration { publication ->
158 publication.configuration {
159 attributes.attribute(variantAttr, publication.variant.name)
160 }
161 }
162
163 whenOutgoingSlot { publication ->
164 def slotName = publication.artifactSlot.slot.name
165 publication.artifactAttributes {
166 attribute(slotAttr, slotName)
167 }
168 }
169 }
170
171 tasks.register('probe') {
172 dependsOn 'assembleVariantArtifactSlot_v7_browser_s6_mainJs'
173 dependsOn 'assembleVariantArtifactSlot_v7_browser_s5_amdJs'
174
175 doLast {
176 def mainDir = layout.buildDirectory.dir('variant-assemblies/browser/mainJs').get().asFile
177 def amdDir = layout.buildDirectory.dir('variant-assemblies/browser/amdJs').get().asFile
178
179 assert new File(mainDir, 'base.js').exists()
180 assert new File(mainDir, 'amd.js').exists()
181 assert new File(mainDir, 'mainJs.txt').exists()
182
183 assert !new File(amdDir, 'base.js').exists()
184 assert new File(amdDir, 'amd.js').exists()
185 assert new File(amdDir, 'amdJs.txt').exists()
186
187 def elements = configurations.getByName('browserElements')
188 def amdVariant = elements.outgoing.variants.getByName('amdJs')
189
190 println('variantAttr=' + elements.attributes.getAttribute(variantAttr))
191 println('primarySlotAttr=' + elements.outgoing.attributes.getAttribute(slotAttr))
192 println('amdSlotAttr=' + amdVariant.attributes.getAttribute(slotAttr))
193 println('configurations=' + configurations.matching { it.name == 'browserElements' }.collect { it.name }.join(','))
194 println('secondaryVariants=' + elements.outgoing.variants.collect { it.name }.sort().join(','))
195 }
196 }
197 """);
198
199 BuildResult result = runner("probe").build();
200
201 assertTrue(result.getOutput().contains("variantAttr=browser"));
202 assertTrue(result.getOutput().contains("primarySlotAttr=mainJs"));
203 assertTrue(result.getOutput().contains("amdSlotAttr=amdJs"));
204 assertTrue(result.getOutput().contains("configurations=browserElements"));
205 assertTrue(result.getOutput().contains("secondaryVariants=amdJs"));
206 assertTrue(result.task(":probe").getOutcome() == TaskOutcome.SUCCESS);
207 }
208
209 @Test
210 void allowsSingleSlotVariantWithoutExplicitPrimarySlot() throws Exception {
211 writeSettings("variant-artifacts-single-slot");
212 writeBuildFile("""
213 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
214
215 variants.layers.create('main')
216 variants.roles.create('main')
217 variants.variant('browser') {
218 role('main') {
219 layers('main')
220 }
221 }
222
223 variantSources.layer('main') {
224 declareOutputs('types')
225 }
226
227 variantArtifacts {
228 variant('browser') {
229 slot('typesPackage') {
230 fromVariant {
231 output('types')
232 }
233 }
234 }
235 }
236
237 tasks.register('probe') {
238 doLast {
239 variantArtifacts.whenAvailable { ctx ->
240 def browser = objects.named(org.implab.gradle.variants.core.Variant, 'browser')
241 println('primary=' + ctx.findOutgoing(browser).get().primarySlot.get().name)
242 }
243 }
244 }
245 """);
246
247 BuildResult result = runner("probe").build();
248
249 assertTrue(result.getOutput().contains("primary=typesPackage"));
250 }
251
252 @Test
253 void materializesDirectSlotInputsWithoutVariantSourceBindings() throws Exception {
254 writeSettings("variant-artifacts-direct-input");
255 writeFile("inputs/bundle.js", "console.log('bundle')\n");
256 writeBuildFile("""
257 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
258
259 variants.layers.create('main')
260 variants.roles.create('main')
261 variants.variant('browser') {
262 role('main') {
263 layers('main')
264 }
265 }
266
267 variantArtifacts {
268 variant('browser') {
269 primarySlot('bundle') {
270 from(layout.projectDirectory.file('inputs/bundle.js'))
271 }
272 }
273 }
274
275 tasks.register('probe') {
276 dependsOn 'assembleVariantArtifactSlot_v7_browser_s6_bundle'
277
278 doLast {
279 def bundleDir = layout.buildDirectory.dir('variant-assemblies/browser/bundle').get().asFile
280 assert new File(bundleDir, 'bundle.js').exists()
281 }
282 }
283 """);
284
285 BuildResult result = runner("probe").build();
286
287 assertTrue(result.task(":probe").getOutcome() == TaskOutcome.SUCCESS);
288 }
289
290 @Test
291 void combinesDirectAndTopologyAwareSlotInputs() throws Exception {
292 writeSettings("variant-artifacts-combined-inputs");
293 writeFile("inputs/base.js", "console.log('base')\n");
294 writeFile("inputs/marker.txt", "marker\n");
295 writeBuildFile("""
296 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
297
298 variants.layers.create('main')
299 variants.roles.create('main')
300 variants.variant('browser') {
301 role('main') {
302 layers('main')
303 }
304 }
305
306 variantSources.layer('main') {
307 declareOutputs('js')
308 registerOutput('js', layout.projectDirectory.file('inputs/base.js'))
309 }
310
311 variantArtifacts {
312 variant('browser') {
313 primarySlot('bundle') {
314 fromVariant {
315 output('js')
316 }
317 from(layout.projectDirectory.file('inputs/marker.txt'))
318 }
319 }
320 }
321
322 tasks.register('probe') {
323 dependsOn 'assembleVariantArtifactSlot_v7_browser_s6_bundle'
324
325 doLast {
326 def bundleDir = layout.buildDirectory.dir('variant-assemblies/browser/bundle').get().asFile
327 assert new File(bundleDir, 'base.js').exists()
328 assert new File(bundleDir, 'marker.txt').exists()
329 }
330 }
331 """);
332
333 BuildResult result = runner("probe").build();
334
335 assertTrue(result.task(":probe").getOutcome() == TaskOutcome.SUCCESS);
336 }
337
338 @Test
339 void failsOnUnknownVariantReference() throws Exception {
340 writeSettings("variant-artifacts-missing-variant");
341 writeBuildFile("""
342 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
343
344 variants.layers.create('main')
345
346 variantArtifacts {
347 variant('browser') {
348 slot('mainJs') {
349 fromVariant {
350 output('js')
351 }
352 }
353 }
354 }
355 """);
356
357 assertBuildFails("isn't declared", "help");
358 }
359
360 @Test
361 void failsOnUnknownRoleReference() throws Exception {
362 writeSettings("variant-artifacts-missing-role");
363 writeBuildFile("""
364 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
365
366 variants.layers.create('main')
367 variants.roles.create('main')
368 variants.variant('browser') {
369 role('main') {
370 layers('main')
371 }
372 }
373
374 variantArtifacts {
375 variant('browser') {
376 slot('mainJs') {
377 fromRole('test') {
378 output('js')
379 }
380 }
381 }
382 }
383 """);
384
385 assertBuildFails("Role projection for variant 'browser' and role 'test' not found", "help");
386 }
387
388 @Test
389 void failsWhenPrimarySlotIsMissingForMultipleSlots() throws Exception {
390 writeSettings("variant-artifacts-missing-primary");
391 writeBuildFile("""
392 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
393
394 variants.layers.create('main')
395 variants.roles.create('main')
396 variants.variant('browser') {
397 role('main') {
398 layers('main')
399 }
400 }
401
402 variantSources.layer('main') {
403 declareOutputs('types', 'js')
404 }
405
406 variantArtifacts {
407 variant('browser') {
408 slot('typesPackage') {
409 fromVariant {
410 output('types')
411 }
412 }
413 slot('js') {
414 fromVariant {
415 output('js')
416 }
417 }
418 }
419 }
420
421 tasks.register('probe') {
422 doLast {
423 variantArtifacts.whenAvailable { ctx ->
424 def browser = objects.named(org.implab.gradle.variants.core.Variant, 'browser')
425 ctx.findOutgoing(browser).get().primarySlot.get()
426 }
427 }
428 }
429 """);
430
431 assertBuildFails("Multiple slots declared for browser, please specify primary slot explicitly", "probe");
432 }
433
434 @Test
435 void failsOnLayerReferenceOutsideVariantTopology() throws Exception {
436 writeSettings("variant-artifacts-layer-outside-topology");
437 writeBuildFile("""
438 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
439
440 variants.layers.create('mainBase')
441 variants.layers.create('extra')
442 variants.roles.create('main')
443 variants.variant('browser') {
444 role('main') {
445 layers('mainBase')
446 }
447 }
448
449 variantArtifacts {
450 variant('browser') {
451 slot('extraJs') {
452 fromLayer('extra') {
453 output('js')
454 }
455 }
456 }
457 }
458 """);
459
460 assertBuildFails("Compile unit for variant 'browser' and layer 'extra' not found", "help");
461 }
462
463 @Test
464 void preservesPrimaryResolutionAndAllowsSecondaryArtifactSelection() throws Exception {
465 writeFile("settings.gradle", """
466 rootProject.name = 'variant-artifacts-resolution'
467 include 'producer', 'consumer'
468 """);
469 writeFile("producer/inputs/types.d.ts", "export type Foo = string\n");
470 writeFile("producer/inputs/index.js", "export const foo = 'bar'\n");
471 writeBuildFile("""
472 import org.gradle.api.attributes.Attribute
473
474 def variantAttr = Attribute.of('test.variant', String)
475 def slotAttr = Attribute.of('test.slot', String)
476
477 subprojects {
478 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
479 }
480
481 project(':producer') {
482 variants.layers.create('main')
483 variants.roles.create('main')
484 variants.variant('browser') {
485 role('main') {
486 layers('main')
487 }
488 }
489
490 variantSources.layer('main') {
491 declareOutputs('types', 'js')
492 registerOutput('types', layout.projectDirectory.file('inputs/types.d.ts'))
493 registerOutput('js', layout.projectDirectory.file('inputs/index.js'))
494 }
495
496 variantArtifacts {
497 variant('browser') {
498 primarySlot('typesPackage') {
499 fromVariant {
500 output('types')
501 }
502 }
503 slot('js') {
504 fromVariant {
505 output('js')
506 }
507 }
508 }
509
510 whenOutgoingConfiguration { publication ->
511 publication.configuration {
512 attributes.attribute(variantAttr, publication.variant.name)
513 }
514 }
515
516 whenOutgoingSlot { publication ->
517 publication.artifactAttributes {
518 attribute(slotAttr, publication.artifactSlot.slot.name)
519 }
520 }
521 }
522
523 }
524
525 project(':consumer') {
526 configurations {
527 compileView {
528 canBeResolved = true
529 canBeConsumed = false
530 canBeDeclared = true
531 attributes {
532 attribute(variantAttr, 'browser')
533 attribute(slotAttr, 'typesPackage')
534 }
535 }
536 }
537
538 dependencies {
539 compileView project(':producer')
540 }
541
542 tasks.register('probe') {
543 doLast {
544 def compileFiles = configurations.compileView.files.collect { it.name }.sort().join(',')
545 def jsFiles = configurations.compileView.incoming.artifactView {
546 attributes {
547 attribute(slotAttr, 'js')
548 }
549 }.files.files.collect { it.name }.sort().join(',')
550
551 println('compileFiles=' + compileFiles)
552 println('jsFiles=' + jsFiles)
553 }
554 }
555 }
556 """);
557
558 BuildResult result = runner(":consumer:probe").build();
559
560 assertTrue(result.getOutput().contains("compileFiles=typesPackage"));
561 assertTrue(result.getOutput().contains("jsFiles=js"));
562 }
563 }
@@ -1,4 +1,4
1 package org.implab.gradle.common.sources;
1 package org.implab.gradle.variants;
2 2
3 3 import org.gradle.api.GradleException;
4 4 import org.gradle.api.NamedDomainObjectContainer;
@@ -6,6 +6,7 import org.gradle.api.Plugin;
6 6 import org.gradle.api.Project;
7 7 import org.gradle.api.logging.Logger;
8 8 import org.gradle.api.logging.Logging;
9 import org.implab.gradle.variants.sources.GenericSourceSet;
9 10
10 11 /**
11 12 * This plugin creates a {@code sources} extension which is
@@ -49,7 +49,7 public abstract class VariantArtifactsPl
49 49 // wire artifact assemblies to configuration variants
50 50 var assembliesBridge = new ArtifactAssemblyBinder(assemblies);
51 51 var primarySlotConvention = new SingleSlotConvention(providers);
52 var materializationHandler = new MaterializationPolicyHandler(assemblies);
52 var materializationHandler = new MaterializationPolicyHandler();
53 53
54 54 // bind slot assemblies to outgoing variants
55 55 outgoing.configureEach(assembliesBridge::execute);
@@ -11,13 +11,13 import org.gradle.api.Plugin;
11 11 import org.gradle.api.Project;
12 12 import org.implab.gradle.common.core.lang.Deferred;
13 13 import org.implab.gradle.common.core.lang.Strings;
14 import org.implab.gradle.common.sources.GenericSourceSet;
15 14 import org.implab.gradle.variants.core.Layer;
16 15 import org.implab.gradle.variants.core.Variant;
17 16 import org.implab.gradle.variants.core.VariantsExtension;
18 17 import org.implab.gradle.variants.core.VariantsView;
19 18 import org.implab.gradle.variants.sources.CompileUnit;
20 19 import org.implab.gradle.variants.sources.CompileUnitsView;
20 import org.implab.gradle.variants.sources.GenericSourceSet;
21 21 import org.implab.gradle.variants.sources.RoleProjectionsView;
22 22 import org.implab.gradle.variants.sources.VariantSourcesContext;
23 23 import org.implab.gradle.variants.sources.VariantSourcesExtension;
@@ -6,6 +6,7 import org.gradle.api.Plugin;
6 6 import org.gradle.api.Project;
7 7 import org.gradle.api.model.ObjectFactory;
8 8 import org.implab.gradle.common.core.lang.Deferred;
9 import org.implab.gradle.internal.IdentityContainerFactory;
9 10 import org.implab.gradle.variants.core.Layer;
10 11 import org.implab.gradle.variants.core.Role;
11 12 import org.implab.gradle.variants.core.Variant;
@@ -44,8 +45,8 public abstract class VariantsPlugin imp
44 45
45 46 public DefaultVariantsExtension(ObjectFactory objectFactory) {
46 47 this.objectFactory = objectFactory;
47 this.layers = objectFactory.domainObjectContainer(Layer.class);
48 this.roles = objectFactory.domainObjectContainer(Role.class);
48 this.layers = IdentityContainerFactory.create(objectFactory, Layer.class);
49 this.roles = IdentityContainerFactory.create(objectFactory, Role.class);
49 50 this.variantDefinitions = objectFactory.domainObjectContainer(VariantDefinition.class);
50 51 }
51 52
@@ -1,7 +1,6
1 1 package org.implab.gradle.variants.artifacts;
2 2
3 3 import org.gradle.api.Action;
4 import org.gradle.api.Task;
5 4 import org.gradle.api.attributes.AttributeContainer;
6 5 import groovy.lang.Closure;
7 6 import org.implab.gradle.common.core.lang.Closures;
@@ -22,13 +21,6 public interface OutgoingConfigurationSl
22 21 ArtifactSlot getArtifactSlot();
23 22
24 23 /**
25 * Returns the assembly backing the published slot.
26 *
27 * @return slot assembly
28 */
29 ArtifactAssembly getAssembly();
30
31 /**
32 24 * Returns whether this slot is the primary outgoing artifact set of the variant.
33 25 *
34 26 * @return {@code true} for the primary slot
@@ -36,19 +28,6 public interface OutgoingConfigurationSl
36 28 boolean isPrimary();
37 29
38 30 /**
39 * Configures the task producing the slot artifact.
40 *
41 * @param action task configuration action
42 */
43 default void assemblyTask(Action<? super Task> action) {
44 getAssembly().getAssemblyTask().configure(action);
45 }
46
47 default void assemblyTask(Closure<?> closure) {
48 assemblyTask(Closures.action(closure));
49 }
50
51 /**
52 31 * Configures attributes of this slot publication.
53 32 *
54 33 * @param action artifact attribute configuration action
@@ -5,8 +5,6 import org.gradle.api.Action;
5 5 import org.gradle.api.artifacts.Configuration;
6 6 import org.gradle.api.attributes.AttributeContainer;
7 7 import org.implab.gradle.internal.ReplayableQueue;
8 import org.implab.gradle.variants.artifacts.ArtifactAssemblies;
9 import org.implab.gradle.variants.artifacts.ArtifactAssembly;
10 8 import org.implab.gradle.variants.artifacts.ArtifactSlot;
11 9 import org.implab.gradle.variants.artifacts.OutgoingConfigurationSlotSpec;
12 10 import org.implab.gradle.variants.artifacts.OutgoingConfigurationSpec;
@@ -18,8 +16,7 import org.implab.gradle.variants.core.V
18 16 *
19 17 * <p>Materialization is the phase where the plugin interprets variant artifact
20 18 * declarations as Gradle outgoing publication state: a consumable configuration,
21 * its primary artifact set, secondary artifact variants, attributes, and backing
22 * assembly tasks.
19 * its primary artifact set, secondary artifact variants, and attributes.
23 20 *
24 21 * <p>The handler provides extension points for customizing the materialized
25 22 * Gradle-facing objects. These hooks intentionally expose Gradle API objects.
@@ -33,13 +30,10 import org.implab.gradle.variants.core.V
33 30 @NonNullByDefault
34 31 public class MaterializationPolicyHandler implements Action<OutgoingVariant> {
35 32
36 private final ArtifactAssemblies resolver;
37
38 33 private final ReplayableQueue<OutgoingConfigurationSpec> variantMaterialization = new ReplayableQueue<>();
39 34 private final ReplayableQueue<OutgoingConfigurationSlotSpec> slotMaterialization = new ReplayableQueue<>();
40 35
41 public MaterializationPolicyHandler(ArtifactAssemblies resolver) {
42 this.resolver = resolver;
36 public MaterializationPolicyHandler() {
43 37 }
44 38
45 39 @Override
@@ -57,12 +51,12 public class MaterializationPolicyHandle
57 51
58 52 slotMaterialized(new ArtifactSlot(variant, primarySlot), true, outgoing.getAttributes());
59 53
60 outgoing.getVariants().configureEach(variantConfiguration -> {
61 var slotName = variantConfiguration.getName();
62 var slot = slots.findByName(slotName);
63 if (slot != null) {
54 slots.forEach(slot -> {
55 if (slot.equals(primarySlot))
56 return;
57
58 var variantConfiguration = outgoing.getVariants().getByName(slot.getName());
64 59 slotMaterialized(new ArtifactSlot(variant, slot), false, variantConfiguration.getAttributes());
65 }
66 60 });
67 61 });
68 62 };
@@ -104,11 +98,6 public class MaterializationPolicyHandle
104 98 }
105 99
106 100 @Override
107 public ArtifactAssembly getAssembly() {
108 return resolver.require(slot);
109 }
110
111 @Override
112 101 public void artifactAttributes(Action<? super AttributeContainer> action) {
113 102 action.execute(attributes);
114 103 }
@@ -14,6 +14,7 import org.gradle.api.artifacts.Configur
14 14 import org.gradle.api.artifacts.ConfigurationContainer;
15 15 import org.gradle.api.model.ObjectFactory;
16 16 import org.gradle.api.provider.Property;
17 import org.implab.gradle.internal.IdentityContainerFactory;
17 18 import org.implab.gradle.internal.ReplayableQueue;
18 19 import org.implab.gradle.variants.artifacts.OutgoingVariant;
19 20 import org.implab.gradle.variants.artifacts.Slot;
@@ -92,7 +93,7 public class OutgoingRegistry {
92 93 NamedDomainObjectProvider<? extends Configuration> configurationProvider) {
93 94 this.variant = variant;
94 95 this.configurationProvider = configurationProvider;
95 this.slots = objects.domainObjectContainer(Slot.class);
96 this.slots = IdentityContainerFactory.create(objects, Slot.class);
96 97 this.primarySlot = objects.property(Slot.class);
97 98 primarySlot.finalizeValueOnRead();
98 99 }
@@ -75,4 +75,4 public final class CompileUnitsView {
75 75 Objects.requireNonNull(variantsView, "variantsView can't be null");
76 76 return new CompileUnitsView(variantsView);
77 77 }
78 } No newline at end of file
78 }
@@ -1,4 +1,4
1 package org.implab.gradle.common.sources;
1 package org.implab.gradle.variants.sources;
2 2
3 3 import java.io.File;
4 4 import java.nio.file.Paths;
@@ -80,4 +80,4 public final class RoleProjectionsView {
80 80 public static RoleProjectionsView of(VariantsView variantsView) {
81 81 return new RoleProjectionsView(variantsView);
82 82 }
83 } No newline at end of file
83 }
@@ -1,7 +1,6
1 1 package org.implab.gradle.variants.sources;
2 2
3 3 import org.gradle.api.NamedDomainObjectProvider;
4 import org.implab.gradle.common.sources.GenericSourceSet;
5 4
6 5 /**
7 6 * Materializes symbolic source set names into actual {@link GenericSourceSet}
@@ -1,7 +1,6
1 1 package org.implab.gradle.variants.sources;
2 2
3 3 import org.gradle.api.Action;
4 import org.implab.gradle.common.sources.GenericSourceSet;
5 4 import org.implab.gradle.variants.core.Layer;
6 5 import org.implab.gradle.variants.core.Variant;
7 6 import org.implab.gradle.variants.core.VariantsView;
@@ -3,7 +3,6 package org.implab.gradle.variants.sourc
3 3 import org.eclipse.jdt.annotation.NonNullByDefault;
4 4 import org.gradle.api.Action;
5 5 import org.implab.gradle.common.core.lang.Closures;
6 import org.implab.gradle.common.sources.GenericSourceSet;
7 6 import groovy.lang.Closure;
8 7
9 8 @NonNullByDefault
@@ -4,12 +4,12 import java.util.HashMap;
4 4 import java.util.Map;
5 5 import org.gradle.api.Action;
6 6 import org.gradle.api.NamedDomainObjectProvider;
7 import org.implab.gradle.common.sources.GenericSourceSet;
8 7 import org.implab.gradle.variants.core.Layer;
9 8 import org.implab.gradle.variants.core.Variant;
10 9 import org.implab.gradle.variants.core.VariantsView;
11 10 import org.implab.gradle.variants.sources.CompileUnit;
12 11 import org.implab.gradle.variants.sources.CompileUnitsView;
12 import org.implab.gradle.variants.sources.GenericSourceSet;
13 13 import org.implab.gradle.variants.sources.RoleProjectionsView;
14 14 import org.implab.gradle.variants.sources.SourceSetMaterializer;
15 15 import org.implab.gradle.variants.sources.VariantSourcesContext;
@@ -14,10 +14,10 import org.gradle.api.Action;
14 14 import org.gradle.api.Named;
15 15 import org.gradle.api.logging.Logger;
16 16 import org.gradle.api.logging.Logging;
17 import org.implab.gradle.common.sources.GenericSourceSet;
18 17 import org.implab.gradle.variants.core.Layer;
19 18 import org.implab.gradle.variants.core.Variant;
20 19 import org.implab.gradle.variants.sources.CompileUnit;
20 import org.implab.gradle.variants.sources.GenericSourceSet;
21 21
22 22 @NonNullByDefault
23 23 public class SourceSetConfigurationRegistry {
@@ -7,7 +7,7 import java.util.function.Consumer;
7 7 import org.gradle.api.NamedDomainObjectContainer;
8 8 import org.gradle.api.model.ObjectFactory;
9 9 import org.implab.gradle.common.core.lang.Deferred;
10 import org.implab.gradle.common.sources.GenericSourceSet;
10 import org.implab.gradle.variants.sources.GenericSourceSet;
11 11
12 12 public class SourceSetRegistry {
13 13 private final Map<String, Deferred<GenericSourceSet>> materialized = new HashMap<>();
@@ -1,1 +1,1
1 implementation-class=org.implab.gradle.common.sources.VariantArtifactsPlugin
1 implementation-class=org.implab.gradle.variants.VariantArtifactsPlugin
@@ -1,1 +1,1
1 implementation-class=org.implab.gradle.common.sources.VariantsSourcesPlugin
1 implementation-class=org.implab.gradle.variants.VariantSourcesPlugin
@@ -1,1 +1,1
1 implementation-class=org.implab.gradle.common.sources.VariantsPlugin
1 implementation-class=org.implab.gradle.variants.VariantsPlugin
@@ -12,7 +12,8 import java.util.stream.Collectors;
12 12
13 13 import org.gradle.testkit.runner.GradleRunner;
14 14 import org.gradle.testkit.runner.UnexpectedBuildFailure;
15 import org.implab.gradle.common.sources.GenericSourceSet;
15 import org.implab.gradle.common.core.lang.Deferred;
16 import org.implab.gradle.variants.sources.GenericSourceSet;
16 17 import org.junit.jupiter.api.io.TempDir;
17 18
18 19 abstract class AbstractFunctionalTest {
@@ -64,7 +65,9 abstract class AbstractFunctionalTest {
64 65 try {
65 66 var files = new LinkedHashSet<File>();
66 67 files.add(codeSource(VariantSourcesPlugin.class));
68 files.add(resourceRoot("META-INF/gradle-plugins/org.implab.gradle-variants.properties"));
67 69 files.add(codeSource(GenericSourceSet.class));
70 files.add(codeSource(Deferred.class));
68 71 return List.copyOf(files);
69 72 } catch (Exception e) {
70 73 throw new RuntimeException("Unable to build plugin classpath for test", e);
@@ -75,7 +78,19 abstract class AbstractFunctionalTest {
75 78 return Path.of(type.getProtectionDomain().getCodeSource().getLocation().toURI()).toFile();
76 79 }
77 80
78 private void writeFile(String relativePath, String content) throws IOException {
81 private static File resourceRoot(String resourceName) throws Exception {
82 var url = VariantSourcesPlugin.class.getClassLoader().getResource(resourceName);
83 if (url == null)
84 throw new IllegalStateException("Classpath resource isn't found: " + resourceName);
85
86 var path = Path.of(url.toURI());
87 var segments = resourceName.split("/").length;
88 for (var i = 0; i < segments; i++)
89 path = path.getParent();
90 return path.toFile();
91 }
92
93 protected void writeFile(String relativePath, String content) throws IOException {
79 94 Path path = testProjectDir.resolve(relativePath);
80 95 Files.createDirectories(path.getParent());
81 96 Files.writeString(path, content);
@@ -35,7 +35,7 class VariantSourcesPluginFunctionalTest
35 35 def browser = ctx.variants.variants.find { it.name == 'browser' }
36 36 def production = ctx.variants.roles.find { it.name == 'production' }
37 37 def mainLayer = ctx.variants.layers.find { it.name == 'main' }
38 def projection = ctx.roleProjections.getProjection(browser, production)
38 def projection = ctx.roleProjections.requireProjection(browser, production)
39 39 def unit = ctx.compileUnits.requireUnit(browser, mainLayer)
40 40
41 41 def left = ctx.sourceSets.getSourceSet(unit)
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
This diff has been collapsed as it changes many lines, (608 lines changed) Show them Hide them
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now