##// END OF EJS Templates
WIP working on variants api
cin -
r46:f260d19f1118 default
parent child
Show More
@@ -0,0 +1,29
1 package org.implab.gradle.variants;
2
3 import org.gradle.api.Plugin;
4 import org.gradle.api.Project;
5 import org.implab.gradle.common.core.lang.Deferred;
6 import org.implab.gradle.variants.artifacts.VariantArtifactsContext;
7 import org.implab.gradle.variants.core.VariantsExtension;
8
9 public abstract class VariantArtifactsPlugin implements Plugin<Project> {
10
11 @Override
12 public void apply(Project target) {
13 var extensions = target.getExtensions();
14
15 // Apply the main VariantsPlugin to ensure the core variant model is available.
16 target.getPlugins().apply(VariantsPlugin.class);
17 // Access the VariantsExtension to configure variant sources.
18 var variantsExtension = extensions.getByType(VariantsExtension.class);
19
20 var deferred = new Deferred<VariantArtifactsContext>();
21
22 variantsExtension.whenFinalized(variants -> {
23
24 });
25
26
27 }
28
29 }
@@ -0,0 +1,22
1 package org.implab.gradle.variants.artifacts;
2
3 import org.eclipse.jdt.annotation.NonNullByDefault;
4
5 /**
6 * Resolves stateful slot assemblies from cheap slot identities.
7 *
8 * <p>The returned assembly is a materialized build-model handle. It may expose lazy Gradle providers, but
9 * it is no longer an identity object suitable for replayable discovery.
10 */
11 @NonNullByDefault
12 public interface ArtifactAssemblies {
13 /**
14 * Resolves the assembly for the given slot.
15 *
16 * <p>This call materializes the build-facing body of the slot from its identity.
17 *
18 * @param slot slot identity inside a variant outgoing contract
19 * @return assembly handle for the slot
20 */
21 ArtifactAssembly resolveSlot(ArtifactSlot slot);
22 }
@@ -0,0 +1,32
1 package org.implab.gradle.variants.artifacts;
2
3 import org.gradle.api.Task;
4 import org.gradle.api.file.FileSystemLocation;
5 import org.gradle.api.provider.Provider;
6 import org.gradle.api.tasks.TaskProvider;
7
8 /**
9 * Materialized body of an {@link ArtifactSlot}.
10 *
11 * <p>An assembly is a stateful build object obtained on demand from
12 * {@link ArtifactAssemblies#resolveSlot(ArtifactSlot)}. It describes how the slot artifact is produced and
13 * where that single published artifact will appear.
14 */
15 public interface ArtifactAssembly {
16
17 /**
18 * Returns the published artifact produced for the slot.
19 *
20 * <p>A slot is expected to produce exactly one artifact represented by one file or one directory.
21 *
22 * @return provider of the produced artifact location
23 */
24 Provider<? extends FileSystemLocation> getArtifact();
25
26 /**
27 * Returns the task that assembles the slot artifact.
28 *
29 * @return provider of the assembly task
30 */
31 TaskProvider<? extends Task> getAssemblyTask();
32 }
@@ -0,0 +1,60
1 package org.implab.gradle.variants.artifacts;
2
3 import org.gradle.api.Action;
4 import groovy.lang.Closure;
5 import org.implab.gradle.common.core.lang.Closures;
6
7 /**
8 * DSL model describing how a slot artifact is assembled.
9 *
10 * <p>Selection rules declared here may refer to internal build topology such as roles, layers or units.
11 * Those selectors influence slot assembly only and do not become part of published artifact identity.
12 *
13 * <p>Regardless of the number of declared inputs, a slot is expected to materialize to a single published
14 * artifact.
15 */
16 public interface ArtifactAssemblySpec {
17 /**
18 * Contributes direct input material to the slot assembly.
19 *
20 * <p>The resulting slot still represents one published artifact.
21 *
22 * @param artifact direct input notation understood by the implementation
23 */
24 void from(Object artifact);
25
26 /**
27 * Selects outputs from the whole variant scope.
28 *
29 * @param action output selection rule
30 */
31 void fromVariant(Action<? super OutputSelectionSpec> action);
32
33 default void fromVariant(Closure<?> closure) {
34 fromVariant(Closures.action(closure));
35 }
36
37 /**
38 * Selects outputs from a role inside the current variant.
39 *
40 * @param roleName role name used only for assembly-time selection
41 * @param action output selection rule
42 */
43 void fromRole(String roleName, Action<? super OutputSelectionSpec> action);
44
45 default void fromRole(String roleName, Closure<?> closure) {
46 fromRole(roleName, Closures.action(closure));
47 }
48
49 /**
50 * Selects outputs from a layer inside the current variant.
51 *
52 * @param layerName layer name used only for assembly-time selection
53 * @param action output selection rule
54 */
55 void fromLayer(String layerName, Action<? super OutputSelectionSpec> action);
56
57 default void fromLayer(String layerName, Closure<?> closure) {
58 fromLayer(layerName, Closures.action(closure));
59 }
60 }
@@ -0,0 +1,14
1 package org.implab.gradle.variants.artifacts;
2
3 import org.implab.gradle.variants.core.Variant;
4
5 /**
6 * Identity of a published artifact slot inside a variant outgoing contract.
7 *
8 * <p>This is a cheap immutable identity object suitable for discovery and selection. Heavy build state is
9 * obtained separately through {@link ArtifactAssemblies}.
10 *
11 * @param variant variant owning the outgoing contract
12 * @param slot slot identity inside the variant
13 */
14 public record ArtifactSlot(Variant variant, Slot slot) {}
@@ -0,0 +1,48
1 package org.implab.gradle.variants.artifacts;
2
3 import java.util.Optional;
4 import java.util.Set;
5
6 import org.implab.gradle.variants.core.Variant;
7
8 /**
9 * Finalized view of artifact slot identities.
10 *
11 * <p>This view exposes only cheap slot identities. Assemblies and publication state are resolved
12 * separately.
13 */
14 public interface ArtifactSlotsView {
15 /**
16 * Returns all declared slot identities.
17 *
18 * @return all slots known to the finalized model
19 */
20 Set<ArtifactSlot> getSlots();
21
22 /**
23 * Returns all slots declared for the given variant.
24 *
25 * @param variant variant identity
26 * @return slots declared for the variant
27 */
28 Set<ArtifactSlot> getSlotsForVariant(Variant variant);
29
30 /**
31 * Finds a slot by typed identities.
32 *
33 * @param variant variant identity
34 * @param slot slot identity inside the variant
35 * @return matching slot when present
36 */
37 Optional<ArtifactSlot> findSlot(Variant variant, Slot slot);
38
39 /**
40 * Requires a slot by typed identities.
41 *
42 * @param variant variant identity
43 * @param slot slot identity inside the variant
44 * @return matching slot
45 */
46 ArtifactSlot requireSlot(Variant variant, Slot slot);
47
48 }
@@ -0,0 +1,59
1 package org.implab.gradle.variants.artifacts;
2
3 import org.gradle.api.Action;
4 import org.gradle.api.Task;
5 import org.gradle.api.attributes.AttributeContainer;
6 import groovy.lang.Closure;
7 import org.implab.gradle.common.core.lang.Closures;
8
9 /**
10 * Materialized outgoing publication state of a single slot.
11 *
12 * <p>This type is a DSL facade to represent already created publication-facing state. Slot-specific
13 * publication tweaks should be applied here rather than through {@link OutgoingVariantSpec}, which
14 * is limited to the root outgoing configuration of the variant.
15 */
16 public interface OutgoingArtifactSlotSpec {
17 /**
18 * Returns the published slot identity.
19 *
20 * @return slot identity
21 */
22 ArtifactSlot getArtifactSlot();
23
24 /**
25 * Returns the assembly backing the published slot.
26 *
27 * @return slot assembly
28 */
29 ArtifactAssembly getAssembly();
30
31 /**
32 * Returns whether this slot is the primary outgoing artifact set of the variant.
33 *
34 * @return {@code true} for the primary slot
35 */
36 boolean isPrimary();
37
38 /**
39 * Configures the task producing the slot artifact.
40 *
41 * @param action task configuration action
42 */
43 void assemblyTask(Action<? super Task> action);
44
45 default void assemblyTask(Closure<?> closure) {
46 assemblyTask(Closures.action(closure));
47 }
48
49 /**
50 * Configures attributes of this slot publication.
51 *
52 * @param action artifact attribute configuration action
53 */
54 void artifactAttributes(Action<? super AttributeContainer> action);
55
56 default void artifactAttributes(Closure<?> closure) {
57 artifactAttributes(Closures.action(closure));
58 }
59 }
@@ -0,0 +1,43
1 package org.implab.gradle.variants.artifacts;
2
3 import org.gradle.api.Action;
4 import org.gradle.api.artifacts.Configuration;
5 import org.implab.gradle.common.core.lang.Closures;
6 import org.implab.gradle.variants.core.Variant;
7
8 import groovy.lang.Closure;
9
10 /**
11 * Materialized root outgoing configuration of a variant.
12 *
13 * <p>This is a variant-level publication hook. Slot-specific publication state is exposed separately via
14 * {@link OutgoingArtifactSlotSpec}.
15 */
16 public interface OutgoingVariantSpec {
17 /**
18 * Returns the variant whose outgoing configuration is represented here.
19 *
20 * @return variant identity
21 */
22 Variant getVariant();
23
24 /**
25 * Returns the root consumable outgoing configuration of the variant.
26 *
27 * @return outgoing configuration
28 */
29 Configuration getConfiguration();
30
31 /**
32 * Applies a configuration action to the root outgoing configuration.
33 *
34 * @param action configuration action
35 */
36 default void configuration(Action<? super Configuration> action) {
37 action.execute(getConfiguration());
38 }
39
40 default void configuration(Closure<?> closure) {
41 configuration(Closures.action(closure));
42 }
43 }
@@ -0,0 +1,24
1 package org.implab.gradle.variants.artifacts;
2
3 /**
4 * DSL model for selecting named outputs from a chosen source scope.
5 *
6 * <p>The selected outputs are inputs to slot assembly. They do not create additional outgoing slots or
7 * affect slot identity.
8 */
9 public interface OutputSelectionSpec {
10 /**
11 * Selects one named output.
12 *
13 * @param name output name
14 */
15 void output(String name);
16
17 /**
18 * Selects several named outputs.
19 *
20 * @param name first output name
21 * @param extra additional output names
22 */
23 void output(String name, String... extra);
24 }
@@ -0,0 +1,12
1 package org.implab.gradle.variants.artifacts;
2
3 import org.gradle.api.Named;
4
5 /**
6 * Named identity of a slot inside a variant outgoing contract.
7 *
8 * <p>A slot does not identify a published artifact on its own. Combine it with a variant to obtain an
9 * {@link ArtifactSlot}.
10 */
11 public interface Slot extends Named {
12 }
@@ -0,0 +1,28
1 package org.implab.gradle.variants.artifacts;
2
3 import org.gradle.api.NamedDomainObjectContainer;
4 import org.gradle.api.NamedDomainObjectProvider;
5 import org.gradle.api.artifacts.Configuration;
6 import org.implab.gradle.variants.core.Variant;
7
8 /** ΠžΠΏΠΈΡΡ‹Π²Π°Π΅Ρ‚ конфигурация Π²Π°Ρ€ΠΈΠ°Π½Ρ‚Π° исходящСй ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ */
9 public interface VariantArtifactsConfiguration {
10 /**
11 * Π˜ΡΡ…ΠΎΠ΄Π½Ρ‹ΠΉ Π²Π°Ρ€ΠΈΠ°Π½Ρ‚ для ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠ³ΠΎ строится Outgoing конфигурация
12 */
13 Variant getVariant();
14
15 /**
16 * ΠŸΡ€ΠΎΠ²Π°ΠΉΠ΄Π΅Ρ€ зарСгистрированной ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ
17 */
18 NamedDomainObjectProvider<Configuration> getOutgoingConfiguration();
19
20 /**
21 * Π‘Π»ΠΎΡ‚Ρ‹ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ, данная коллСкция Тивая, ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ для
22 * получСния ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ ΠΎΠ± ΠΎΠ±ΡŠΡΠ²Π»Π΅Π½Π½Ρ‹Ρ… слотах, Π½ΠΎ эти слоты Π½Π΅
23 * обязаны Π±Ρ‹Ρ‚ΡŒ сконфигурированы, Ρ‚.Π΅. это Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Identity.
24 *
25 * @see {@link ArtifactSlot}
26 */
27 NamedDomainObjectContainer<Slot> getSlots();
28 }
@@ -0,0 +1,27
1 package org.implab.gradle.variants.artifacts;
2
3 import java.util.Optional;
4
5 import org.gradle.api.Action;
6 import org.implab.gradle.variants.core.Variant;
7 import org.implab.gradle.variants.core.VariantsView;
8
9 /**
10 * ΠšΠΎΠ½Ρ‚Π΅ΠΊΡΡ‚ Ρ€Π°Π±ΠΎΡ‚Ρ‹ с Π²Π°Ρ€ΠΈΠ°Π½Ρ‚Π°ΠΌΠΈ ΠΏΡƒΠ±Π»ΠΈΠΊΠ°Ρ†ΠΈΠΈ, становится доступным послС
11 * Ρ„ΠΈΠ½Π°Π»ΠΈΠ·Π°Ρ†ΠΈΠΈ ΠΌΠΎΠ΄Π΅Π»ΠΈ Π²Π°Ρ€ΠΈΠ°Π½Ρ‚ΠΎΠ². ЀактичСски являСтся ΠΆΠΈΠ²ΠΎΠΉ модСлью
12 */
13 public interface VariantArtifactsContext {
14
15 /**
16 * ЗафиксированноС прСдставлСниС ΠΎ Π²Π°Ρ€ΠΈΠ°Π½Ρ‚Π°Ρ… Π½Π° основС ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠ³ΠΎ Π°Π΄Π°ΠΏΡ‚Π΅Ρ€Ρ‹ ΠΌΠΎΠ³ΡƒΡ‚
17 * ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ Π°Ρ€Ρ‚Π΅Ρ„Π°ΠΊΡ‚Ρ‹ ΠΈ исходящиС ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ
18 */
19 VariantsView getVariants();
20
21 void all(Action<? super VariantArtifactsConfiguration> action);
22
23 Optional<VariantArtifactsConfiguration> findArtifacts(Variant variant);
24
25 VariantArtifactsConfiguration requireArtifacts(Variant variant);
26
27 }
@@ -0,0 +1,59
1 package org.implab.gradle.variants.artifacts;
2
3 import org.gradle.api.Action;
4 import org.implab.gradle.common.core.lang.Closures;
5
6 import groovy.lang.Closure;
7
8 /**
9 * Project-level DSL entry point for declaring outgoing artifacts per variant.
10 *
11 * <p>A variant represents one external outgoing contract. Slots declared inside a variant represent
12 * artifact sets within that contract. One slot is expected to materialize to one published artifact.
13 */
14 public interface VariantArtifactsExtension {
15 /**
16 * Configures artifact slots of the named variant.
17 *
18 * @param variantName variant name
19 * @param action variant artifact declaration
20 */
21 void variant(String variantName, Action<? super VariantArtifactsSpec> action);
22
23 default void variant(String variantName, Closure<?> closure) {
24 variant(variantName, Closures.action(closure));
25 }
26
27 /**
28 * Registers a callback invoked with the finalized artifact model.
29 *
30 * @param action finalized-model callback
31 */
32 void whenFinalized(Action<? super VariantArtifactsContext> action);
33
34 default void whenFinalized(Closure<?> closure) {
35 whenFinalized(Closures.action(closure));
36 }
37
38 /**
39 * Registers a callback invoked for each materialized root outgoing configuration.
40 *
41 * @param action variant-level outgoing configuration callback
42 */
43 void whenOutgoingVariant(Action<? super OutgoingVariantSpec> action);
44
45 default void whenOutgoingVariant(Closure<?> closure) {
46 whenOutgoingVariant(Closures.action(closure));
47 }
48
49 /**
50 * Registers a callback invoked for each materialized outgoing slot publication.
51 *
52 * @param action slot-level outgoing publication callback
53 */
54 void whenOutgoingSlot(Action<? super OutgoingArtifactSlotSpec> action);
55
56 default void whenOutgoingSlot(Closure<?> closure) {
57 whenOutgoingSlot(Closures.action(closure));
58 }
59 }
@@ -0,0 +1,40
1 package org.implab.gradle.variants.artifacts;
2
3 import org.gradle.api.Action;
4 import groovy.lang.Closure;
5 import org.implab.gradle.common.core.lang.Closures;
6
7 /**
8 * DSL model for declaring slots of a single variant.
9 *
10 * <p>The surrounding variant defines the external outgoing contract. Slots declared here define artifact
11 * sets within that contract. If a variant exposes more than one slot, one of them is expected to be the
12 * primary slot.
13 */
14 public interface VariantArtifactsSpec {
15 /**
16 * Declares a non-primary slot of the current variant.
17 *
18 * @param name slot name
19 * @param action slot declaration
20 * @return slot identity
21 */
22 Slot slot(String name, Action<? super ArtifactAssemblySpec> action);
23
24 default Slot slot(String name, Closure<?> closure) {
25 return slot(name, Closures.action(closure));
26 }
27
28 /**
29 * Declares the primary slot of the current variant.
30 *
31 * @param name slot name
32 * @param action slot declaration
33 * @return slot identity
34 */
35 Slot primarySlot(String name, Action<? super ArtifactAssemblySpec> action);
36
37 default Slot primarySlot(String name, Closure<?> closure) {
38 return primarySlot(name, Closures.action(closure));
39 }
40 }
@@ -0,0 +1,37
1 package org.implab.gradle.variants.artifacts.internal;
2
3 import java.util.Optional;
4
5 import org.gradle.api.Action;
6 import org.implab.gradle.variants.artifacts.VariantArtifactsConfiguration;
7 import org.implab.gradle.variants.artifacts.VariantArtifactsContext;
8 import org.implab.gradle.variants.core.Variant;
9 import org.implab.gradle.variants.core.VariantsView;
10
11 public class DefaultVariantArtifactsContext implements VariantArtifactsContext {
12
13 @Override
14 public VariantsView getVariants() {
15 // TODO Auto-generated method stub
16 throw new UnsupportedOperationException("Unimplemented method 'getVariants'");
17 }
18
19 @Override
20 public void all(Action<? super VariantArtifactsConfiguration> action) {
21 // TODO Auto-generated method stub
22 throw new UnsupportedOperationException("Unimplemented method 'all'");
23 }
24
25 @Override
26 public Optional<VariantArtifactsConfiguration> findArtifacts(Variant variant) {
27 // TODO Auto-generated method stub
28 throw new UnsupportedOperationException("Unimplemented method 'findArtifacts'");
29 }
30
31 @Override
32 public VariantArtifactsConfiguration requireArtifacts(Variant variant) {
33 // TODO Auto-generated method stub
34 throw new UnsupportedOperationException("Unimplemented method 'requireArtifacts'");
35 }
36
37 }
@@ -0,0 +1,11
1 package org.implab.gradle.variants.artifacts.internal;
2
3 import org.gradle.api.Action;
4 import org.implab.gradle.variants.artifacts.VariantArtifactsSpec;
5 import org.implab.gradle.variants.core.Variant;
6
7 public class VariantArtifactsRegistry {
8 public void configureVariant(Variant variant, Action<? super VariantArtifactsSpec> action) {
9
10 }
11 }
@@ -0,0 +1,41
1 /**
2 * Variant-scoped outgoing artifacts.
3 *
4 * <p>This package models the external artifact contract of a project in terms of variant-local slots.
5 * A variant represents one outgoing contract, while a slot represents one artifact set inside that
6 * contract.
7 *
8 * <p>The model intentionally separates cheap identity objects from stateful build objects:
9 *
10 * <ul>
11 * <li>{@link org.implab.gradle.variants.artifacts.ArtifactSlot} identifies a published slot inside a
12 * variant;</li>
13 * <li>{@link org.implab.gradle.variants.artifacts.ArtifactAssembly} is the lazily materialized body of
14 * that slot.</li>
15 * </ul>
16 *
17 * <p>Each slot is expected to materialize to exactly one published artifact: either one file or one
18 * directory. Internal build topology such as roles may participate in slot assembly rules, but does not
19 * belong to external artifact identity.
20 *
21 * <p>Typical usage:
22 *
23 * <pre>{@code
24 * variantArtifacts {
25 * variant("browser") {
26 * primarySlot("runtime") {
27 * fromRole("main") { output("js") }
28 * }
29 * slot("sources") {
30 * fromLayer("main") { output("sources") }
31 * }
32 * }
33 * }
34 * }</pre>
35 *
36 * <p>After finalization, slot identities can be observed through
37 * {@link org.implab.gradle.variants.artifacts.VariantArtifactsContext#getSlots()}, while slot bodies are
38 * obtained on demand through
39 * {@link org.implab.gradle.variants.artifacts.VariantArtifactsContext#getAssemblies()}.
40 */
41 package org.implab.gradle.variants.artifacts;
@@ -1,52 +1,54
1 1 # AGENTS.md
2 2
3 3 ## ΠŸΡ€ΠΎΠ΅ΠΊΡ‚Π½Ρ‹Π΅ договорСнности
4 4
5 5 ### ΠŸΡƒΠ±Π»ΠΈΡ‡Π½ΠΎΠ΅ API Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊ
6 6
7 7 - ΠŸΡ€Π΅Π΄ΠΏΠΎΡ‡Ρ‚ΠΈΡ‚Π΅Π»Π΅Π½ `non-null` ΠΏΠΎΠ΄Ρ…ΠΎΠ΄.
8 8 - Π’Π°ΠΌ, Π³Π΄Π΅ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ ΠΆΠΈΠ²Π΅Ρ‚ Π² Gradle Provider API, возвращаСтся `Provider<T>` (Π½Π΅ `null`).
9 9 - Π’Π°ΠΌ, Π³Π΄Π΅ lookup синхронный, возвращаСтся `Optional<T>` (Π½Π΅ `null`).
10 10 - `find*` рассматриваСтся ΠΊΠ°ΠΊ синоним legacy `get*` (поиск Π±Π΅Π· `fail-fast`).
11 11 - `require*` это `find*` + `fail-fast` с понятной ошибкой Π² мСстС Π²Ρ‹Π·ΠΎΠ²Π°.
12 12 - Для Π½ΠΎΠ²ΠΎΠ³ΠΎ API ΠΏΡ€Π΅Π΄ΠΏΠΎΡ‡Ρ‚ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹ Ρ„ΠΎΡ€ΠΌΡ‹ `find/require`; Π½ΠΎΠ²Ρ‹Π΅ `get*` ΠΏΠΎ возмоТности Π½Π΅ Π΄ΠΎΠ±Π°Π²Π»ΡΡ‚ΡŒ.
13 - Π˜Π½Ρ‚Π΅Ρ€Ρ„Π΅ΠΉΡΡ‹ ΠΈ классы, ΠΎΠΏΠΈΡΡ‹Π²Π°ΡŽΡ‰ΠΈΠ΅ ΠΌΠΎΠ΄Π΅Π»ΠΈ DSL Π΄ΠΎΠ»ΠΆΠ½Ρ‹ ΠΈΠΌΠ΅Ρ‚ΡŒ суффикс `Spec` Ρƒ ΠΌΠΎΠ΄Π΅Π»Π΅ΠΉ ΠΎΠΏΠΈΡΡ‹Π²Π°ΡŽΡ‰ΠΈΡ… ΡƒΡ€ΠΎΠ²Π΅Π½ΡŒ сСрвисов ΠΈ состояниС сцСнария сборки Ρ‚Π°ΠΊΠΎΠ³ΠΎ суффикса Π½Π΅ Π΄ΠΎΠ»ΠΆΠ½ΠΎ Π±Ρ‹Ρ‚ΡŒ.
14 - МодСль Ρ€Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΡ Π½Π° ΡƒΡ€ΠΎΠ²Π½Π΅ ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π° Π΄ΠΎΠ»ΠΆΠ½Π° ΠΈΠΌΠ΅Ρ‚ΡŒ суффикс `Extension`.
13 15
14 16 ### ДокумСнтация
15 17
16 18 - Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ ΠΊΠΎΠ΄Π° Π΄ΠΎΠ»ΠΆΠ½ΠΎ Π±Ρ‹Ρ‚ΡŒ Π½Π° английском языкС
17 19 - ΠΊ ΠΏΡƒΠ±Π»ΠΈΡ‡Π½ΠΎΠΌΡƒ API
18 20 - описаниС Π΄ΠΎΠ»ΠΆΠ½ΠΎ ΠΎΡ‚Ρ€Π°ΠΆΠ°Ρ‚ΡŒ Π½Π°Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅, Π³Π΄Π΅ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ ΠΈ ΠΊΠ°ΠΊΠΎΠ΅ влияниС ΠΎΠΊΠ°Π·Ρ‹Π²Π°Π΅Ρ‚ Π½Π° ΠΎΡΡ‚Π°Π»ΡŒΠ½Ρ‹Π΅ части ΠΏΡ€ΠΎΠ³Ρ€Π°ΠΌΠΌΡ‹
19 21 - Π΄Π°Π²Π°Ρ‚ΡŒ нСбольшоС описаниС ΠΊΠΎΠ½Ρ†Π΅ΠΏΡ†ΠΈΠΈ, Π° Ρ‚Π°ΠΊΠΆΠ΅ ΠΊΡ€Π°Ρ‚ΠΊΠΈΠ΅ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹
20 22 - ΠΊ ΠΏΡ€ΠΈΠ²Π°Ρ‚Π½ΠΎΠΌΡƒ API достаточно Π΄Π°Π²Π°Ρ‚ΡŒ ΠΊΡ€Π°Ρ‚ΠΊΡƒΡŽ справку ΠΎ Π½Π°Π·Π½Π°Ρ‡Π΅Π½ΠΈΠΈ ΠΈ использовании
21 23 - Ρ€Π΅Π°Π»ΠΈΠ·Π°Ρ†ΠΈΡŽ Π°Π»Π³ΠΎΡ€ΠΈΡ‚ΠΌΠΎΠ² Π² ΠΊΠΎΠ΄Π΅ ΡΠΎΠΏΡ€ΠΎΠ²ΠΎΠΆΠ΄Π°Ρ‚ΡŒ коммСнтариями с пояснСниями, Ρ‚Ρ€ΠΈΠ²ΠΈΠ°Π»ΡŒΠ½Ρ‹Π΅ ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΠΈ ΠΏΠΎΡΡΠ½ΡΡ‚ΡŒ Π½Π΅ трСбуСтся.
22 24 - докумСнтация Π΄ΠΎΠ»ΠΆΠ½Π° Ρ„ΠΎΡ€ΠΌΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒΡΡ согласно трСбованиям ΠΏΠΎ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΡŽ Ρ‚ΠΈΠΏΠ° javadoc, jsdoc ΠΈ Ρ‚.ΠΏ., Π² зависимости ΠΎΡ‚ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌΡ‹Ρ… Π² ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π΅ языках ΠΈ инструмСнтах.
23 25
24 26 ## Identity-first modeling
25 27
26 28 Prefer an **identity-first** split between:
27 29
28 30 - **identity objects** used for discovery and selection
29 31 - **stateful/materialized objects** obtained through separate API calls
30 32
31 33 ### Rules
32 34
33 35 - Objects intended for replayable observation (`all(...)`, similar collection APIs) should be **identity objects**.
34 36 - Identity objects must be:
35 37 - cheap to create
36 38 - effectively immutable
37 39 - limited to identity and cheap selection metadata
38 40 - Heavy, computed, provider-based, or runtime-bound state must be resolved separately.
39 41 - Aggregate content should be accessed through dedicated lookup/materialization APIs.
40 42 - Eager observation of identity is acceptable.
41 43 - Eager observation of computed state is not.
42 44
43 45 ### Do not
44 46
45 47 - Do not store heavy computed state inside identity objects.
46 48 - Do not store runtime references to foreign domains inside identity objects.
47 49 - Do not use custom events when replayable identity registries plus on-demand lookup are sufficient.
48 50
49 51 ### Rule of thumb
50 52
51 53 **Identity objects contain selection metadata.
52 54 Aggregate content is obtained separately, on demand.**
@@ -1,78 +1,78
1 1 package org.implab.gradle.variants.sources;
2 2
3 3 import java.util.HashMap;
4 4 import java.util.Map;
5 5 import java.util.Objects;
6 6 import java.util.Optional;
7 7 import java.util.Set;
8 8 import java.util.stream.Collectors;
9 9
10 10 import org.eclipse.jdt.annotation.NonNullByDefault;
11 11 import org.implab.gradle.variants.core.Layer;
12 12 import org.implab.gradle.variants.core.Role;
13 13 import org.implab.gradle.variants.core.Variant;
14 14 import org.implab.gradle.variants.core.VariantsView;
15 15 import org.implab.gradle.variants.core.VariantsView.VariantRoleLayer;
16 16
17 17 @NonNullByDefault
18 18 public final class CompileUnitsView {
19 19
20 20 private final VariantsView variants;
21 21 private final Map<Variant, Set<CompileUnit>> unitsByVariant = new HashMap<>();
22 22
23 23 private CompileUnitsView(VariantsView variants) {
24 24 this.variants = variants;
25 25 }
26 26
27 27 public Set<CompileUnit> getUnits() {
28 28 return variants.getEntries().stream()
29 29 .map(CompileUnit::of)
30 30 .collect(Collectors.toUnmodifiableSet());
31 31 }
32 32
33 33 public Set<CompileUnit> getUnitsForVariant(Variant variant) {
34 34 Objects.requireNonNull(variant, "Variant can't be null");
35 35
36 36 return unitsByVariant.computeIfAbsent(variant, key -> variants
37 37 .getEntriesForVariant(variant).stream()
38 38 .map(CompileUnit::of)
39 39 .collect(Collectors.toUnmodifiableSet()));
40 40 }
41 41
42 42 public Optional<CompileUnit> findUnit(Variant variant, Layer layer) {
43 43 Objects.requireNonNull(variant, "Variant can't be null");
44 44 Objects.requireNonNull(layer, "Layer can't be null");
45 45
46 46 return getUnitsForVariant(variant).stream()
47 47 .filter(u -> u.layer().equals(layer))
48 48 .findAny();
49 49 }
50 50
51 51 public boolean contains(Variant variant, Layer layer) {
52 52 return findUnit(variant, layer).isPresent();
53 53 }
54 54
55 55 /**
56 56 * In which logical roles this compile unit participates.
57 57 */
58 58
59 59 public Set<Role> getRoles(CompileUnit unit) {
60 60 Objects.requireNonNull(unit, "Compile unit can't be null");
61 61 return variants.getEntriesForVariant(unit.variant()).stream()
62 62 .filter(entry -> entry.layer().equals(unit.layer()))
63 63 .map(VariantRoleLayer::role)
64 64 .collect(Collectors.toUnmodifiableSet());
65 65 }
66 66
67 public CompileUnit getUnit(Variant variant, Layer layer) {
67 public CompileUnit requireUnit(Variant variant, Layer layer) {
68 68 return findUnit(variant, layer)
69 69 .orElseThrow(() -> new IllegalArgumentException(
70 70 "Compile unit for variant '" + variant.getName()
71 71 + "' and layer '" + layer.getName() + "' not found"));
72 72 }
73 73
74 74 public static CompileUnitsView of(VariantsView variantsView) {
75 75 Objects.requireNonNull(variantsView, "variantsView can't be null");
76 76 return new CompileUnitsView(variantsView);
77 77 }
78 78 } No newline at end of file
@@ -1,431 +1,431
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.junit.jupiter.api.Test;
7 7
8 8 class VariantSourcesPluginFunctionalTest extends AbstractFunctionalTest {
9 9
10 10 @Test
11 11 void exposesDerivedViewsAndStableSourceSetProvider() throws Exception {
12 12 writeSettings("variant-sources-derived-views");
13 13 writeBuildFile("""
14 14 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
15 15
16 16 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
17 17 variantsExt.layers.create('main')
18 18 variantsExt.layers.create('test')
19 19 variantsExt.roles.create('production')
20 20 variantsExt.roles.create('test')
21 21
22 22 variantsExt.variant('browser') {
23 23 role('production') { layers('main') }
24 24 role('test') { layers('main', 'test') }
25 25 }
26 26
27 27 def lines = []
28 28
29 29 variantSources.whenFinalized { ctx ->
30 30 lines << "units=" + ctx.compileUnits.units
31 31 .collect { "${it.variant().name}:${it.layer().name}" }
32 32 .sort()
33 33 .join(',')
34 34
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 38 def projection = ctx.roleProjections.getProjection(browser, production)
39 def unit = ctx.compileUnits.getUnit(browser, mainLayer)
39 def unit = ctx.compileUnits.requireUnit(browser, mainLayer)
40 40
41 41 def left = ctx.sourceSets.getSourceSet(unit)
42 42 def right = ctx.sourceSets.getSourceSet(unit)
43 43
44 44 lines << "projectionUnits=" + ctx.roleProjections.getUnits(projection)
45 45 .collect { it.layer().name }
46 46 .sort()
47 47 .join(',')
48 48 lines << "mainSourceSet=" + left.name
49 49 lines << "sameProvider=" + left.is(right)
50 50 }
51 51
52 52 afterEvaluate {
53 53 variantSources.whenFinalized { ctx ->
54 54 lines << "late:variants=" + ctx.variants.variants.collect { it.name }.sort().join(',')
55 55 }
56 56 }
57 57
58 58 tasks.register('probe') {
59 59 doLast {
60 60 lines.each { println(it) }
61 61 }
62 62 }
63 63 """);
64 64
65 65 BuildResult result = runner("probe").build();
66 66
67 67 assertTrue(result.getOutput().contains("units=browser:main,browser:test"));
68 68 assertTrue(result.getOutput().contains("projectionUnits=main"));
69 69 assertTrue(result.getOutput().contains("mainSourceSet=browserMain"));
70 70 assertTrue(result.getOutput().contains("sameProvider=true"));
71 71 assertTrue(result.getOutput().contains("late:variants=browser"));
72 72 }
73 73
74 74 @Test
75 75 void appliesSelectorPrecedenceForFutureMaterialization() throws Exception {
76 76 writeSettings("variant-sources-precedence");
77 77 writeBuildFile("""
78 78 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
79 79
80 80 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
81 81 variantsExt.layers.create('main')
82 82 variantsExt.layers.create('test')
83 83 variantsExt.roles.create('production')
84 84 variantsExt.roles.create('test')
85 85
86 86 variantsExt.variant('browser') {
87 87 role('production') { layers('main') }
88 88 role('test') { layers('main', 'test') }
89 89 }
90 90
91 91 variantsExt.variant('node') {
92 92 role('production') { layers('main') }
93 93 }
94 94
95 95 def events = []
96 96
97 97 variantSources {
98 98 variant('browser') {
99 99 events << "variant:" + name
100 100 }
101 101 layer('main') {
102 102 events << "layer:" + name
103 103 }
104 104 unit('browser', 'main') {
105 105 events << "unit:" + name
106 106 }
107 107 }
108 108
109 109 afterEvaluate {
110 110 variantSources.whenFinalized { ctx ->
111 111 def browser = ctx.variants.variants.find { it.name == 'browser' }
112 112 def node = ctx.variants.variants.find { it.name == 'node' }
113 113 def mainLayer = ctx.variants.layers.find { it.name == 'main' }
114 114 def testLayer = ctx.variants.layers.find { it.name == 'test' }
115 115
116 def browserMain = ctx.sourceSets.getSourceSet(ctx.compileUnits.getUnit(browser, mainLayer)).get()
117 def browserTest = ctx.sourceSets.getSourceSet(ctx.compileUnits.getUnit(browser, testLayer)).get()
118 def nodeMain = ctx.sourceSets.getSourceSet(ctx.compileUnits.getUnit(node, mainLayer)).get()
116 def browserMain = ctx.sourceSets.getSourceSet(ctx.compileUnits.requireUnit(browser, mainLayer)).get()
117 def browserTest = ctx.sourceSets.getSourceSet(ctx.compileUnits.requireUnit(browser, testLayer)).get()
118 def nodeMain = ctx.sourceSets.getSourceSet(ctx.compileUnits.requireUnit(node, mainLayer)).get()
119 119 def bySourceSet = events.groupBy { it.split(':', 2)[1] }
120 120
121 121 println("browserMain=" + bySourceSet[browserMain.name].collect { it.split(':', 2)[0] }.join(','))
122 122 println("browserTest=" + bySourceSet[browserTest.name].collect { it.split(':', 2)[0] }.join(','))
123 123 println("nodeMain=" + bySourceSet[nodeMain.name].collect { it.split(':', 2)[0] }.join(','))
124 124 }
125 125 }
126 126 """);
127 127
128 128 BuildResult result = runner("help").build();
129 129
130 130 assertTrue(result.getOutput().contains("browserMain=variant,layer,unit"));
131 131 assertTrue(result.getOutput().contains("browserTest=variant"));
132 132 assertTrue(result.getOutput().contains("nodeMain=layer"));
133 133 }
134 134
135 135 @Test
136 136 void failsLateConfigurationByDefaultAfterMaterialization() throws Exception {
137 137 writeSettings("variant-sources-late-fail");
138 138 writeBuildFile("""
139 139 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
140 140
141 141 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
142 142 variantsExt.layers.create('main')
143 143 variantsExt.roles.create('production')
144 144 variantsExt.variant('browser') {
145 145 role('production') { layers('main') }
146 146 }
147 147
148 148 afterEvaluate {
149 149 variantSources.whenFinalized { ctx ->
150 150 def browser = ctx.variants.variants.find { it.name == 'browser' }
151 151 def mainLayer = ctx.variants.layers.find { it.name == 'main' }
152 def unit = ctx.compileUnits.getUnit(browser, mainLayer)
152 def unit = ctx.compileUnits.requireUnit(browser, mainLayer)
153 153
154 154 ctx.sourceSets.getSourceSet(unit).get()
155 155 variantSources.layer('main') {
156 156 declareOutputs('late')
157 157 }
158 158 }
159 159 }
160 160 """);
161 161
162 162 assertBuildFails("Source sets for [layer=main] layer already materialized", "help");
163 163 }
164 164
165 165 @Test
166 166 void allowsLateConfigurationWhenSelectedBeforeFirstSelector() throws Exception {
167 167 writeSettings("variant-sources-late-allow");
168 168 writeBuildFile("""
169 169 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
170 170
171 171 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
172 172 variantsExt.layers.create('main')
173 173 variantsExt.roles.create('production')
174 174 variantsExt.variant('browser') {
175 175 role('production') { layers('main') }
176 176 }
177 177
178 178 variantSources {
179 179 lateConfigurationPolicy {
180 180 allowLateConfiguration()
181 181 }
182 182 }
183 183
184 184 afterEvaluate {
185 185 variantSources.whenFinalized { ctx ->
186 186 def browser = ctx.variants.variants.find { it.name == 'browser' }
187 187 def mainLayer = ctx.variants.layers.find { it.name == 'main' }
188 def unit = ctx.compileUnits.getUnit(browser, mainLayer)
188 def unit = ctx.compileUnits.requireUnit(browser, mainLayer)
189 189
190 190 def sourceSet = ctx.sourceSets.getSourceSet(unit).get()
191 191 variantSources.layer('main') {
192 192 declareOutputs('late')
193 193 }
194 194 sourceSet.output('late')
195 195 println('lateAllowed=ok')
196 196 }
197 197 }
198 198 """);
199 199
200 200 BuildResult result = runner("help").build();
201 201 assertTrue(result.getOutput().contains("lateAllowed=ok"));
202 202 }
203 203
204 204 @Test
205 205 void warnsAndAppliesLateConfigurationWhenWarnModeSelected() throws Exception {
206 206 writeSettings("variant-sources-late-warn");
207 207 writeBuildFile("""
208 208 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
209 209
210 210 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
211 211 variantsExt.layers.create('main')
212 212 variantsExt.roles.create('production')
213 213 variantsExt.variant('browser') {
214 214 role('production') { layers('main') }
215 215 }
216 216
217 217 variantSources {
218 218 lateConfigurationPolicy {
219 219 warnOnLateConfiguration()
220 220 }
221 221 }
222 222
223 223 afterEvaluate {
224 224 variantSources.whenFinalized { ctx ->
225 225 def browser = ctx.variants.variants.find { it.name == 'browser' }
226 226 def mainLayer = ctx.variants.layers.find { it.name == 'main' }
227 def unit = ctx.compileUnits.getUnit(browser, mainLayer)
227 def unit = ctx.compileUnits.requireUnit(browser, mainLayer)
228 228
229 229 def sourceSet = ctx.sourceSets.getSourceSet(unit).get()
230 230 variantSources.layer('main') {
231 231 declareOutputs('late')
232 232 }
233 233 sourceSet.output('late')
234 234 println('lateWarn=ok')
235 235 }
236 236 }
237 237 """);
238 238
239 239 BuildResult result = runner("help").build();
240 240
241 241 assertTrue(result.getOutput().contains("Source sets for [layer=main] layer already materialized"));
242 242 assertTrue(result.getOutput().contains("lateWarn=ok"));
243 243 }
244 244
245 245 @Test
246 246 void rejectsChangingLateConfigurationPolicyAfterFirstSelector() throws Exception {
247 247 writeSettings("variant-sources-late-policy-fixed");
248 248 writeBuildFile("""
249 249 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
250 250
251 251 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
252 252 variantsExt.layers.create('main')
253 253 variantsExt.roles.create('production')
254 254 variantsExt.variant('browser') {
255 255 role('production') { layers('main') }
256 256 }
257 257
258 258 variantSources {
259 259 variant('browser') {
260 260 declareOutputs('js')
261 261 }
262 262 lateConfigurationPolicy {
263 263 allowLateConfiguration()
264 264 }
265 265 }
266 266 """);
267 267
268 268 assertBuildFails("Lazy configuration policy already applied", "help");
269 269 }
270 270
271 271 @Test
272 272 void failsOnProjectedNameCollisionByDefault() throws Exception {
273 273 writeSettings("variant-sources-name-collision-fail");
274 274 writeBuildFile("""
275 275 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
276 276
277 277 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
278 278 variantsExt.layers.create('variantBar')
279 279 variantsExt.layers.create('bar')
280 280 variantsExt.roles.create('production')
281 281
282 282 variantsExt.variant('foo') {
283 283 role('production') { layers('variantBar') }
284 284 }
285 285 variantsExt.variant('fooVariant') {
286 286 role('production') { layers('bar') }
287 287 }
288 288 """);
289 289
290 290 assertBuildFails("The same source set names are produced by different compile units", "help");
291 291 }
292 292
293 293 @Test
294 294 void resolvesProjectedNameCollisionDeterministicallyWhenConfigured() throws Exception {
295 295 writeSettings("variant-sources-name-collision-resolve");
296 296 writeBuildFile("""
297 297 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
298 298
299 299 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
300 300 variantsExt.layers.create('variantBar')
301 301 variantsExt.layers.create('bar')
302 302 variantsExt.roles.create('production')
303 303
304 304 variantsExt.variant('foo') {
305 305 role('production') { layers('variantBar') }
306 306 }
307 307 variantsExt.variant('fooVariant') {
308 308 role('production') { layers('bar') }
309 309 }
310 310
311 311 variantSources {
312 312 namingPolicy {
313 313 resolveNameCollision()
314 314 }
315 315 }
316 316
317 317 afterEvaluate {
318 318 variantSources.whenFinalized { ctx ->
319 319 def foo = ctx.variants.variants.find { it.name == 'foo' }
320 320 def fooVariant = ctx.variants.variants.find { it.name == 'fooVariant' }
321 321 def variantBar = ctx.variants.layers.find { it.name == 'variantBar' }
322 322 def bar = ctx.variants.layers.find { it.name == 'bar' }
323 323
324 def later = ctx.compileUnits.getUnit(fooVariant, bar)
325 def earlier = ctx.compileUnits.getUnit(foo, variantBar)
324 def later = ctx.compileUnits.requireUnit(fooVariant, bar)
325 def earlier = ctx.compileUnits.requireUnit(foo, variantBar)
326 326
327 327 println("map1=" + later.variant().name + ":" + later.layer().name + "->" + ctx.sourceSets.getSourceSet(later).name)
328 328 println("map2=" + earlier.variant().name + ":" + earlier.layer().name + "->" + ctx.sourceSets.getSourceSet(earlier).name)
329 329 }
330 330 }
331 331 """);
332 332
333 333 BuildResult result = runner("help").build();
334 334
335 335 assertTrue(result.getOutput().contains("map1=fooVariant:bar->fooVariantBar2"));
336 336 assertTrue(result.getOutput().contains("map2=foo:variantBar->fooVariantBar"));
337 337 }
338 338
339 339 @Test
340 340 void failsOnUnknownVariantSelectorTarget() throws Exception {
341 341 writeSettings("variant-sources-missing-variant");
342 342 writeBuildFile("""
343 343 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
344 344
345 345 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
346 346 variantsExt.layers.create('main')
347 347 variantsExt.roles.create('production')
348 348 variantsExt.variant('browser') {
349 349 role('production') { layers('main') }
350 350 }
351 351
352 352 variantSources {
353 353 variant('missing') {
354 354 declareOutputs('js')
355 355 }
356 356 }
357 357 """);
358 358
359 359 assertBuildFails("Variant 'missing' is't declared", "help");
360 360 }
361 361
362 362 @Test
363 363 void failsOnUnknownLayerSelectorTarget() throws Exception {
364 364 writeSettings("variant-sources-missing-layer");
365 365 writeBuildFile("""
366 366 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
367 367
368 368 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
369 369 variantsExt.layers.create('main')
370 370 variantsExt.roles.create('production')
371 371 variantsExt.variant('browser') {
372 372 role('production') { layers('main') }
373 373 }
374 374
375 375 variantSources {
376 376 layer('missing') {
377 377 declareOutputs('js')
378 378 }
379 379 }
380 380 """);
381 381
382 382 assertBuildFails("Layer 'missing' isn't declared", "help");
383 383 }
384 384
385 385 @Test
386 386 void failsOnUndeclaredCompileUnitSelectorTarget() throws Exception {
387 387 writeSettings("variant-sources-missing-unit");
388 388 writeBuildFile("""
389 389 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
390 390
391 391 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
392 392 variantsExt.layers.create('main')
393 393 variantsExt.layers.create('test')
394 394 variantsExt.roles.create('production')
395 395 variantsExt.variant('browser') {
396 396 role('production') { layers('main') }
397 397 }
398 398
399 399 variantSources {
400 400 unit('browser', 'test') {
401 401 declareOutputs('js')
402 402 }
403 403 }
404 404 """);
405 405
406 406 assertBuildFails("The CompileUnit isn't declared for variant 'browser', layer 'test'", "help");
407 407 }
408 408
409 409 @Test
410 410 void rejectsChangingNamingPolicyAfterContextBecomesObservable() throws Exception {
411 411 writeSettings("variant-sources-name-policy-fixed");
412 412 writeBuildFile("""
413 413 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
414 414
415 415 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
416 416 variantsExt.layers.create('main')
417 417 variantsExt.roles.create('production')
418 418 variantsExt.variant('browser') {
419 419 role('production') { layers('main') }
420 420 }
421 421
422 422 variantSources.whenFinalized {
423 423 variantSources.namingPolicy {
424 424 resolveNameCollision()
425 425 }
426 426 }
427 427 """);
428 428
429 429 assertBuildFails("Naming policy already applied", "help");
430 430 }
431 431 }
@@ -1,104 +1,104
1 1 package org.implab.gradle.variants.sources;
2 2
3 3 import static org.junit.jupiter.api.Assertions.assertEquals;
4 4 import static org.junit.jupiter.api.Assertions.assertThrows;
5 5 import static org.junit.jupiter.api.Assertions.assertTrue;
6 6
7 7 import java.lang.reflect.Constructor;
8 8 import java.util.Set;
9 9
10 10 import org.implab.gradle.variants.core.Layer;
11 11 import org.implab.gradle.variants.core.Role;
12 12 import org.implab.gradle.variants.core.Variant;
13 13 import org.implab.gradle.variants.core.VariantsView;
14 14 import org.implab.gradle.variants.core.VariantsView.VariantRoleLayer;
15 15 import org.junit.jupiter.api.Test;
16 16
17 17 class CompileUnitsViewTest {
18 18 @Test
19 19 void deduplicatesCompileUnitsAcrossRolesAndExposesParticipatingRoles() {
20 20 var browser = new TestVariant("browser");
21 21 var main = new TestLayer("main");
22 22 var test = new TestLayer("test");
23 23 var production = new TestRole("production");
24 24 var qa = new TestRole("test");
25 25
26 26 var view = view(
27 27 Set.of(main, test),
28 28 Set.of(production, qa),
29 29 Set.of(browser),
30 30 Set.of(
31 31 new VariantRoleLayer(browser, production, main),
32 32 new VariantRoleLayer(browser, qa, main),
33 33 new VariantRoleLayer(browser, qa, test)));
34 34
35 35 var units = CompileUnitsView.of(view);
36 var browserMain = units.getUnit(browser, main);
37 var browserTest = units.getUnit(browser, test);
36 var browserMain = units.requireUnit(browser, main);
37 var browserTest = units.requireUnit(browser, test);
38 38
39 39 assertEquals(2, units.getUnits().size());
40 40 assertEquals(Set.of(browserMain, browserTest), units.getUnitsForVariant(browser));
41 41 assertEquals(Set.of(production, qa), units.getRoles(browserMain));
42 42 assertEquals(Set.of(qa), units.getRoles(browserTest));
43 43 assertTrue(units.contains(browser, main));
44 44 assertTrue(units.contains(browser, test));
45 45 }
46 46
47 47 @Test
48 48 void rejectsMissingCompileUnitLookup() {
49 49 var browser = new TestVariant("browser");
50 50 var main = new TestLayer("main");
51 51 var missing = new TestLayer("missing");
52 52 var production = new TestRole("production");
53 53
54 54 var view = view(
55 55 Set.of(main),
56 56 Set.of(production),
57 57 Set.of(browser),
58 58 Set.of(new VariantRoleLayer(browser, production, main)));
59 59
60 60 var units = CompileUnitsView.of(view);
61 61
62 var ex = assertThrows(IllegalArgumentException.class, () -> units.getUnit(browser, missing));
62 var ex = assertThrows(IllegalArgumentException.class, () -> units.requireUnit(browser, missing));
63 63 assertTrue(ex.getMessage().contains("Compile unit for variant 'browser' and layer 'missing' not found"));
64 64 }
65 65
66 66 private static VariantsView view(
67 67 Set<Layer> layers,
68 68 Set<Role> roles,
69 69 Set<Variant> variants,
70 70 Set<VariantRoleLayer> entries) {
71 71 try {
72 72 Constructor<VariantsView> ctor = VariantsView.class.getDeclaredConstructor(
73 73 Set.class,
74 74 Set.class,
75 75 Set.class,
76 76 Set.class);
77 77 ctor.setAccessible(true);
78 78 return ctor.newInstance(layers, roles, variants, entries);
79 79 } catch (Exception e) {
80 80 throw new RuntimeException("Unable to create VariantsView fixture", e);
81 81 }
82 82 }
83 83
84 84 private record TestVariant(String value) implements Variant {
85 85 @Override
86 86 public String getName() {
87 87 return value;
88 88 }
89 89 }
90 90
91 91 private record TestLayer(String value) implements Layer {
92 92 @Override
93 93 public String getName() {
94 94 return value;
95 95 }
96 96 }
97 97
98 98 private record TestRole(String value) implements Role {
99 99 @Override
100 100 public String getName() {
101 101 return value;
102 102 }
103 103 }
104 104 }
General Comments 0
You need to be logged in to leave comments. Login now