##// END OF EJS Templates
variants: stabilize artifact slot materialization
cin -
r55:a06b08ec0a7f default
parent child
Show More
@@ -1,112 +1,112
1 1 package org.implab.gradle.variants;
2 2
3 3 import org.gradle.api.Action;
4 4 import org.gradle.api.Plugin;
5 5 import org.gradle.api.Project;
6 6 import org.implab.gradle.common.core.lang.Deferred;
7 7 import org.implab.gradle.variants.artifacts.OutgoingConfigurationSpec;
8 8 import org.implab.gradle.variants.artifacts.OutgoingVariantsContext;
9 9 import org.implab.gradle.variants.artifacts.VariantArtifactsExtension;
10 10 import org.implab.gradle.variants.artifacts.VariantArtifactsSpec;
11 11 import org.implab.gradle.variants.artifacts.internal.ArtifactAssemblyBinder;
12 12 import org.implab.gradle.variants.artifacts.internal.ArtifactAssemblyHandler;
13 13 import org.implab.gradle.variants.artifacts.internal.ArtifactAssemblyRegistry;
14 14 import org.implab.gradle.variants.artifacts.internal.DefaultOutgoingVariantsContext;
15 15 import org.implab.gradle.variants.artifacts.internal.MaterializationPolicyHandler;
16 16 import org.implab.gradle.variants.artifacts.internal.OutgoingRegistry;
17 17 import org.implab.gradle.variants.artifacts.internal.SingleSlotConvention;
18 18 import org.implab.gradle.variants.core.Variant;
19 19 import org.implab.gradle.variants.core.VariantsExtension;
20 20 import org.implab.gradle.variants.sources.VariantSourcesExtension;
21 21 import org.implab.gradle.variants.artifacts.OutgoingConfigurationSlotSpec;
22 22
23 23 public abstract class VariantArtifactsPlugin implements Plugin<Project> {
24 24
25 25 @Override
26 26 public void apply(Project target) {
27 27 var extensions = target.getExtensions();
28 28 var objects = target.getObjects();
29 29 var providers = target.getProviders();
30 30 var plugins = target.getPlugins();
31 31 var configurations = target.getConfigurations();
32 32 var tasks = target.getTasks();
33 33 var layout = target.getLayout();
34 34
35 35 // Apply the main VariantsPlugin to ensure the core variant model is available.
36 36 plugins.apply(VariantsPlugin.class);
37 37 plugins.apply(VariantSourcesPlugin.class);
38 38 // Access the VariantsExtension to configure variant sources.
39 39 var variantsExtension = extensions.getByType(VariantsExtension.class);
40 40 var sourcesExtension = extensions.getByType(VariantSourcesExtension.class);
41 41
42 42 var deferred = new Deferred<OutgoingVariantsContext>();
43 43
44 44 variantsExtension.whenFinalized(variants -> {
45 45
46 46 var outgoing = new OutgoingRegistry(configurations, objects, variants.getVariants());
47 47 var assemblies = new ArtifactAssemblyRegistry();
48 48
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();
52 var materializationHandler = new MaterializationPolicyHandler(objects);
53 53
54 54 // bind slot assemblies to outgoing variants
55 55 outgoing.configureEach(assembliesBridge::execute);
56 56 // apply primary slot convention when outgoing variant has a single slot
57 57 outgoing.configureEach(primarySlotConvention::execute);
58 58 // apply materialization policy hooks to outgoing variants
59 59 outgoing.configureEach(materializationHandler::execute);
60 60
61 61 sourcesExtension.whenFinalized(sources -> {
62 62 var assemblyHandler = new ArtifactAssemblyHandler(
63 63 objects,
64 64 tasks,
65 65 assemblies,
66 66 sources.getCompileUnits(),
67 67 sources.getRoleProjections(),
68 68 sources.getSourceSets());
69 69 assemblyHandler.getAssembliesDirectory().set(layout.getBuildDirectory().dir("variant-assemblies"));
70 70
71 71 deferred.resolve(new DefaultOutgoingVariantsContext(
72 72 assemblies,
73 73 outgoing,
74 74 assemblyHandler,
75 75 materializationHandler));
76 76 });
77 77
78 78 });
79 79
80 80 var variantArtifacts = new VariantArtifactsExtension() {
81 81
82 82 @Override
83 83 public void variant(String variantName, Action<? super VariantArtifactsSpec> action) {
84 84 deferred.whenResolved(context -> {
85 85 var variant = objects.named(Variant.class, variantName);
86 86 context.configureVariant(variant, action);
87 87 });
88 88 }
89 89
90 90 @Override
91 91 public void whenAvailable(Action<? super OutgoingVariantsContext> action) {
92 92 deferred.whenResolved(handler -> action.execute(handler));
93 93 }
94 94
95 95 @Override
96 96 public void whenOutgoingConfiguration(Action<? super OutgoingConfigurationSpec> action) {
97 97 deferred.whenResolved(registry -> registry.whenOutgoingConfiguration(action));
98 98
99 99 }
100 100
101 101 @Override
102 102 public void whenOutgoingSlot(Action<? super OutgoingConfigurationSlotSpec> action) {
103 103 deferred.whenResolved(registry -> registry.whenOutgoingSlot(action));
104 104 }
105 105
106 106 };
107 107
108 108 extensions.add(VariantArtifactsExtension.class, "variantArtifacts", variantArtifacts);
109 109
110 110 }
111 111
112 112 }
@@ -1,56 +1,61
1 1 package org.implab.gradle.variants.artifacts;
2 2
3 3 import java.util.Optional;
4 4
5 5 import org.eclipse.jdt.annotation.NonNullByDefault;
6 6 import org.gradle.api.Action;
7 7 import org.gradle.api.InvalidUserDataException;
8 8
9 9 /**
10 10 * Resolves stateful slot assemblies from cheap slot identities.
11 11 *
12 12 * <p>
13 13 * The returned assembly is a materialized build-model handle. It may expose
14 14 * lazy Gradle providers, but
15 15 * it is no longer an identity object suitable for replayable discovery.
16 16 */
17 17 @NonNullByDefault
18 18 public interface ArtifactAssemblies {
19 19 /**
20 20 * Resolves the assembly for the given slot.
21 21 *
22 22 * <p>
23 23 * This call materializes the build-facing body of the slot from its identity.
24 24 *
25 25 * @param slot slot identity inside a variant outgoing contract
26 26 * @return assembly handle for the slot
27 27 */
28 28 default ArtifactAssembly require(ArtifactSlot slot) {
29 29 return find(slot)
30 30 .orElseThrow(() -> new InvalidUserDataException("Artifact assembly '" + slot + "' isn't registered"));
31 31 }
32 32
33 33 /**
34 34 * Registers a configuration action for the assembly of the given slot.
35 35 *
36 36 * <p>If the assembly is already registered, the action is invoked immediately.
37 37 * Otherwise, it is queued and invoked when slot materialization registers the
38 38 * assembly handle.
39 39 *
40 40 * @param slot slot identity inside a variant outgoing contract
41 41 * @param action assembly configuration action
42 42 */
43 43 void when(ArtifactSlot slot, Action<? super ArtifactAssembly> action);
44 44
45 45 /**
46 46 * Adds global configuration action for all materialized assemblies. If some assemblies are
47 47 * already registered, the action will be invoked for them immediately. For assemblies that
48 48 * are materialized later, the action will be invoked when they are registered.
49 49 *
50 50 * @param action assembly configuration action.
51 51 */
52 52 void configureEach(Action<? super ArtifactAssembly> action);
53 53
54 /** Π˜Ρ‰Π΅Ρ‚ зарСгистрированный слот */
54 /**
55 * Finds a registered assembly for the given slot identity.
56 *
57 * @param slot slot identity inside a variant outgoing contract
58 * @return registered assembly, if the slot body has already been materialized
59 */
55 60 Optional<ArtifactAssembly> find(ArtifactSlot slot);
56 61 }
@@ -1,43 +1,43
1 1 package org.implab.gradle.variants.artifacts;
2 2
3 3 import org.gradle.api.Action;
4 4 import org.gradle.api.artifacts.Configuration;
5 5 import org.implab.gradle.common.core.lang.Closures;
6 6 import org.implab.gradle.variants.core.Variant;
7 7
8 8 import groovy.lang.Closure;
9 9
10 10 /**
11 11 * Materialized root outgoing configuration of a variant.
12 12 *
13 13 * <p>This is a variant-level publication hook. Slot-specific publication state is exposed separately via
14 * {@link OutgoingArtifactSlotSpec}.
14 * {@link OutgoingConfigurationSlotSpec}.
15 15 */
16 16 public interface OutgoingConfigurationSpec {
17 17 /**
18 18 * Returns the variant whose outgoing configuration is represented here.
19 19 *
20 20 * @return variant identity
21 21 */
22 22 Variant getVariant();
23 23
24 24 /**
25 25 * Returns the root consumable outgoing configuration of the variant.
26 26 *
27 27 * @return outgoing configuration
28 28 */
29 29 Configuration getConfiguration();
30 30
31 31 /**
32 32 * Applies a configuration action to the root outgoing configuration.
33 33 *
34 34 * @param action configuration action
35 35 */
36 36 default void configuration(Action<? super Configuration> action) {
37 37 action.execute(getConfiguration());
38 38 }
39 39
40 40 default void configuration(Closure<?> closure) {
41 41 configuration(Closures.action(closure));
42 42 }
43 43 }
@@ -1,50 +1,63
1 1 package org.implab.gradle.variants.artifacts;
2 2
3 3 import org.gradle.api.Action;
4 4 import org.gradle.api.NamedDomainObjectContainer;
5 5 import org.gradle.api.NamedDomainObjectProvider;
6 6 import org.gradle.api.artifacts.Configuration;
7 7 import org.gradle.api.provider.Property;
8 8 import org.implab.gradle.variants.core.Variant;
9 9
10 10 /**
11 * ΠžΠΏΠΈΡΡ‹Π²Π°Π΅Ρ‚ ΠΈΡΡ…ΠΎΠ΄ΡΡ‰ΡƒΡŽ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ Π²Π°Ρ€ΠΈΠ°Π½Ρ‚Π°
11 * Plugin model object for one variant-level outgoing contract.
12 12 *
13 * Π—Π°Π΄Π°Π΅Ρ‚ связь ΠΌΠ΅ΠΆΠ΄Ρƒ модСлью Π²Π°Ρ€ΠΈΠ°Π½Ρ‚ΠΎΠ² ΠΈ модСлью ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΉ gradle Ρ‡Π΅Ρ€Π΅Π·
14 * свойство {@link #getConfiguration()}. Π’Π°ΠΊΠΆΠ΅ Π·Π°Π΄Π°Π΅Ρ‚ ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½ΡƒΡŽ ось слотов
15 * ΠΏΡƒΠ±Π»ΠΈΠΊΠ°Ρ†ΠΈΠΈ, Π½ΠΎ Π½Π΅ Π·Π°Π΄Π°Π΅Ρ‚ ΠΏΡ€Π°Π²ΠΈΠ» связывания этих слотов с самой ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠ΅ΠΉ
16 * ΠΈ ΠΈΡ… содСрТимым. Π‘Π°ΠΌΡ‹ΠΉ простой Π²Π°Ρ€ΠΈΠ°Π½Ρ‚ это {@link ArtifactAssemblies}.
13 * <p>An outgoing variant connects a core {@link Variant} identity with the lazy
14 * Gradle consumable configuration registered for that variant. It also exposes a
15 * live container of slot identities. Slot identities are only declarations and do
16 * not imply that a Gradle outgoing artifact variant or an {@link ArtifactAssembly}
17 * has been materialized.
17 18 */
18 19 public interface OutgoingVariant {
19 20 /**
20 * Π˜ΡΡ…ΠΎΠ΄Π½Ρ‹ΠΉ Π²Π°Ρ€ΠΈΠ°Π½Ρ‚ для ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠ³ΠΎ строится Outgoing конфигурация
21 * Returns the variant that owns this outgoing contract.
22 *
23 * @return variant identity
21 24 */
22 25 Variant getVariant();
23 26
24 27 /**
25 * ΠŸΡ€ΠΎΠ²Π°ΠΉΠ΄Π΅Ρ€ зарСгистрированной ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ
28 * Returns the provider of the registered consumable outgoing configuration.
29 *
30 * @return outgoing configuration provider
26 31 */
27 32 NamedDomainObjectProvider<? extends Configuration> getConfiguration();
28 33
34 /**
35 * Configures the registered outgoing configuration.
36 *
37 * @param action configuration action
38 */
29 39 default void configureOutgoing(Action<? super Configuration> action) {
30 40 getConfiguration().configure(action);
31 41 }
32 42
33 43 /**
34 * Π‘Π»ΠΎΡ‚Ρ‹ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ, данная коллСкция Тивая, ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ для
35 * получСния ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ ΠΎΠ± ΠΎΠ±ΡŠΡΠ²Π»Π΅Π½Π½Ρ‹Ρ… слотах, Π½ΠΎ эти слоты Π½Π΅
36 * обязаны Π±Ρ‹Ρ‚ΡŒ сконфигурированы, Ρ‚.Π΅. это Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Identity.
44 * Returns the live slot identity container.
45 *
46 * <p>This collection is intended for discovery and selection. Slot presence does
47 * not guarantee that the slot has a configured assembly body or a materialized
48 * Gradle outgoing artifact variant.
37 49 *
38 50 * @see {@link ArtifactSlot}
39 51 */
40 52 NamedDomainObjectContainer<Slot> getSlots();
41 53
42 54 /**
43 * Основной Π½Π°Π±ΠΎΡ€ Π°Ρ€Ρ‚Π΅Ρ„Π°ΠΊΡ‚ΠΎΠ² (primary variant) для исходящСй ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ
55 * Returns the primary slot property.
44 56 *
45 * <p>
46 * Если Π² свойствС {@link #getSlots()} Π΅ΡΡ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΎΠ΄ΠΈΠ½ слой, Ρ‚ΠΎ ΠΏΠΎ ΠΊΠΎΠ½Π²Π΅Π½Ρ†ΠΈΠΈ ΠΎΠ½
47 * считаСтся Ρ‚Π°ΠΊΠΆΠ΅ основным.
57 * <p>If exactly one slot is declared, the single-slot convention uses that slot as
58 * the primary one. Reading this property finalizes the selected primary slot.
59 *
60 * @return primary slot property
48 61 */
49 62 Property<Slot> getPrimarySlot();
50 63 }
@@ -1,36 +1,87
1 1 package org.implab.gradle.variants.artifacts;
2 2
3 3 import java.util.Optional;
4 4
5 5 import org.gradle.api.Action;
6 6 import org.gradle.api.InvalidUserDataException;
7 7 import org.implab.gradle.variants.core.Variant;
8 8
9 9 /**
10 * ΠšΠΎΠ½Ρ‚Π΅ΠΊΡΡ‚ Ρ€Π°Π±ΠΎΡ‚Ρ‹ с Π²Π°Ρ€ΠΈΠ°Π½Ρ‚Π°ΠΌΠΈ ΠΏΡƒΠ±Π»ΠΈΠΊΠ°Ρ†ΠΈΠΈ, становится доступным послС
11 * Ρ„ΠΈΠ½Π°Π»ΠΈΠ·Π°Ρ†ΠΈΠΈ ΠΌΠΎΠ΄Π΅Π»ΠΈ Π²Π°Ρ€ΠΈΠ°Π½Ρ‚ΠΎΠ². ЀактичСски являСтся ΠΆΠΈΠ²ΠΎΠΉ модСлью
10 * Live context for declaring and observing variant outgoing publications.
11 *
12 * <p>The context becomes available after the core variant model has been finalized.
13 * It owns variant-level outgoing declarations, assembly lookup, and hooks for
14 * materialized Gradle-facing publication state.
12 15 */
13 16 public interface OutgoingVariantsContext {
14 17
18 /**
19 * Returns the assembly lookup service.
20 *
21 * <p>Assemblies are stateful slot bodies resolved from cheap {@link ArtifactSlot}
22 * identities.
23 *
24 * @return assembly lookup service
25 */
15 26 ArtifactAssemblies getAssemblies();
16 27
28 /**
29 * Configures artifact slots of the given variant.
30 *
31 * @param variant variant identity
32 * @param action variant artifact declaration
33 */
17 34 void configureVariant(Variant variant, Action<? super VariantArtifactsSpec> action);
18 35
19 36 /**
20 * Replayable hook для всСх ΠΎΠ±ΡŠΡΠ²Π»Π΅Π½Π½Ρ‹Ρ… ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΉ
37 * Registers a replayable action for declared outgoing variants.
38 *
39 * <p>The action receives the plugin model object, not necessarily a materialized
40 * Gradle publication. Use {@link #whenOutgoingConfiguration(Action)} and
41 * {@link #whenOutgoingSlot(Action)} for materialized Gradle-facing state.
42 *
43 * @param action outgoing variant action
21 44 */
22 45 void configureEach(Action<? super OutgoingVariant> action);
23 46
47 /**
48 * Finds the outgoing model for the given variant.
49 *
50 * @param variant variant identity
51 * @return outgoing model, if declared
52 */
24 53 Optional<OutgoingVariant> findOutgoing(Variant variant);
25 54
55 /**
56 * Requires the outgoing model for the given variant.
57 *
58 * @param variant variant identity
59 * @return outgoing model
60 * @throws InvalidUserDataException if the outgoing variant is not declared
61 */
26 62 default OutgoingVariant requireOutgoing(Variant variant) {
27 63 return findOutgoing(variant)
28 64 .orElseThrow(() -> new InvalidUserDataException("Outgoing variant '" + variant + "' isn't registered"));
29 65 }
30 66
67 /**
68 * Registers a replayable action for materialized variant-level outgoing
69 * configurations.
70 *
71 * @param action materialized outgoing configuration action
72 */
31 73 void whenOutgoingConfiguration(Action<? super OutgoingConfigurationSpec> action);
32 74
75 /**
76 * Registers a replayable action for materialized slot publications.
77 *
78 * <p>Slot publication hooks follow the Gradle outgoing publication model. A
79 * declared slot identity by itself does not guarantee that a slot publication has
80 * been materialized.
81 *
82 * @param action materialized slot publication action
83 */
33 84 void whenOutgoingSlot(Action<? super OutgoingConfigurationSlotSpec> action);
34 85
35 86
36 87 }
@@ -1,40 +1,38
1 1 package org.implab.gradle.variants.artifacts;
2 2
3 3 import org.gradle.api.Action;
4 4 import groovy.lang.Closure;
5 5 import org.implab.gradle.common.core.lang.Closures;
6 6
7 7 /**
8 8 * DSL model for declaring slots of a single variant.
9 9 *
10 10 * <p>The surrounding variant defines the external outgoing contract. Slots declared here define artifact
11 11 * sets within that contract. If a variant exposes more than one slot, one of them is expected to be the
12 12 * primary slot.
13 13 */
14 14 public interface VariantArtifactsSpec {
15 15 /**
16 16 * Declares a non-primary slot of the current variant.
17 17 *
18 18 * @param name slot name
19 19 * @param action slot declaration
20 * @return slot identity
21 20 */
22 21 void slot(String name, Action<? super ArtifactAssemblySpec> action);
23 22
24 23 default void slot(String name, Closure<?> closure) {
25 24 slot(name, Closures.action(closure));
26 25 }
27 26
28 27 /**
29 28 * Declares the primary slot of the current variant.
30 29 *
31 30 * @param name slot name
32 31 * @param action slot declaration
33 * @return slot identity
34 32 */
35 33 void primarySlot(String name, Action<? super ArtifactAssemblySpec> action);
36 34
37 35 default void primarySlot(String name, Closure<?> closure) {
38 36 primarySlot(name, Closures.action(closure));
39 37 }
40 38 }
@@ -1,56 +1,56
1 1 package org.implab.gradle.variants.artifacts.internal;
2 2
3 3 import org.eclipse.jdt.annotation.NonNullByDefault;
4 4 import org.gradle.api.Action;
5 5 import org.implab.gradle.variants.artifacts.ArtifactAssemblies;
6 6 import org.implab.gradle.variants.artifacts.ArtifactSlot;
7 7 import org.implab.gradle.variants.artifacts.OutgoingVariant;
8 8
9 9 /**
10 * БвязываСт описаниС исходящих ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΉ gradle ΠΈ сборку содСрТимого слотов
11 * ΠΈΠ· {@link ArtifactAssemblies}
10 * Binds materialized slot assemblies to Gradle outgoing publications.
12 11 */
13 12 @NonNullByDefault
14 13 public class ArtifactAssemblyBinder implements Action<OutgoingVariant> {
15 14
16 15 private final ArtifactAssemblies resolver;
17 16
18 17 public ArtifactAssemblyBinder(ArtifactAssemblies resolver) {
19 18 this.resolver = resolver;
20 19 }
21 20
22 21 @Override
23 22 public void execute(OutgoingVariant outgoingVariant) {
24 23 var slots = outgoingVariant.getSlots();
25 24 var primarySlotProvider = outgoingVariant.getPrimarySlot();
26 25 var variant = outgoingVariant.getVariant();
27 26
28 // связываСм ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ
27 // Bind publication state when the owning configuration is materialized.
29 28 outgoingVariant.configureOutgoing(configuration -> {
30 29 var primarySlot = primarySlotProvider.get();
31 30 var outgoing = configuration.getOutgoing();
32 31
33 // связываСм основной Π²Π°Ρ€ΠΈΠ°Π½Ρ‚ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ
32 // Bind the primary artifact set to the root outgoing configuration.
34 33 resolver.when(
35 34 new ArtifactSlot(variant, primarySlot),
36 35 assembly -> outgoing.artifact(assembly.getArtifact()));
37 36
38 // для всСх ΠΎΠ±ΡŠΡΠ²Π»Π΅Π½Π½Ρ‹Ρ… слотов
37 // Bind non-primary slots to Gradle secondary artifact variants.
39 38 slots.all(slot -> {
40 // ΠΊΡ€ΠΎΠΌΠ΅ основного
41 39 if (slot.equals(primarySlot))
42 40 return;
43 41
44 // связываСм Π°Ρ€Ρ‚Π΅Ρ„Π°ΠΊΡ‚Ρ‹
45 42 resolver.when(
46 43 new ArtifactSlot(variant, slot),
44 // Gradle artifact variants must be created while the owning
45 // configuration is being materialized. Lazy registration may
46 // otherwise be realized only after dependency resolution starts.
47 47 assembly -> outgoing.getVariants()
48 .register(slot.getName())
49 .configure(cv -> cv.artifact(assembly.getArtifact())));
48 .create(slot.getName())
49 .artifact(assembly.getArtifact()));
50 50 });
51 51 });
52 52 }
53 53
54 54
55 55
56 56 }
@@ -1,218 +1,202
1 1 package org.implab.gradle.variants.artifacts.internal;
2 2
3 3 import java.util.HashMap;
4 4 import java.util.HashSet;
5 5 import java.util.Map;
6 6 import java.util.Set;
7 7
8 8 import org.eclipse.jdt.annotation.NonNullByDefault;
9 9 import org.gradle.api.Action;
10 10 import org.gradle.api.file.ConfigurableFileCollection;
11 11 import org.gradle.api.file.Directory;
12 12 import org.gradle.api.file.DirectoryProperty;
13 13 import org.gradle.api.file.FileCollection;
14 14 import org.gradle.api.model.ObjectFactory;
15 15 import org.gradle.api.provider.Provider;
16 16 import org.gradle.api.tasks.Sync;
17 17 import org.gradle.api.tasks.TaskContainer;
18 18 import org.gradle.language.base.plugins.LifecycleBasePlugin;
19 19 import org.implab.gradle.common.core.lang.FilePaths;
20 import org.implab.gradle.variants.artifacts.ArtifactAssembly;
20 21 import org.implab.gradle.variants.artifacts.ArtifactAssemblySpec;
21 22 import org.implab.gradle.variants.artifacts.ArtifactSlot;
22 23 import org.implab.gradle.variants.sources.CompileUnit;
23 24 import org.implab.gradle.variants.sources.CompileUnitsView;
24 25 import org.implab.gradle.variants.sources.RoleProjectionsView;
25 26 import org.implab.gradle.variants.sources.SourceSetMaterializer;
26 27
27 28 /**
28 * АдаптСр ΠΌΠ΅ΠΆΠ΄Ρƒ Ρ„Ρ€Π°Π³ΠΌΠ΅Π½Ρ‚Π°ΠΌΠΈ Π°Ρ€Ρ‚Π΅Ρ„Π°ΠΊΡ‚ΠΎΠ², прСдставлСнными Π² Π²ΠΈΠ΄Π΅
29 * {@link SlotContribution},
30 * ΠΈ ArtifactAssemblyRegistry, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ ΠΎΠΏΠ΅Ρ€ΠΈΡ€ΡƒΠ΅Ρ‚ ΡƒΠΆΠ΅ собранными
31 * {@link ArtifactAssembly}.
29 * Adapts slot contribution declarations to materialized {@link ArtifactAssembly}
30 * handles.
32 31 *
33 * Π”Π°Π½Π½Ρ‹ΠΉ класс ΠΎΡ‚Π²Π΅Ρ‡Π°Π΅Ρ‚ Π·Π° сборку ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½Ρ‹Ρ… Ρ„Ρ€Π°Π³ΠΌΠ΅Π½Ρ‚ΠΎΠ² Π°Ρ€Ρ‚Π΅Ρ„Π°ΠΊΡ‚ΠΎΠ² Π² Π΅Π΄ΠΈΠ½Ρ‹ΠΉ
34 * ΠΊΠ°Ρ‚Π°Π»ΠΎΠ³, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ Π·Π°Ρ‚Π΅ΠΌ рСгистрируСтся Π² ArtifactAssemblyRegistry Π² Π²ΠΈΠ΄Π΅
35 * {@link ArtifactAssembly}. Π‘Π±ΠΎΡ€ΠΊΠ° ΠΊΠΎΠ½Π΅Ρ‡Π½ΠΎΠ³ΠΎ Π°Ρ€Ρ‚Π΅Ρ„Π°ΠΊΡ‚Π° происходит ΠΏΡ€ΠΈ ΠΏΠΎΠΌΠΎΡ‰ΠΈ
36 * Π·Π°Π΄Π°Ρ‡ΠΈ, которая ΠΊΠΎΠΏΠΈΡ€ΡƒΠ΅Ρ‚ всС Π²Ρ…ΠΎΠ΄Π½Ρ‹Π΅ Ρ„Π°ΠΉΠ»Ρ‹ Π² Π²Ρ‹Ρ…ΠΎΠ΄Π½ΠΎΠΉ ΠΊΠ°Ρ‚Π°Π»ΠΎΠ³. Π—Π°Π΄Π°Ρ‡Π°
37 * создаСтся для ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ {@link ArtifactSlot} ΠΈ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚
38 * {@link SlotAssembly#inputs()} ΠΊΠ°ΠΊ источник Π²Ρ…ΠΎΠ΄Π½Ρ‹Ρ… Π΄Π°Π½Π½Ρ‹Ρ….
32 * <p>The handler creates one {@link Sync} task per {@link ArtifactSlot}. The task
33 * copies all collected slot inputs into a single output directory. That output
34 * directory is then registered in {@link ArtifactAssemblyRegistry} as the
35 * published artifact for the slot.
39 36 *
40 * Для сборки ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ ΠΏΠ°Ρ‚Ρ‚Π΅Ρ€Π½ Visitor: ΠΊΠ°ΠΆΠ΄Ρ‹ΠΉ Ρ„Ρ€Π°Π³ΠΌΠ΅Π½Ρ‚ Π°Ρ€Ρ‚Π΅Ρ„Π°ΠΊΡ‚Π°
41 * прСдставлСн Π² Π²ΠΈΠ΄Π΅ Ρ€Π΅Π°Π»ΠΈΠ·Π°Ρ†ΠΈΠΈ интСрфСйса {@link SlotContribution}, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ
42 * ΠΈΠΌΠ΅Π΅Ρ‚ ΠΌΠ΅Ρ‚ΠΎΠ΄ accept, ΠΏΡ€ΠΈΠ½ΠΈΠΌΠ°ΡŽΡ‰ΠΈΠΉ Visitor.
43 * Visitor Ρ€Π΅Π°Π»ΠΈΠ·ΠΎΠ²Π°Π½ Π²ΠΎ Π²Π½ΡƒΡ‚Ρ€Π΅Π½Π½Π΅ΠΌ классС {@link ContributionVisitor}, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ
44 * Π·Π½Π°Π΅Ρ‚, ΠΊΠ°ΠΊ ΠΎΠ±Ρ€Π°Π±Π°Ρ‚Ρ‹Π²Π°Ρ‚ΡŒ ΠΊΠ°ΠΆΠ΄Ρ‹ΠΉ Ρ‚ΠΈΠΏ Ρ„Ρ€Π°Π³ΠΌΠ΅Π½Ρ‚Π° ΠΈ Π΄ΠΎΠ±Π°Π²Π»ΡΡ‚ΡŒ Π΅Π³ΠΎ Π² сборку.
45 * Для ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ {@link SlotContribution} создаСтся ΠΊΠ»ΡŽΡ‡ {@link SlotInputKey},
46 * ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ для Π΄Π΅Π΄ΡƒΠΏΠ»ΠΈΠΊΠ°Ρ†ΠΈΠΈ: Ссли Ρ„Ρ€Π°Π³ΠΌΠ΅Π½Ρ‚ с Ρ‚Π°ΠΊΠΈΠΌ ΠΆΠ΅ ΠΊΠ»ΡŽΡ‡ΠΎΠΌ ΡƒΠΆΠ΅
47 * Π±Ρ‹Π» Π΄ΠΎΠ±Π°Π²Π»Π΅Π½, Ρ‚ΠΎ ΠΎΠ½ игнорируСтся.
37 * <p>Input collection uses {@link SlotContributionVisitor}. Each contribution is
38 * converted to a {@link SlotInputKey}; duplicate keys are ignored so that repeated
39 * topology-based selections do not add the same input twice.
48 40 */
49 41 @NonNullByDefault
50 42 public class ArtifactAssemblyHandler {
51 43 private final ObjectFactory objects;
52 44
53 45 private final ArtifactAssemblyRegistry assemblyRegistry;
54 46
55 47 private final DirectoryProperty assembliesDirectory;
56 48
57 49 private final TaskContainer tasks;
58 50
59 51 private final CompileUnitsView compileUnitsView;
60 52
61 53 private final RoleProjectionsView roleProjectionsView;
62 54
63 55 private final SourceSetMaterializer sourceSetMaterializer;
64 56
65 57 private final Map<ArtifactSlot, SlotAssembly> slotInputs = new HashMap<>();
66 58
67 59 public ArtifactAssemblyHandler(
68 60 ObjectFactory objects,
69 61 TaskContainer tasks,
70 62 ArtifactAssemblyRegistry assemblyRegistry,
71 63 CompileUnitsView compileUnitsView,
72 64 RoleProjectionsView roleProjectionsView,
73 65 SourceSetMaterializer sourceSetMaterializer) {
74 66 this.objects = objects;
75 67 this.tasks = tasks;
76 68 this.assemblyRegistry = assemblyRegistry;
77 69 this.compileUnitsView = compileUnitsView;
78 70 this.roleProjectionsView = roleProjectionsView;
79 71 this.sourceSetMaterializer = sourceSetMaterializer;
80 72
81 73 assembliesDirectory = objects.directoryProperty();
82 74 }
83 75
84 76 public DirectoryProperty getAssembliesDirectory() {
85 77 return assembliesDirectory;
86 78 }
87 79
88 80 public void configureAssembly(ArtifactSlot artifactSlot, Action<? super ArtifactAssemblySpec> action) {
89 81 var visitor = contributionVisitor(artifactSlot);
90 82 var spec = new DefaultArtifactAssemblySpec(objects, c -> c.accept(visitor));
91 83 action.execute(spec);
92 84 }
93 85
94 86 public SlotContributionVisitor contributionVisitor(ArtifactSlot artifactSlot) {
95 87 var assembly = slotInputs.computeIfAbsent(artifactSlot, this::createSlotAssembly);
96 88 return new ContributionVisitor(artifactSlot, assembly);
97 89 }
98 90
99 91 /**
100 * Π‘ΠΎΠ·Π΄Π°Π΅Ρ‚ сборку для ΡƒΠΊΠ°Π·Π°Π½Π½ΠΎΠ³ΠΎ слота Π°Ρ€Ρ‚Π΅Ρ„Π°ΠΊΡ‚Π°, сборка рСгистрируСтся Π²
101 * ArtifactAssemblyRegistry, Ссли для слота сборка ΡƒΠΆΠ΅ Π±Ρ‹Π»Π° зарСгистрирована
102 * ΠΊΠ΅ΠΌ-Ρ‚ΠΎ Π΅Ρ‰Π΅, Ρ‚ΠΎ Π²ΠΎΠ·Π½ΠΈΠΊΠ°Π΅Ρ‚ ошибка.
92 * Creates the assembly task for the given slot and registers its output artifact.
103 93 */
104 94 private SlotAssembly createSlotAssembly(ArtifactSlot artifactSlot) {
105 95 var assembly = new SlotAssembly();
106 96 var fileCollection = assembly.inputs();
107 97
108 98 var outputDirectory = outputDirectory(artifactSlot);
109 99
110 100 var task = tasks.register(assembleTaskName(artifactSlot), Sync.class, copy -> {
111 101 copy.setGroup(LifecycleBasePlugin.BUILD_GROUP);
112 102 copy.into(outputDirectory);
113 103 copy.from(fileCollection);
114 104 });
115 105
116 106 assemblyRegistry.register(artifactSlot, task, t -> outputDirectory);
117 107
118 108 return assembly;
119 109 }
120 110
121 111 private String assembleTaskName(ArtifactSlot artifactSlot) {
122 112 var variantName = artifactSlot.variant().getName();
123 113 var slotName = artifactSlot.slot().getName();
124 114
125 115 return "assembleVariantArtifactSlot"
126 116 + "_v" + variantName.length() + "_" + variantName
127 117 + "_s" + slotName.length() + "_" + slotName;
128 118 }
129 119
130 120 private Provider<Directory> outputDirectory(ArtifactSlot artifactSlot) {
131 121 return assembliesDirectory.dir(
132 122 FilePaths.cat(
133 123 artifactSlot.variant().getName(),
134 124 artifactSlot.slot().getName()));
135 125 }
136 126
137 127 /**
138 * Π‘ΠΎΠ±ΠΈΡ€Π°Π΅Ρ‚ ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½Ρ‹Π΅ Ρ„Ρ€Π°Π³ΠΌΠ΅Π½Ρ‚Ρ‹ Π°Ρ€Ρ‚Π΅Ρ„Π°ΠΊΡ‚ΠΎΠ² Π² Π΅Π΄ΠΈΠ½Ρ‹ΠΉ источник
139 * {@link ConfigurableFileCollection}.
140 * Для Ρ„Ρ€Π°Π³ΠΌΠ΅Π½Ρ‚ΠΎΠ² {@link SlotContribution} ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ ΠΌΠ΅Ρ…Π°Π½ΠΈΠ·ΠΌ Π΄Π΅Π΄ΡƒΠΏΠ»ΠΈΠΊΠ°Ρ†ΠΈΠΈ:
141 * для каТдого
142 * водящСго Ρ„Ρ€Π°Π³ΠΌΠ΅Π½Ρ‚Π° создаСтся ΠΊΠ»ΡŽΡ‡ {@link SlotInputKey} ΠΈ Π΄ΠΎΠ±Π°Π²Π»ΡΡŽΡ‚ΡΡ
143 * Ρ„Ρ€Π°Π³ΠΌΠ΅Π½Ρ‚Ρ‹ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ
144 * с ΡƒΠ½ΠΈΠΊΠ°Π»ΡŒΠ½Ρ‹ΠΌ ΠΊΠ»ΡŽΡ‡ΠΎΠΌ, ΠΏΠΎΠ²Ρ‚ΠΎΡ€Ρ‹ ΠΈΠ³Π½ΠΎΡ€ΠΈΡ€ΡƒΡŽΡ‚ΡΡ.
128 * Collects slot contributions into one {@link ConfigurableFileCollection}.
145 129 */
146 130 private class ContributionVisitor implements SlotContributionVisitor {
147 131 // artifact slot for this assembly
148 132 private final ArtifactSlot artifactSlot;
149 133
150 134 // seen inputs, used for deduplication
151 135 private final SlotAssembly assembly;
152 136
153 137 ContributionVisitor(ArtifactSlot artifactSlot, SlotAssembly assembly) {
154 138 this.artifactSlot = artifactSlot;
155 139 this.assembly = assembly;
156 140 }
157 141
158 142 @Override
159 143 public void visit(DirectContribution contribution) {
160 144 contribute(
161 145 SlotInputKey.newUniqueKey("Direct input for " + artifactSlot),
162 146 contribution.input());
163 147 }
164 148
165 149 @Override
166 150 public void visit(VariantOutputsContribution contribution) {
167 151 var units = compileUnitsView.getUnitsForVariant(artifactSlot.variant());
168 152 contributeCompileUnits(units, contribution.outputs());
169 153 }
170 154
171 155 @Override
172 156 public void visit(RoleOutputsContribution contribution) {
173 157 var roleProjection = roleProjectionsView.requireProjection(artifactSlot.variant(),
174 158 contribution.role());
175 159 var units = roleProjectionsView.getUnits(roleProjection);
176 160
177 161 contributeCompileUnits(units, contribution.outputs());
178 162
179 163 }
180 164
181 165 @Override
182 166 public void visit(LayerOutputsContribution contribution) {
183 167 var unit = compileUnitsView.requireUnit(artifactSlot.variant(), contribution.layer());
184 168 contributeCompileUnits(Set.of(unit), contribution.outputs());
185 169 }
186 170
187 171 private void contributeCompileUnits(Set<CompileUnit> units, Set<String> outputs) {
188 172 units.stream()
189 173 // expand variant compile units, make (compileUnit, outputName) pairs
190 174 .flatMap(unit -> outputs.stream()
191 175 .map(output -> new CompileUnitOutputKey(unit, output)))
192 176 .forEach(key -> contribute(
193 177 key,
194 178 sourceSetMaterializer.getSourceSet(key.unit())
195 179 .map(s -> s.output(key.outputName()))));
196 180 }
197 181
198 182 private void contribute(SlotInputKey key, Object resolvedInput) {
199 183 assembly.addSlotInput(key, resolvedInput);
200 184 }
201 185 }
202 186
203 /** БостояниС для ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½ΠΎΠ³ΠΎ слота */
187 /** Mutable input state for one slot assembly. */
204 188 class SlotAssembly {
205 189 private final ConfigurableFileCollection inputs = objects.fileCollection();
206 190 private final Set<SlotInputKey> seen = new HashSet<>();
207 191
208 192 public void addSlotInput(SlotInputKey key, Object input) {
209 193 if (!seen.add(key))
210 194 return;
211 195 inputs.from(input);
212 196 }
213 197
214 198 public FileCollection inputs() {
215 199 return inputs;
216 200 }
217 201 }
218 202 }
@@ -1,79 +1,81
1 1 package org.implab.gradle.variants.artifacts.internal;
2 2
3 3 import java.util.HashSet;
4 4 import java.util.Set;
5 5 import java.util.function.Consumer;
6 6 import java.util.stream.Stream;
7 7
8 8 import org.eclipse.jdt.annotation.NonNullByDefault;
9 9 import org.gradle.api.Action;
10 10 import org.gradle.api.model.ObjectFactory;
11 11 import org.implab.gradle.common.core.lang.Strings;
12 12 import org.implab.gradle.variants.artifacts.ArtifactAssemblySpec;
13 13 import org.implab.gradle.variants.core.Layer;
14 14 import org.implab.gradle.variants.core.Role;
15 15 import org.implab.gradle.variants.artifacts.OutputSelectionSpec;
16 16
17 17 /**
18 * РСализация DSL ΠΌΠΎΠ΄Π΅Π»ΠΈ, строит Π½Π°Π±ΠΎΡ€ {@link SlotContribution}. ΠŸΡ€ΠΈ построСнии Π½Π°Π±ΠΎΡ€Π°
19 * ΠΏΡ€Π°Π²ΠΈΠ»Π° ΠΈ ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚Π½ΠΎΡΡ‚ΡŒ Π½Π΅ ΠΏΡ€ΠΎΠ²Π΅Ρ€ΡΡŽΡ‚ΡΡ. По ΠΎΠΊΠΎΠ½Ρ‡Π°Π½ΠΈΠΈ использования ΠΊΠ»ΠΈΠ΅Π½Ρ‚
20 * Π²Ρ‹Π·Ρ‹Π²Π°Π΅Ρ‚ ΠΌΠ΅Ρ‚ΠΎΠ΄ {@link #process(Consumer)} для ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ΠΎΠ².
18 * Default DSL facade for collecting {@link SlotContribution} declarations.
21 19 *
20 * <p>The spec does not validate topology references immediately. It translates DSL
21 * calls to contribution objects and passes them to the supplied consumer; semantic
22 * validation happens later when the assembly handler resolves contributions
23 * against the finalized source model.
22 24 */
23 25 @NonNullByDefault
24 26 final class DefaultArtifactAssemblySpec implements ArtifactAssemblySpec {
25 27 private final Consumer<? super SlotContribution> consumer;
26 28 private final ObjectFactory objectFactory;
27 29
28 30 DefaultArtifactAssemblySpec(ObjectFactory objectFactory, Consumer<? super SlotContribution> consumer) {
29 31 this.consumer = consumer;
30 32 this.objectFactory = objectFactory;
31 33 }
32 34
33 35 @Override
34 36 public void from(Object artifact) {
35 37 consumer.accept(new DirectContribution(artifact));
36 38 }
37 39
38 40 @Override
39 41 public void fromVariant(Action<? super OutputSelectionSpec> action) {
40 42 consumer.accept(new VariantOutputsContribution(outputs(action)));
41 43 }
42 44
43 45 @Override
44 46 public void fromRole(String roleName, Action<? super OutputSelectionSpec> action) {
45 47
46 48 consumer.accept(new RoleOutputsContribution(
47 49 objectFactory.named(Role.class, roleName),
48 50 outputs(action)));
49 51 }
50 52
51 53 @Override
52 54 public void fromLayer(String layerName, Action<? super OutputSelectionSpec> action) {
53 55 consumer.accept(new LayerOutputsContribution(
54 56 objectFactory.named(Layer.class, layerName),
55 57 outputs(action)));
56 58 }
57 59
58 60 private static Set<String> outputs(Action<? super OutputSelectionSpec> action) {
59 61 var spec = new OutputsSetSpec();
60 62 action.execute(spec);
61 63 return spec.outputs();
62 64 }
63 65
64 66 private static class OutputsSetSpec implements OutputSelectionSpec {
65 67 private final Set<String> outputs = new HashSet<>();
66 68
67 69 @Override
68 70 public void output(String name, String... extra) {
69 71 Stream.concat(Stream.of(name), Stream.of(extra))
70 72 .map(Strings::requireNonBlank)
71 73 .forEach(outputs::add);
72 74 }
73 75
74 76 Set<String> outputs() {
75 77 return Set.copyOf(outputs);
76 78 }
77 79
78 80 }
79 81 }
@@ -1,107 +1,112
1 1 package org.implab.gradle.variants.artifacts.internal;
2 2
3 3 import org.eclipse.jdt.annotation.NonNullByDefault;
4 4 import org.gradle.api.Action;
5 5 import org.gradle.api.artifacts.Configuration;
6 6 import org.gradle.api.attributes.AttributeContainer;
7 import org.implab.gradle.internal.ReplayableQueue;
7 import org.gradle.api.model.ObjectFactory;
8 import org.implab.gradle.common.core.lang.ReplayableQueue;
8 9 import org.implab.gradle.variants.artifacts.ArtifactSlot;
9 10 import org.implab.gradle.variants.artifacts.OutgoingConfigurationSlotSpec;
10 11 import org.implab.gradle.variants.artifacts.OutgoingConfigurationSpec;
11 12 import org.implab.gradle.variants.artifacts.OutgoingVariant;
13 import org.implab.gradle.variants.artifacts.Slot;
12 14 import org.implab.gradle.variants.core.Variant;
13 15
14 16 /**
15 17 * Handles outgoing artifact materialization policy.
16 18 *
17 19 * <p>Materialization is the phase where the plugin interprets variant artifact
18 20 * declarations as Gradle outgoing publication state: a consumable configuration,
19 21 * its primary artifact set, secondary artifact variants, and attributes.
20 22 *
21 23 * <p>The handler provides extension points for customizing the materialized
22 24 * Gradle-facing objects. These hooks intentionally expose Gradle API objects.
23 25 * This allows advanced customization, but also means that callers can bypass
24 26 * the plugin model. Such customizations are considered caller responsibility.
25 27 *
26 28 * <p>The internal binding mechanics are not part of the public contract. The
27 29 * contract is the materialized outgoing state exposed through the specification
28 30 * objects.
29 31 */
30 32 @NonNullByDefault
31 33 public class MaterializationPolicyHandler implements Action<OutgoingVariant> {
32 34
33 35 private final ReplayableQueue<OutgoingConfigurationSpec> variantMaterialization = new ReplayableQueue<>();
34 36 private final ReplayableQueue<OutgoingConfigurationSlotSpec> slotMaterialization = new ReplayableQueue<>();
37 private final ObjectFactory objects;
35 38
36 public MaterializationPolicyHandler() {
39 public MaterializationPolicyHandler(ObjectFactory objects) {
40 this.objects = objects;
37 41 }
38 42
39 43 @Override
40 44 public void execute(OutgoingVariant outgoingVariant) {
41 var slots = outgoingVariant.getSlots();
42 45 var primarySlotProvider = outgoingVariant.getPrimarySlot();
43 46 var variant = outgoingVariant.getVariant();
44 47
45 // связываСм ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ
48 // Materialization hooks are attached to the owning outgoing configuration.
46 49 outgoingVariant.configureOutgoing(configuration -> {
47 50 var primarySlot = primarySlotProvider.get();
48 51 var outgoing = configuration.getOutgoing();
49 52
50 53 variantMaterialized(variant, configuration);
51 54
52 55 slotMaterialized(new ArtifactSlot(variant, primarySlot), true, outgoing.getAttributes());
53 56
54 slots.forEach(slot -> {
57 // Slot publication hooks follow materialized Gradle artifact variants,
58 // not the live slot identity view.
59 outgoing.getVariants().configureEach(variantConfiguration -> {
60 var slot = objects.named(Slot.class, variantConfiguration.getName());
55 61 if (slot.equals(primarySlot))
56 62 return;
57 63
58 var variantConfiguration = outgoing.getVariants().getByName(slot.getName());
59 64 slotMaterialized(new ArtifactSlot(variant, slot), false, variantConfiguration.getAttributes());
60 65 });
61 66 });
62 67 };
63 68
64 69 public void whenVariantMaterialized(Action<? super OutgoingConfigurationSpec> action) {
65 70 variantMaterialization.forEach(action::execute);
66 71 }
67 72
68 73 public void whenSlotMaterialized(Action<? super OutgoingConfigurationSlotSpec> action) {
69 74 slotMaterialization.forEach(action::execute);
70 75 }
71 76
72 77 private void variantMaterialized(Variant variant, Configuration configuration) {
73 78 variantMaterialization.add(new OutgoingConfigurationSpec() {
74 79
75 80 @Override
76 81 public Variant getVariant() {
77 82 return variant;
78 83 }
79 84
80 85 @Override
81 86 public Configuration getConfiguration() {
82 87 return configuration;
83 88 }
84 89
85 90 });
86 91 }
87 92
88 93 private void slotMaterialized(ArtifactSlot slot, boolean primary, AttributeContainer attributes) {
89 94 slotMaterialization.add(new OutgoingConfigurationSlotSpec() {
90 95 @Override
91 96 public boolean isPrimary() {
92 97 return primary;
93 98 }
94 99
95 100 @Override
96 101 public ArtifactSlot getArtifactSlot() {
97 102 return slot;
98 103 }
99 104
100 105 @Override
101 106 public void artifactAttributes(Action<? super AttributeContainer> action) {
102 107 action.execute(attributes);
103 108 }
104 109 });
105 110 }
106 111
107 112 }
@@ -1,33 +1,33
1 1 package org.implab.gradle.variants.artifacts.internal;
2 2
3 3 import org.gradle.api.Action;
4 4 import org.gradle.api.InvalidUserDataException;
5 5 import org.gradle.api.provider.ProviderFactory;
6 6 import org.implab.gradle.variants.artifacts.OutgoingVariant;
7 7
8 8 public class SingleSlotConvention implements Action<OutgoingVariant> {
9 9
10 10 private final ProviderFactory providers;
11 11
12 12 public SingleSlotConvention(ProviderFactory providers) {
13 13 this.providers = providers;
14 14 }
15 15
16 16 @Override
17 17 public void execute(OutgoingVariant outgoingVariant) {
18 18 var slots = outgoingVariant.getSlots();
19 19
20 20 outgoingVariant.getPrimarySlot().convention(
21 // Ссли Π΅ΡΡ‚ΡŒ Ρ€ΠΎΠ²Π½ΠΎ ΠΎΠ΄ΠΈΠ½ слот, Ρ‚ΠΎ ΠΎΠ½ считаСтся primary
21 // If exactly one slot is declared, it becomes the primary slot.
22 22 providers.provider(() -> {
23 23 if (slots.size() == 0)
24 24 throw new InvalidUserDataException("No slots declared for " + outgoingVariant.getVariant());
25 25 if (slots.size() > 1)
26 26 throw new InvalidUserDataException("Multiple slots declared for " + outgoingVariant.getVariant() +
27 27 ", please specify primary slot explicitly");
28 28
29 29 return slots.stream().findAny().get();
30 30 }));
31 31 }
32 32
33 33 }
@@ -1,41 +1,40
1 1 /**
2 2 * Variant-scoped outgoing artifacts.
3 3 *
4 4 * <p>This package models the external artifact contract of a project in terms of variant-local slots.
5 5 * A variant represents one outgoing contract, while a slot represents one artifact set inside that
6 6 * contract.
7 7 *
8 8 * <p>The model intentionally separates cheap identity objects from stateful build objects:
9 9 *
10 10 * <ul>
11 11 * <li>{@link org.implab.gradle.variants.artifacts.ArtifactSlot} identifies a published slot inside a
12 12 * variant;</li>
13 13 * <li>{@link org.implab.gradle.variants.artifacts.ArtifactAssembly} is the lazily materialized body of
14 14 * that slot.</li>
15 15 * </ul>
16 16 *
17 17 * <p>Each slot is expected to materialize to exactly one published artifact: either one file or one
18 18 * directory. Internal build topology such as roles may participate in slot assembly rules, but does not
19 19 * belong to external artifact identity.
20 20 *
21 21 * <p>Typical usage:
22 22 *
23 23 * <pre>{@code
24 24 * variantArtifacts {
25 25 * variant("browser") {
26 26 * primarySlot("runtime") {
27 27 * fromRole("main") { output("js") }
28 28 * }
29 29 * slot("sources") {
30 30 * fromLayer("main") { output("sources") }
31 31 * }
32 32 * }
33 33 * }
34 34 * }</pre>
35 35 *
36 * <p>After finalization, slot identities can be observed through
37 * {@link org.implab.gradle.variants.artifacts.OutgoingVariantsContext#getSlots()}, while slot bodies are
38 * obtained on demand through
36 * <p>After finalization, {@link org.implab.gradle.variants.artifacts.OutgoingVariant} exposes declared slot
37 * identities through {@code getSlots()}, while slot bodies are obtained on demand through
39 38 * {@link org.implab.gradle.variants.artifacts.OutgoingVariantsContext#getAssemblies()}.
40 39 */
41 40 package org.implab.gradle.variants.artifacts;
@@ -1,563 +1,691
1 1 package org.implab.gradle.variants;
2 2
3 3 import static org.junit.jupiter.api.Assertions.assertTrue;
4 4
5 5 import org.gradle.testkit.runner.BuildResult;
6 6 import org.gradle.testkit.runner.TaskOutcome;
7 7 import org.junit.jupiter.api.Test;
8 8
9 9 class VariantArtifactsPluginFunctionalTest extends AbstractFunctionalTest {
10 10
11 11 @Test
12 12 void gradleReferenceLazyOutgoingConfigurationAllowsSecondaryArtifactSelection() throws Exception {
13 13 writeFile("settings.gradle", """
14 14 rootProject.name = 'gradle-reference-outgoing-resolution'
15 15 include 'producer', 'consumer'
16 16 """);
17 17 writeFile("producer/inputs/typesPackage", "types\n");
18 18 writeFile("producer/inputs/js", "js\n");
19 19 writeBuildFile("""
20 20 import org.gradle.api.attributes.Attribute
21 21
22 22 def variantAttr = Attribute.of('test.variant', String)
23 23 def slotAttr = Attribute.of('test.slot', String)
24 24
25 25 project(':producer') {
26 26 def browserElements = configurations.consumable('browserElements')
27 27
28 28 println('reference: registered browserElements provider')
29 29
30 30 browserElements.configure { configuration ->
31 31 println('reference: configuring browserElements')
32 32
33 33 configuration.attributes.attribute(variantAttr, 'browser')
34 34 configuration.outgoing.attributes.attribute(slotAttr, 'typesPackage')
35 35 configuration.outgoing.artifact(layout.projectDirectory.file('inputs/typesPackage'))
36 36
37 37 configuration.outgoing.variants.create('js') { secondary ->
38 38 println('reference: creating js outgoing variant')
39 39
40 40 secondary.attributes.attribute(slotAttr, 'js')
41 41 secondary.artifact(layout.projectDirectory.file('inputs/js'))
42 42 }
43 43 }
44 44 }
45 45
46 46 project(':consumer') {
47 47 configurations {
48 48 compileView {
49 49 canBeResolved = true
50 50 canBeConsumed = false
51 51 canBeDeclared = true
52 52 attributes {
53 53 attribute(variantAttr, 'browser')
54 54 attribute(slotAttr, 'typesPackage')
55 55 }
56 56 }
57 57 }
58 58
59 59 dependencies {
60 60 compileView project(':producer')
61 61 }
62 62
63 63 tasks.register('probe') {
64 64 doLast {
65 65 println('reference: resolving primary files')
66 66
67 67 def compileFiles = configurations.compileView.files.collect { it.name }.sort().join(',')
68 68
69 69 println('reference: resolving secondary files')
70 70
71 71 def jsFiles = configurations.compileView.incoming.artifactView {
72 72 attributes {
73 73 attribute(slotAttr, 'js')
74 74 }
75 75 }.files.files.collect { it.name }.sort().join(',')
76 76
77 77 println('compileFiles=' + compileFiles)
78 78 println('jsFiles=' + jsFiles)
79 79 }
80 80 }
81 81 }
82 82 """);
83 83
84 84 BuildResult result = runner(":consumer:probe").build();
85 85 var output = result.getOutput();
86 86 var registered = output.indexOf("reference: registered browserElements provider");
87 87 var resolvingPrimary = output.indexOf("reference: resolving primary files");
88 88 var configuring = output.indexOf("reference: configuring browserElements");
89 89 var creatingSecondary = output.indexOf("reference: creating js outgoing variant");
90 90 var resolvingSecondary = output.indexOf("reference: resolving secondary files");
91 91
92 92 assertTrue(registered >= 0);
93 93 assertTrue(resolvingPrimary >= 0);
94 94 assertTrue(configuring >= 0);
95 95 assertTrue(creatingSecondary >= 0);
96 96 assertTrue(resolvingSecondary >= 0);
97 97 assertTrue(registered < resolvingPrimary);
98 98 assertTrue(resolvingPrimary < configuring);
99 99 assertTrue(configuring < creatingSecondary);
100 100 assertTrue(creatingSecondary < resolvingSecondary);
101 101 assertTrue(output.contains("compileFiles=typesPackage"));
102 102 assertTrue(output.contains("jsFiles=js"));
103 103 }
104 104
105 105 @Test
106 void gradleReferenceRegisteredSecondaryArtifactVariantIsNotRealizedBeforeResolution() throws Exception {
107 // Gradle issue: https://github.com/gradle/gradle/issues/27441
108 // Registered outgoing artifact variants are not realized before dependency resolution.
109 writeFile("settings.gradle", """
110 rootProject.name = 'gradle-reference-registered-secondary-variant'
111 include 'producer', 'consumer'
112 """);
113 writeFile("producer/inputs/typesPackage", "types\n");
114 writeFile("producer/inputs/js", "js\n");
115 writeFile("build.gradle", """
116 import org.gradle.api.attributes.Attribute
117
118 def variantAttr = Attribute.of('test.variant', String)
119 def slotAttr = Attribute.of('test.slot', String)
120
121 project(':producer') {
122 def browserElements = configurations.consumable('browserElements')
123
124 browserElements.configure { configuration ->
125 configuration.attributes.attribute(variantAttr, 'browser')
126 configuration.outgoing.attributes.attribute(slotAttr, 'typesPackage')
127 configuration.outgoing.artifact(layout.projectDirectory.file('inputs/typesPackage'))
128
129 configuration.outgoing.variants.register('js') { secondary ->
130 secondary.attributes.attribute(slotAttr, 'js')
131 secondary.artifact(layout.projectDirectory.file('inputs/js'))
132 }
133 }
134 }
135
136 project(':consumer') {
137 configurations {
138 compileView {
139 canBeResolved = true
140 canBeConsumed = false
141 canBeDeclared = true
142 attributes {
143 attribute(variantAttr, 'browser')
144 attribute(slotAttr, 'typesPackage')
145 }
146 }
147 }
148
149 dependencies {
150 compileView project(':producer')
151 }
152
153 tasks.register('probe') {
154 doLast {
155 def compileFiles = configurations.compileView.files.collect { it.name }.sort().join(',')
156 def jsFiles = configurations.compileView.incoming.artifactView {
157 attributes {
158 attribute(slotAttr, 'js')
159 }
160 }.files.files.collect { it.name }.sort().join(',')
161
162 println('compileFiles=' + compileFiles)
163 println('jsFiles=' + jsFiles)
164 }
165 }
166 }
167 """);
168
169 assertBuildFails("Cannot create variant 'js' after dependency configuration ':producer:browserElements' has been resolved",
170 ":consumer:probe");
171 }
172
173 @Test
106 174 void materializesPrimaryAndSecondarySlotsAndInvokesOutgoingHooks() throws Exception {
107 175 writeSettings("variant-artifacts-slots");
108 176 writeFile("inputs/base.js", "console.log('base')\n");
109 177 writeFile("inputs/amd.js", "console.log('amd')\n");
110 178 writeFile("inputs/mainJs.txt", "mainJs marker\n");
111 179 writeFile("inputs/amdJs.txt", "amdJs marker\n");
112 180 writeBuildFile("""
113 181 import org.gradle.api.attributes.Attribute
114 182
115 183 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
116 184
117 185 def variantAttr = Attribute.of('test.variant', String)
118 186 def slotAttr = Attribute.of('test.slot', String)
119 187
120 188 variants.layers.create('mainBase')
121 189 variants.layers.create('mainAmd')
122 190 variants.roles.create('main')
123 191 variants.roles.create('test')
124 192 variants.variant('browser') {
125 193 role('main') {
126 194 layers('mainBase', 'mainAmd')
127 195 }
128 196 }
129 197
130 198 variantSources {
131 199 layer('mainBase') {
132 200 declareOutputs('js')
133 201 registerOutput('js', layout.projectDirectory.file('inputs/base.js'))
134 202 }
135 203 layer('mainAmd') {
136 204 declareOutputs('js')
137 205 registerOutput('js', layout.projectDirectory.file('inputs/amd.js'))
138 206 }
139 207 }
140 208
141 209 variantArtifacts {
142 210 variant('browser') {
143 211 primarySlot('mainJs') {
144 212 fromRole('main') {
145 213 output('js')
146 214 }
147 215 from(layout.projectDirectory.file('inputs/mainJs.txt'))
148 216 }
149 217 slot('amdJs') {
150 218 fromLayer('mainAmd') {
151 219 output('js')
152 220 }
153 221 from(layout.projectDirectory.file('inputs/amdJs.txt'))
154 222 }
155 223 }
156 224
157 225 whenOutgoingConfiguration { publication ->
158 226 publication.configuration {
159 227 attributes.attribute(variantAttr, publication.variant.name)
160 228 }
161 229 }
162 230
163 231 whenOutgoingSlot { publication ->
164 232 def slotName = publication.artifactSlot.slot.name
165 233 publication.artifactAttributes {
166 234 attribute(slotAttr, slotName)
167 235 }
168 236 }
169 237 }
170 238
171 239 tasks.register('probe') {
172 240 dependsOn 'assembleVariantArtifactSlot_v7_browser_s6_mainJs'
173 241 dependsOn 'assembleVariantArtifactSlot_v7_browser_s5_amdJs'
174 242
175 243 doLast {
176 244 def mainDir = layout.buildDirectory.dir('variant-assemblies/browser/mainJs').get().asFile
177 245 def amdDir = layout.buildDirectory.dir('variant-assemblies/browser/amdJs').get().asFile
178 246
179 247 assert new File(mainDir, 'base.js').exists()
180 248 assert new File(mainDir, 'amd.js').exists()
181 249 assert new File(mainDir, 'mainJs.txt').exists()
182 250
183 251 assert !new File(amdDir, 'base.js').exists()
184 252 assert new File(amdDir, 'amd.js').exists()
185 253 assert new File(amdDir, 'amdJs.txt').exists()
186 254
187 255 def elements = configurations.getByName('browserElements')
188 256 def amdVariant = elements.outgoing.variants.getByName('amdJs')
189 257
190 258 println('variantAttr=' + elements.attributes.getAttribute(variantAttr))
191 259 println('primarySlotAttr=' + elements.outgoing.attributes.getAttribute(slotAttr))
192 260 println('amdSlotAttr=' + amdVariant.attributes.getAttribute(slotAttr))
193 261 println('configurations=' + configurations.matching { it.name == 'browserElements' }.collect { it.name }.join(','))
194 262 println('secondaryVariants=' + elements.outgoing.variants.collect { it.name }.sort().join(','))
195 263 }
196 264 }
197 265 """);
198 266
199 267 BuildResult result = runner("probe").build();
200 268
201 269 assertTrue(result.getOutput().contains("variantAttr=browser"));
202 270 assertTrue(result.getOutput().contains("primarySlotAttr=mainJs"));
203 271 assertTrue(result.getOutput().contains("amdSlotAttr=amdJs"));
204 272 assertTrue(result.getOutput().contains("configurations=browserElements"));
205 273 assertTrue(result.getOutput().contains("secondaryVariants=amdJs"));
206 274 assertTrue(result.task(":probe").getOutcome() == TaskOutcome.SUCCESS);
207 275 }
208 276
209 277 @Test
278 void outgoingSlotHookFollowsMaterializedGradleArtifactVariants() throws Exception {
279 writeSettings("variant-artifacts-materialized-gradle-variant");
280 writeFile("inputs/typesPackage", "types\n");
281 writeFile("inputs/js", "js\n");
282 writeBuildFile("""
283 import org.gradle.api.attributes.Attribute
284
285 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
286
287 def slotAttr = Attribute.of('test.slot', String)
288
289 variants.layers.create('main')
290 variants.roles.create('main')
291 variants.variant('browser') {
292 role('main') {
293 layers('main')
294 }
295 }
296
297 variantArtifacts {
298 variant('browser') {
299 primarySlot('typesPackage') {
300 from(layout.projectDirectory.file('inputs/typesPackage'))
301 }
302 }
303
304 whenOutgoingConfiguration { publication ->
305 publication.configuration {
306 outgoing.variants.create('js') { secondary ->
307 secondary.artifact(layout.projectDirectory.file('inputs/js'))
308 }
309 }
310 }
311
312 whenOutgoingSlot { publication ->
313 publication.artifactAttributes {
314 attribute(slotAttr, publication.artifactSlot.slot.name)
315 }
316 }
317 }
318
319 tasks.register('probe') {
320 doLast {
321 def elements = configurations.getByName('browserElements')
322 def jsVariant = elements.outgoing.variants.getByName('js')
323
324 println('primarySlotAttr=' + elements.outgoing.attributes.getAttribute(slotAttr))
325 println('jsSlotAttr=' + jsVariant.attributes.getAttribute(slotAttr))
326 }
327 }
328 """);
329
330 BuildResult result = runner("probe").build();
331
332 assertTrue(result.getOutput().contains("primarySlotAttr=typesPackage"));
333 assertTrue(result.getOutput().contains("jsSlotAttr=js"));
334 assertTrue(result.task(":probe").getOutcome() == TaskOutcome.SUCCESS);
335 }
336
337 @Test
210 338 void allowsSingleSlotVariantWithoutExplicitPrimarySlot() throws Exception {
211 339 writeSettings("variant-artifacts-single-slot");
212 340 writeBuildFile("""
213 341 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
214 342
215 343 variants.layers.create('main')
216 344 variants.roles.create('main')
217 345 variants.variant('browser') {
218 346 role('main') {
219 347 layers('main')
220 348 }
221 349 }
222 350
223 351 variantSources.layer('main') {
224 352 declareOutputs('types')
225 353 }
226 354
227 355 variantArtifacts {
228 356 variant('browser') {
229 357 slot('typesPackage') {
230 358 fromVariant {
231 359 output('types')
232 360 }
233 361 }
234 362 }
235 363 }
236 364
237 365 tasks.register('probe') {
238 366 doLast {
239 367 variantArtifacts.whenAvailable { ctx ->
240 368 def browser = objects.named(org.implab.gradle.variants.core.Variant, 'browser')
241 369 println('primary=' + ctx.findOutgoing(browser).get().primarySlot.get().name)
242 370 }
243 371 }
244 372 }
245 373 """);
246 374
247 375 BuildResult result = runner("probe").build();
248 376
249 377 assertTrue(result.getOutput().contains("primary=typesPackage"));
250 378 }
251 379
252 380 @Test
253 381 void materializesDirectSlotInputsWithoutVariantSourceBindings() throws Exception {
254 382 writeSettings("variant-artifacts-direct-input");
255 383 writeFile("inputs/bundle.js", "console.log('bundle')\n");
256 384 writeBuildFile("""
257 385 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
258 386
259 387 variants.layers.create('main')
260 388 variants.roles.create('main')
261 389 variants.variant('browser') {
262 390 role('main') {
263 391 layers('main')
264 392 }
265 393 }
266 394
267 395 variantArtifacts {
268 396 variant('browser') {
269 397 primarySlot('bundle') {
270 398 from(layout.projectDirectory.file('inputs/bundle.js'))
271 399 }
272 400 }
273 401 }
274 402
275 403 tasks.register('probe') {
276 404 dependsOn 'assembleVariantArtifactSlot_v7_browser_s6_bundle'
277 405
278 406 doLast {
279 407 def bundleDir = layout.buildDirectory.dir('variant-assemblies/browser/bundle').get().asFile
280 408 assert new File(bundleDir, 'bundle.js').exists()
281 409 }
282 410 }
283 411 """);
284 412
285 413 BuildResult result = runner("probe").build();
286 414
287 415 assertTrue(result.task(":probe").getOutcome() == TaskOutcome.SUCCESS);
288 416 }
289 417
290 418 @Test
291 419 void combinesDirectAndTopologyAwareSlotInputs() throws Exception {
292 420 writeSettings("variant-artifacts-combined-inputs");
293 421 writeFile("inputs/base.js", "console.log('base')\n");
294 422 writeFile("inputs/marker.txt", "marker\n");
295 423 writeBuildFile("""
296 424 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
297 425
298 426 variants.layers.create('main')
299 427 variants.roles.create('main')
300 428 variants.variant('browser') {
301 429 role('main') {
302 430 layers('main')
303 431 }
304 432 }
305 433
306 434 variantSources.layer('main') {
307 435 declareOutputs('js')
308 436 registerOutput('js', layout.projectDirectory.file('inputs/base.js'))
309 437 }
310 438
311 439 variantArtifacts {
312 440 variant('browser') {
313 441 primarySlot('bundle') {
314 442 fromVariant {
315 443 output('js')
316 444 }
317 445 from(layout.projectDirectory.file('inputs/marker.txt'))
318 446 }
319 447 }
320 448 }
321 449
322 450 tasks.register('probe') {
323 451 dependsOn 'assembleVariantArtifactSlot_v7_browser_s6_bundle'
324 452
325 453 doLast {
326 454 def bundleDir = layout.buildDirectory.dir('variant-assemblies/browser/bundle').get().asFile
327 455 assert new File(bundleDir, 'base.js').exists()
328 456 assert new File(bundleDir, 'marker.txt').exists()
329 457 }
330 458 }
331 459 """);
332 460
333 461 BuildResult result = runner("probe").build();
334 462
335 463 assertTrue(result.task(":probe").getOutcome() == TaskOutcome.SUCCESS);
336 464 }
337 465
338 466 @Test
339 467 void failsOnUnknownVariantReference() throws Exception {
340 468 writeSettings("variant-artifacts-missing-variant");
341 469 writeBuildFile("""
342 470 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
343 471
344 472 variants.layers.create('main')
345 473
346 474 variantArtifacts {
347 475 variant('browser') {
348 476 slot('mainJs') {
349 477 fromVariant {
350 478 output('js')
351 479 }
352 480 }
353 481 }
354 482 }
355 483 """);
356 484
357 485 assertBuildFails("isn't declared", "help");
358 486 }
359 487
360 488 @Test
361 489 void failsOnUnknownRoleReference() throws Exception {
362 490 writeSettings("variant-artifacts-missing-role");
363 491 writeBuildFile("""
364 492 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
365 493
366 494 variants.layers.create('main')
367 495 variants.roles.create('main')
368 496 variants.variant('browser') {
369 497 role('main') {
370 498 layers('main')
371 499 }
372 500 }
373 501
374 502 variantArtifacts {
375 503 variant('browser') {
376 504 slot('mainJs') {
377 505 fromRole('test') {
378 506 output('js')
379 507 }
380 508 }
381 509 }
382 510 }
383 511 """);
384 512
385 513 assertBuildFails("Role projection for variant 'browser' and role 'test' not found", "help");
386 514 }
387 515
388 516 @Test
389 517 void failsWhenPrimarySlotIsMissingForMultipleSlots() throws Exception {
390 518 writeSettings("variant-artifacts-missing-primary");
391 519 writeBuildFile("""
392 520 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
393 521
394 522 variants.layers.create('main')
395 523 variants.roles.create('main')
396 524 variants.variant('browser') {
397 525 role('main') {
398 526 layers('main')
399 527 }
400 528 }
401 529
402 530 variantSources.layer('main') {
403 531 declareOutputs('types', 'js')
404 532 }
405 533
406 534 variantArtifacts {
407 535 variant('browser') {
408 536 slot('typesPackage') {
409 537 fromVariant {
410 538 output('types')
411 539 }
412 540 }
413 541 slot('js') {
414 542 fromVariant {
415 543 output('js')
416 544 }
417 545 }
418 546 }
419 547 }
420 548
421 549 tasks.register('probe') {
422 550 doLast {
423 551 variantArtifacts.whenAvailable { ctx ->
424 552 def browser = objects.named(org.implab.gradle.variants.core.Variant, 'browser')
425 553 ctx.findOutgoing(browser).get().primarySlot.get()
426 554 }
427 555 }
428 556 }
429 557 """);
430 558
431 559 assertBuildFails("Multiple slots declared for browser, please specify primary slot explicitly", "probe");
432 560 }
433 561
434 562 @Test
435 563 void failsOnLayerReferenceOutsideVariantTopology() throws Exception {
436 564 writeSettings("variant-artifacts-layer-outside-topology");
437 565 writeBuildFile("""
438 566 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
439 567
440 568 variants.layers.create('mainBase')
441 569 variants.layers.create('extra')
442 570 variants.roles.create('main')
443 571 variants.variant('browser') {
444 572 role('main') {
445 573 layers('mainBase')
446 574 }
447 575 }
448 576
449 577 variantArtifacts {
450 578 variant('browser') {
451 579 slot('extraJs') {
452 580 fromLayer('extra') {
453 581 output('js')
454 582 }
455 583 }
456 584 }
457 585 }
458 586 """);
459 587
460 588 assertBuildFails("Compile unit for variant 'browser' and layer 'extra' not found", "help");
461 589 }
462 590
463 591 @Test
464 592 void preservesPrimaryResolutionAndAllowsSecondaryArtifactSelection() throws Exception {
465 593 writeFile("settings.gradle", """
466 594 rootProject.name = 'variant-artifacts-resolution'
467 595 include 'producer', 'consumer'
468 596 """);
469 597 writeFile("producer/inputs/types.d.ts", "export type Foo = string\n");
470 598 writeFile("producer/inputs/index.js", "export const foo = 'bar'\n");
471 599 writeBuildFile("""
472 600 import org.gradle.api.attributes.Attribute
473 601
474 602 def variantAttr = Attribute.of('test.variant', String)
475 603 def slotAttr = Attribute.of('test.slot', String)
476 604
477 605 subprojects {
478 606 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
479 607 }
480 608
481 609 project(':producer') {
482 610 variants.layers.create('main')
483 611 variants.roles.create('main')
484 612 variants.variant('browser') {
485 613 role('main') {
486 614 layers('main')
487 615 }
488 616 }
489 617
490 618 variantSources.layer('main') {
491 619 declareOutputs('types', 'js')
492 620 registerOutput('types', layout.projectDirectory.file('inputs/types.d.ts'))
493 621 registerOutput('js', layout.projectDirectory.file('inputs/index.js'))
494 622 }
495 623
496 624 variantArtifacts {
497 625 variant('browser') {
498 626 primarySlot('typesPackage') {
499 627 fromVariant {
500 628 output('types')
501 629 }
502 630 }
503 631 slot('js') {
504 632 fromVariant {
505 633 output('js')
506 634 }
507 635 }
508 636 }
509 637
510 638 whenOutgoingConfiguration { publication ->
511 639 publication.configuration {
512 640 attributes.attribute(variantAttr, publication.variant.name)
513 641 }
514 642 }
515 643
516 644 whenOutgoingSlot { publication ->
517 645 publication.artifactAttributes {
518 646 attribute(slotAttr, publication.artifactSlot.slot.name)
519 647 }
520 648 }
521 649 }
522 650
523 651 }
524 652
525 653 project(':consumer') {
526 654 configurations {
527 655 compileView {
528 656 canBeResolved = true
529 657 canBeConsumed = false
530 658 canBeDeclared = true
531 659 attributes {
532 660 attribute(variantAttr, 'browser')
533 661 attribute(slotAttr, 'typesPackage')
534 662 }
535 663 }
536 664 }
537 665
538 666 dependencies {
539 667 compileView project(':producer')
540 668 }
541 669
542 670 tasks.register('probe') {
543 671 doLast {
544 672 def compileFiles = configurations.compileView.files.collect { it.name }.sort().join(',')
545 673 def jsFiles = configurations.compileView.incoming.artifactView {
546 674 attributes {
547 675 attribute(slotAttr, 'js')
548 676 }
549 677 }.files.files.collect { it.name }.sort().join(',')
550 678
551 679 println('compileFiles=' + compileFiles)
552 680 println('jsFiles=' + jsFiles)
553 681 }
554 682 }
555 683 }
556 684 """);
557 685
558 686 BuildResult result = runner(":consumer:probe").build();
559 687
560 688 assertTrue(result.getOutput().contains("compileFiles=typesPackage"));
561 689 assertTrue(result.getOutput().contains("jsFiles=js"));
562 690 }
563 691 }
General Comments 0
You need to be logged in to leave comments. Login now