##// END OF EJS Templates
variants: validate source context identities
cin -
r57:1abf7dba60ee default
parent child
Show More
@@ -1,227 +1,259
1 1 package org.implab.gradle.variants.core;
2 2
3 3 import java.util.ArrayList;
4 4 import java.util.LinkedHashMap;
5 5 import java.util.List;
6 6 import java.util.Map;
7 7 import java.util.Objects;
8 8 import java.util.Optional;
9 9 import java.util.Set;
10 10 import java.util.stream.Collectors;
11 11
12 12 import org.eclipse.jdt.annotation.NonNullByDefault;
13 13 import org.gradle.api.InvalidUserDataException;
14 14
15 15 /**
16 16 * A resolved view of declared variants, roles, layers, and their bindings.
17 17 *
18 18 * Built from {@link VariantDefinition} instances, this class materializes validated
19 19 * {@link VariantRoleLayer} entries and provides lookup APIs grouped by variant,
20 20 * role, or layer.
21 21 *
22 22 * Typical usage is to collect identities and variant definitions through
23 23 * {@link Builder}, then use the resulting view to traverse resolved bindings.
24 24 */
25 25 @NonNullByDefault
26 26 public class VariantsView {
27 27 private final Set<Layer> layers;
28 28 private final Set<Role> roles;
29 29 private final Set<Variant> variants;
30 30 private final Set<VariantRoleLayer> entries;
31 31
32 32 private final Map<Variant, Set<VariantRoleLayer>> entriesByVariant;
33 33 private final Map<Role, Set<VariantRoleLayer>> entriesByRole;
34 34 private final Map<Layer, Set<VariantRoleLayer>> entriesByLayer;
35 35
36 36 private VariantsView(Set<Layer> layers, Set<Role> roles, Set<Variant> variants, Set<VariantRoleLayer> entries) {
37 37 this.layers = layers;
38 38 this.roles = roles;
39 39 this.variants = variants;
40 40 this.entries = entries;
41 41 this.entriesByVariant = entries.stream()
42 42 .collect(Collectors.groupingBy(VariantRoleLayer::variant, Collectors.toSet()));
43 43 this.entriesByRole = entries.stream()
44 44 .collect(Collectors.groupingBy(VariantRoleLayer::role, Collectors.toSet()));
45 45 this.entriesByLayer = entries.stream()
46 46 .collect(Collectors.groupingBy(VariantRoleLayer::layer, Collectors.toSet()));
47 47 }
48 48
49 49 /**
50 50 * Returns all declared layers included in this view.
51 51 */
52 52 public Set<Layer> getLayers() {
53 53 return layers;
54 54 }
55 55
56 56 /**
57 57 * Returns all declared roles included in this view.
58 58 */
59 59 public Set<Role> getRoles() {
60 60 return roles;
61 61 }
62 62
63 /**
64 * Requires the layer to be declared in this view.
65 *
66 * @param layer layer identity
67 * @throws InvalidUserDataException if the layer is not declared
68 */
69 public void assertLayer(Layer layer) {
70 Objects.requireNonNull(layer, "The layer can't be null");
71
72 if (!layers.contains(layer))
73 throw new InvalidUserDataException("The specified layer '" + layer.getName() + "' isn't declared");
74 }
75
76 /**
77 * Requires the role to be declared in this view.
78 *
79 * @param role role identity
80 * @throws InvalidUserDataException if the role is not declared
81 */
63 82 public void assertRole(Role role) {
64 83 Objects.requireNonNull(role, "The role can't be null");
65 84
66 85 if (!roles.contains(role))
67 86 throw new InvalidUserDataException("The specified role '" + role.getName() + "' isn't declared");
68 87 }
69 88
70 89 /**
71 90 * Returns all declared variants included in this view.
72 91 */
73 92 public Set<Variant> getVariants() {
74 93 return variants;
75 94 }
76 95
77 96 /**
97 * Requires the variant to be declared in this view.
98 *
99 * @param variant variant identity
100 * @throws InvalidUserDataException if the variant is not declared
101 */
102 public void assertVariant(Variant variant) {
103 Objects.requireNonNull(variant, "The variant can't be null");
104
105 if (!variants.contains(variant))
106 throw new InvalidUserDataException("The specified variant '" + variant.getName() + "' isn't declared");
107 }
108
109 /**
78 110 * Returns all resolved variant-role-layer bindings.
79 111 */
80 112 public Set<VariantRoleLayer> getEntries() {
81 113 return entries;
82 114 }
83 115
84 116 /**
85 117 * Returns all bindings associated with the specified variant.
86 118 *
87 119 * An empty set is returned when the variant has no bindings in this view.
88 120 */
89 121 public Set<VariantRoleLayer> getEntriesForVariant(Variant variant) {
90 122 return entriesByVariant.getOrDefault(variant, Set.of());
91 123 }
92 124
93 125 /**
94 126 * Returns all bindings associated with the specified layer.
95 127 *
96 128 * An empty set is returned when the layer has no bindings in this view.
97 129 */
98 130 public Set<VariantRoleLayer> getEntriesForLayer(Layer layer) {
99 131 return entriesByLayer.getOrDefault(layer, Set.of());
100 132 }
101 133
102 134 /**
103 135 * Returns all bindings associated with the specified role.
104 136 *
105 137 * An empty set is returned when the role has no bindings in this view.
106 138 */
107 139 public Set<VariantRoleLayer> getEntriesForRole(Role role) {
108 140 return entriesByRole.getOrDefault(role, Set.of());
109 141 }
110 142
111 143 /**
112 144 * A resolved binding between a variant, a role, and a layer.
113 145 *
114 146 * @param variant the resolved variant
115 147 * @param role the resolved role
116 148 * @param layer the resolved layer
117 149 */
118 150 public record VariantRoleLayer(Variant variant, Role role, Layer layer) {
119 151 }
120 152
121 153 /**
122 154 * Creates a builder for assembling a {@link VariantsView}.
123 155 */
124 156 public static Builder builder() {
125 157 return new Builder();
126 158 }
127 159
128 160 /**
129 161 * Collects declared identities and variant definitions, then resolves them
130 162 * into a {@link VariantsView}.
131 163 */
132 164 public static class Builder {
133 165
134 166 private final Map<String, Layer> layers = new LinkedHashMap<>();
135 167 private final Map<String, Role> roles = new LinkedHashMap<>();
136 168 private final Map<String, Variant> variants = new LinkedHashMap<>();
137 169 private final List<VariantDefinition> definitions = new ArrayList<>();
138 170
139 171 private Builder() {
140 172 }
141 173
142 174 /**
143 175 * Adds or replaces a role by its name.
144 176 */
145 177 public Builder addRole(Role role) {
146 178 Objects.requireNonNull(role, "role can't be null");
147 179 roles.put(role.getName(), role);
148 180 return this;
149 181 }
150 182
151 183 /**
152 184 * Adds or replaces a layer by its name.
153 185 */
154 186 public Builder addLayer(Layer layer) {
155 187 Objects.requireNonNull(layer, "layer can't be null");
156 188 layers.put(layer.getName(), layer);
157 189 return this;
158 190 }
159 191
160 192 /**
161 193 * Adds or replaces a variant by its name.
162 194 */
163 195 public Builder addVariant(Variant variant) {
164 196 Objects.requireNonNull(variant, "variant can't be null");
165 197 variants.put(variant.getName(), variant);
166 198 return this;
167 199 }
168 200
169 201 /**
170 202 * Adds a variant definition to be resolved during {@link #build()}.
171 203 */
172 204 public Builder addDefinition(VariantDefinition definition) {
173 205 Objects.requireNonNull(definition, "definition can't be null");
174 206 definitions.add(definition);
175 207 return this;
176 208 }
177 209
178 210 /**
179 211 * Resolves collected identities and definitions into an immutable view.
180 212 *
181 213 * Missing variant, role, or layer declarations referenced by definitions
182 214 * cause {@link InvalidUserDataException}.
183 215 */
184 216 public VariantsView build() {
185 217
186 218 var entries = definitions.stream()
187 219 .flatMap(def -> def.getRoleBindings().get().stream()
188 220 .map(layerRole -> createVariantRoleLayer(
189 221 def.getName(), // variantName
190 222 layerRole.roleName(),
191 223 layerRole.layerName())))
192 224 .collect(Collectors.toSet());
193 225
194 226 return new VariantsView(
195 227 Set.copyOf(layers.values()),
196 228 Set.copyOf(roles.values()),
197 229 Set.copyOf(variants.values()),
198 230 entries);
199 231 }
200 232
201 233 private VariantRoleLayer createVariantRoleLayer(String variantName, String roleName, String layerName) {
202 234 return new VariantRoleLayer(
203 235 resolveVariant(variantName,
204 236 "Variant '" + variantName + "' isn't declared"),
205 237 resolveRole(roleName,
206 238 "Role '" + roleName + "' isn't declared, referenced in '" + variantName + "' variant"),
207 239 resolveLayer(layerName,
208 240 "Layer '" + layerName + "' isn't declared, referenced in '" + variantName
209 241 + "' variant with '" + roleName + "' role"));
210 242 }
211 243
212 244 private Layer resolveLayer(String layerName, String errorMessage) {
213 245 return Optional.ofNullable(layers.get(layerName))
214 246 .orElseThrow(() -> new InvalidUserDataException(errorMessage));
215 247 }
216 248
217 249 private Variant resolveVariant(String variantName, String errorMessage) {
218 250 return Optional.ofNullable(variants.get(variantName))
219 251 .orElseThrow(() -> new InvalidUserDataException(errorMessage));
220 252 }
221 253
222 254 private Role resolveRole(String roleName, String errorMessage) {
223 255 return Optional.ofNullable(roles.get(roleName))
224 256 .orElseThrow(() -> new InvalidUserDataException(errorMessage));
225 257 }
226 258 }
227 259 }
@@ -1,78 +1,79
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 import org.gradle.api.InvalidUserDataException;
11 12 import org.implab.gradle.variants.core.Layer;
12 13 import org.implab.gradle.variants.core.Role;
13 14 import org.implab.gradle.variants.core.Variant;
14 15 import org.implab.gradle.variants.core.VariantsView;
15 16 import org.implab.gradle.variants.core.VariantsView.VariantRoleLayer;
16 17
17 18 @NonNullByDefault
18 19 public final class CompileUnitsView {
19 20
20 21 private final VariantsView variants;
21 22 private final Map<Variant, Set<CompileUnit>> unitsByVariant = new HashMap<>();
22 23
23 24 private CompileUnitsView(VariantsView variants) {
24 25 this.variants = variants;
25 26 }
26 27
27 28 public Set<CompileUnit> getUnits() {
28 29 return variants.getEntries().stream()
29 30 .map(CompileUnit::of)
30 31 .collect(Collectors.toUnmodifiableSet());
31 32 }
32 33
33 34 public Set<CompileUnit> getUnitsForVariant(Variant variant) {
34 35 Objects.requireNonNull(variant, "Variant can't be null");
35 36
36 37 return unitsByVariant.computeIfAbsent(variant, key -> variants
37 38 .getEntriesForVariant(variant).stream()
38 39 .map(CompileUnit::of)
39 40 .collect(Collectors.toUnmodifiableSet()));
40 41 }
41 42
42 43 public Optional<CompileUnit> findUnit(Variant variant, Layer layer) {
43 44 Objects.requireNonNull(variant, "Variant can't be null");
44 45 Objects.requireNonNull(layer, "Layer can't be null");
45 46
46 47 return getUnitsForVariant(variant).stream()
47 48 .filter(u -> u.layer().equals(layer))
48 49 .findAny();
49 50 }
50 51
51 52 public boolean contains(Variant variant, Layer layer) {
52 53 return findUnit(variant, layer).isPresent();
53 54 }
54 55
55 56 /**
56 57 * In which logical roles this compile unit participates.
57 58 */
58 59
59 60 public Set<Role> getRoles(CompileUnit unit) {
60 61 Objects.requireNonNull(unit, "Compile unit can't be null");
61 62 return variants.getEntriesForVariant(unit.variant()).stream()
62 63 .filter(entry -> entry.layer().equals(unit.layer()))
63 64 .map(VariantRoleLayer::role)
64 65 .collect(Collectors.toUnmodifiableSet());
65 66 }
66 67
67 68 public CompileUnit requireUnit(Variant variant, Layer layer) {
68 69 return findUnit(variant, layer)
69 .orElseThrow(() -> new IllegalArgumentException(
70 .orElseThrow(() -> new InvalidUserDataException(
70 71 "Compile unit for variant '" + variant.getName()
71 72 + "' and layer '" + layer.getName() + "' not found"));
72 73 }
73 74
74 75 public static CompileUnitsView of(VariantsView variantsView) {
75 76 Objects.requireNonNull(variantsView, "variantsView can't be null");
76 77 return new CompileUnitsView(variantsView);
77 78 }
78 79 }
@@ -1,83 +1,85
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 import org.gradle.api.InvalidUserDataException;
10 11 import org.implab.gradle.variants.core.Layer;
11 12 import org.implab.gradle.variants.core.Role;
12 13 import org.implab.gradle.variants.core.Variant;
13 14 import org.implab.gradle.variants.core.VariantsView;
14 15
15 16 public final class RoleProjectionsView {
16 17 private final VariantsView variants;
17 18
18 19 private final Map<Variant, Set<RoleProjection>> projectionsByVariant = new HashMap<>();
19 20
20 21 private RoleProjectionsView(VariantsView variants) {
21 22 this.variants = variants;
22 23 }
23 24
24 25 public Set<RoleProjection> getProjections() {
25 26 return variants.getEntries().stream()
26 27 .map(RoleProjection::of)
27 28 .collect(Collectors.toUnmodifiableSet());
28 29 }
29 30
30 31 public Set<RoleProjection> getProjectionsForVariant(Variant variant) {
31 32 Objects.requireNonNull(variant, "Variant can't be null");
32 33 return projectionsByVariant.computeIfAbsent(variant, key -> variants
33 34 .getEntriesForVariant(variant).stream()
34 35 .map(RoleProjection::of)
35 36 .collect(Collectors.toUnmodifiableSet()));
36 37 }
37 38
38 39 public Set<RoleProjection> getProjectionsForRole(Role role) {
39 40 Objects.requireNonNull(role, "Role can't be null");
40 41 return variants.getEntriesForRole(role).stream()
41 42 .map(RoleProjection::of)
42 43 .collect(Collectors.toUnmodifiableSet());
43 44 }
44 45
45 46 public Optional<RoleProjection> findProjection(Variant variant, Role role) {
46 47 Objects.requireNonNull(variant, "Variant can't be null");
47 48 Objects.requireNonNull(role, "Role can't be null");
48 49 return variants.getEntriesForVariant(variant).stream()
49 50 .filter(entry -> entry.role().equals(role))
50 51 .map(RoleProjection::of)
51 52 .findAny();
52 53 }
53 54
54 55 public boolean contains(Variant variant, Role role) {
55 56 return findProjection(variant, role).isPresent();
56 57 }
57 58
58 59 public Set<CompileUnit> getUnits(RoleProjection projection) {
59 60 Objects.requireNonNull(projection, "Role projection can't be null");
60 61 return variants.getEntriesForVariant(projection.variant()).stream()
61 62 .filter(entry -> entry.role().equals(projection.role()))
62 63 .map(CompileUnit::of)
63 64 .collect(Collectors.toUnmodifiableSet());
64 65
65 66 }
66 67
67 68 public RoleProjection requireProjection(Variant variant, Role role) {
68 69 return findProjection(variant, role)
69 .orElseThrow(() -> new IllegalArgumentException(
70 .orElseThrow(() -> new InvalidUserDataException(
70 71 "Role projection for variant '" + variant.getName()
71 72 + "' and role '" + role.getName() + "' not found"));
72 73 }
73 74
74 75 public Set<Layer> getLayers(RoleProjection projection) {
75 76 return getUnits(projection).stream()
76 77 .map(CompileUnit::layer)
77 78 .collect(java.util.stream.Collectors.toUnmodifiableSet());
78 79 }
79 80
80 81 public static RoleProjectionsView of(VariantsView variantsView) {
82 Objects.requireNonNull(variantsView, "variantsView can't be null");
81 83 return new RoleProjectionsView(variantsView);
82 84 }
83 85 }
@@ -1,20 +1,23
1 1 package org.implab.gradle.variants.sources;
2 2
3 3 import org.gradle.api.NamedDomainObjectProvider;
4 4
5 5 /**
6 6 * Materializes symbolic source set names into actual {@link GenericSourceSet}
7 7 * instances.
8 8 *
9 9 * <p>Symbolic names are assigned from the finalized compile-unit model using
10 10 * the selected
11 11 * {@link VariantSourcesExtension#namingPolicy(org.gradle.api.Action)}.
12 12 */
13 13 public interface SourceSetMaterializer {
14 14 /**
15 15 * Returns a lazy provider for the source set corresponding to the compile unit.
16 16 *
17 17 * <p>The provider is stable and cached per compile unit.
18 *
19 * @throws org.gradle.api.InvalidUserDataException if the compile unit is not
20 * part of the finalized variant model
18 21 */
19 22 NamedDomainObjectProvider<GenericSourceSet> getSourceSet(CompileUnit unit);
20 23 }
@@ -1,72 +1,81
1 1 package org.implab.gradle.variants.sources;
2 2
3 3 import org.gradle.api.Action;
4 4 import org.implab.gradle.variants.core.Layer;
5 5 import org.implab.gradle.variants.core.Variant;
6 6 import org.implab.gradle.variants.core.VariantsView;
7 7
8 8 /**
9 9 * Registry of symbolic source set names produced by sources projection.
10 10 *
11 11 * <p>Identity in this registry is the {@link GenericSourceSet} name assigned
12 12 * by the finalized
13 13 * {@link VariantSourcesExtension#namingPolicy(org.gradle.api.Action)}.
14 14 */
15 15 public interface VariantSourcesContext {
16 16
17 17 /**
18 18 * Finalized core model.
19 19 */
20 20 VariantsView getVariants();
21 21
22 22 /**
23 23 * Derived compile-side view.
24 24 */
25 25 CompileUnitsView getCompileUnits();
26 26
27 27 /**
28 28 * Derived role-side view.
29 29 */
30 30 RoleProjectionsView getRoleProjections();
31 31
32 32 /**
33 33 * Lazy source set provider service.
34 34 */
35 35 SourceSetMaterializer getSourceSets();
36 36
37 37 /**
38 38 * Configures all GenericSourceSets produced from the given layer.
39 39 *
40 40 * The action is applied:
41 41 * - to already materialized source sets of this layer
42 42 * - to all future source sets of this layer
43 43 *
44 44 * <p>For future source sets, selector precedence and registration order are
45 45 * preserved by the materializer.
46 46 *
47 47 * <p>For already materialized source sets, behavior is governed by
48 48 * {@link VariantSourcesExtension#lateConfigurationPolicy(org.gradle.api.Action)}.
49 49 * In warn/allow modes the action is applied as a late imperative step and does
50 50 * not retroactively restore selector precedence.
51 *
52 * @throws org.gradle.api.InvalidUserDataException if the layer is not part of
53 * the finalized variant model
51 54 */
52 55 void configureLayer(Layer layer, Action<? super GenericSourceSet> action);
53 56
54 57 /**
55 58 * Configures all GenericSourceSets produced from the given variant.
56 59 *
57 60 * <p>Late application semantics for already materialized source sets are
58 61 * governed by
59 62 * {@link VariantSourcesExtension#lateConfigurationPolicy(org.gradle.api.Action)}.
63 *
64 * @throws org.gradle.api.InvalidUserDataException if the variant is not part
65 * of the finalized variant model
60 66 */
61 67 void configureVariant(Variant variant, Action<? super GenericSourceSet> action);
62 68
63 69 /**
64 70 * Configures the GenericSourceSet produced from the given compile unit.
65 71 *
66 72 * <p>Late application semantics for already materialized source sets are
67 73 * governed by
68 74 * {@link VariantSourcesExtension#lateConfigurationPolicy(org.gradle.api.Action)}.
75 *
76 * @throws org.gradle.api.InvalidUserDataException if the compile unit is not
77 * part of the finalized variant model
69 78 */
70 79 void configureUnit(CompileUnit unit, Action<? super GenericSourceSet> action);
71 80
72 81 }
@@ -1,170 +1,170
1 1 package org.implab.gradle.variants.sources;
2 2
3 3 import org.eclipse.jdt.annotation.NonNullByDefault;
4 4 import org.gradle.api.Action;
5 5 import org.implab.gradle.common.core.lang.Closures;
6 6 import groovy.lang.Closure;
7 7
8 8 @NonNullByDefault
9 9 public interface VariantSourcesExtension {
10 10
11 11 /**
12 12 * Selects how selector rules behave when they target an already materialized
13 13 * {@link GenericSourceSet}.
14 14 *
15 15 * <p>This policy is single-valued:
16 16 * <ul>
17 17 * <li>it must be selected before the first selector rule is registered via
18 18 * {@link #variant(String, Action)}, {@link #layer(String, Action)} or
19 19 * {@link #unit(String, String, Action)};</li>
20 20 * <li>it must be selected before the source context becomes observable via
21 21 * {@link #whenAvailable(Action)};</li>
22 22 * <li>once selected or once source context creation begins, it cannot
23 23 * be changed later;</li>
24 24 * <li>the policy controls both diagnostics and late-application semantics.</li>
25 25 * </ul>
26 26 *
27 27 * <p>If not selected explicitly, the default is
28 28 * {@link LateConfigurationPolicySpec#failOnLateConfiguration()}.
29 29 */
30 30 void lateConfigurationPolicy(Action<? super LateConfigurationPolicySpec> action);
31 31
32 32 default void lateConfigurationPolicy(Closure<?> closure) {
33 33 lateConfigurationPolicy(Closures.action(closure));
34 34 }
35 35
36 36 /**
37 37 * Selects how compile-unit name collisions are handled when the source
38 38 * context is created from the finalized variant model.
39 39 *
40 40 * <p>This policy is single-valued:
41 41 * <ul>
42 * <li>it must be selected before the finalized
42 * <li>it must be selected before the
43 43 * {@link VariantSourcesContext} becomes observable through
44 44 * {@link #whenAvailable(Action)};</li>
45 45 * <li>once the context is being created, the policy is fixed and cannot be
46 46 * changed later;</li>
47 47 * <li>the policy governs validation of compile-unit names produced by the
48 48 * source-set materializer.</li>
49 49 * </ul>
50 50 *
51 51 * <p>If not selected explicitly, the default is
52 52 * {@link NamingPolicySpec#failOnNameCollision()}.
53 53 */
54 54 void namingPolicy(Action<? super NamingPolicySpec> action);
55 55
56 56 default void namingPolicy(Closure<?> closure) {
57 57 namingPolicy(Closures.action(closure));
58 58 }
59 59
60 60 /**
61 61 * Registers a selector rule for all compile units of the given layer.
62 62 *
63 63 * <p>Registering the first selector rule fixes the selected
64 64 * {@link #lateConfigurationPolicy(Action)} for the remaining extension
65 65 * lifecycle.
66 66 */
67 67 void layer(String layerName, Action<? super GenericSourceSet> action);
68 68
69 69 default void layer(String layerName, Closure<?> closure) {
70 70 layer(layerName, Closures.action(closure));
71 71 }
72 72
73 73 /**
74 74 * Registers a selector rule for all compile units of the given variant.
75 75 *
76 76 * <p>Registering the first selector rule fixes the selected
77 77 * {@link #lateConfigurationPolicy(Action)} for the remaining extension
78 78 * lifecycle.
79 79 */
80 80 void variant(String variantName, Action<? super GenericSourceSet> action);
81 81
82 82 default void variant(String variantName, Closure<?> closure) {
83 83 variant(variantName, Closures.action(closure));
84 84 }
85 85
86 86 /**
87 87 * Registers a selector rule for one exact compile unit.
88 88 *
89 89 * <p>Registering the first selector rule fixes the selected
90 90 * {@link #lateConfigurationPolicy(Action)} for the remaining extension
91 91 * lifecycle.
92 92 */
93 93 void unit(String variantName, String layerName, Action<? super GenericSourceSet> action);
94 94
95 95 default void unit(String variantName, String layerName, Closure<?> closure) {
96 96 unit(variantName, layerName, Closures.action(closure));
97 97 }
98 98
99 99 /**
100 100 * Invoked when the variants-derived source context becomes available.
101 101 *
102 102 * Replayable:
103 103 * <ul>
104 104 * <li>if called before variants finalization, action is queued
105 105 * <li>if called after variants finalization, action is invoked immediately
106 106 * </ul>
107 107 *
108 108 * <p>By the time this callback becomes observable, compile-unit naming
109 109 * policy has already been fixed and symbolic source-set names for finalized
110 110 * compile units are determined.
111 111 */
112 112 void whenAvailable(Action<? super VariantSourcesContext> action);
113 113
114 114 default void whenAvailable(Closure<?> closure) {
115 115 whenAvailable(Closures.action(closure));
116 116 }
117 117
118 118
119 119 /**
120 120 * Imperative selector for the late-configuration mode.
121 121 *
122 122 * <p>Exactly one mode is expected to be chosen for the extension lifecycle.
123 123 */
124 124 interface LateConfigurationPolicySpec {
125 125 /**
126 126 * Rejects selector registration if it targets any already materialized
127 127 * source set.
128 128 */
129 129 void failOnLateConfiguration();
130 130
131 131 /**
132 132 * Allows late selector registration, but emits a warning when it targets an
133 133 * already materialized source set.
134 134 *
135 135 * <p>For such targets, selector precedence is not re-established
136 136 * retroactively. The action is applied as a late imperative step, after the
137 137 * state already produced at the materialization moment.
138 138 */
139 139 void warnOnLateConfiguration();
140 140
141 141 /**
142 142 * Allows late selector registration without a warning when it targets an
143 143 * already materialized source set.
144 144 *
145 145 * <p>For such targets, selector precedence is not re-established
146 146 * retroactively. The action is applied as a late imperative step, after the
147 147 * state already produced at the materialization moment.
148 148 */
149 149 void allowLateConfiguration();
150 150 }
151 151
152 152 interface NamingPolicySpec {
153 153 /**
154 154 * Rejects finalized compile-unit models that project the same source-set
155 155 * name for different compile units.
156 156 */
157 157 void failOnNameCollision();
158 158
159 159 /**
160 160 * Resolves name collisions deterministically for the finalized
161 161 * compile-unit model.
162 162 *
163 163 * <p>Conflicting compile units are ordered canonically by
164 164 * {@code (variant.name, layer.name)}. The first unit keeps the base
165 165 * projected name, and each next unit receives a numeric suffix
166 166 * ({@code 2}, {@code 3}, ...).
167 167 */
168 168 void resolveNameCollision();
169 169 }
170 170 }
@@ -1,91 +1,91
1 1 package org.implab.gradle.variants.sources.internal;
2 2
3 3 import java.text.MessageFormat;
4 4 import java.util.Collection;
5 5 import java.util.Comparator;
6 6 import java.util.HashMap;
7 7 import java.util.HashSet;
8 8 import java.util.Map;
9 9 import java.util.Optional;
10 10 import java.util.Set;
11 11 import java.util.function.Function;
12 12 import java.util.stream.Collectors;
13 13
14 14 import org.gradle.api.InvalidUserDataException;
15 15 import org.implab.gradle.common.core.lang.Strings;
16 16 import org.implab.gradle.variants.sources.CompileUnit;
17 17
18 18 public interface CompileUnitNamer {
19 19
20 20 String resolveName(CompileUnit unit);
21 21
22 22 public static Builder builder() {
23 23 return new Builder();
24 24 }
25 25
26 26 static class Builder {
27 27 private final Set<CompileUnit> units = new HashSet<>();
28 28 private NameCollisionPolicy nameCollisionPolicy = NameCollisionPolicy.FAIL;
29 29
30 30 private Builder() {
31 31 }
32 32
33 33 public Builder addUnits(Collection<CompileUnit> other) {
34 34 units.addAll(other);
35 35 return this;
36 36 }
37 37
38 38 public Builder nameCollisionPolicy(NameCollisionPolicy policy) {
39 39 nameCollisionPolicy = policy;
40 40 return this;
41 41 }
42 42
43 43 public CompileUnitNamer build() {
44 44 Map<String, Integer> seen = new HashMap<>();
45 45
46 46 if (nameCollisionPolicy == NameCollisionPolicy.FAIL) {
47 47 var collisions = units.stream()
48 48 .collect(Collectors.groupingBy(this::projectName))
49 49 .entrySet().stream()
50 50 .filter(pair -> pair.getValue().size() > 1)
51 51 .map(pair -> MessageFormat.format(
52 52 "({0}: {1})",
53 53 pair.getKey(),
54 54 pair.getValue().stream()
55 55 .map(Object::toString)
56 56 .collect(Collectors.joining(","))))
57 57 .collect(Collectors.joining(","));
58 58 if (!collisions.isEmpty())
59 59 throw new InvalidUserDataException(
60 60 "The same source set names are produced by different compile units: " + collisions);
61 61 }
62 62
63 63 var unitNames = units.stream()
64 64 .sorted(Comparator
65 65 .comparing((CompileUnit unit) -> unit.variant().getName())
66 66 .thenComparing(unit -> unit.layer().getName()))
67 67 .collect(Collectors.toUnmodifiableMap(Function.identity(), unit -> {
68 68 var baseName = projectName(unit);
69 69
70 70 var c = seen.compute(baseName, (key, count) -> count == null ? 1 : count + 1);
71 71 return c == 1 ? baseName : baseName + String.valueOf(c);
72 72 }));
73 73
74 74 return new CompileUnitNamer() {
75 75
76 76 @Override
77 77 public String resolveName(CompileUnit unit) {
78 78 return Optional.ofNullable(unitNames.get(unit)).orElseThrow(
79 () -> new IllegalArgumentException(MessageFormat.format(
79 () -> new InvalidUserDataException(MessageFormat.format(
80 80 "Compile unit {0} doesn't have an associated name",
81 81 unit)));
82 82 }
83 83
84 84 };
85 85 }
86 86
87 87 private String projectName(CompileUnit unit) {
88 88 return unit.variant().getName() + Strings.capitalize(unit.layer().getName());
89 89 }
90 90 }
91 91 }
@@ -1,36 +1,37
1 1 package org.implab.gradle.variants.sources.internal;
2 2
3 import org.gradle.api.InvalidUserDataException;
3 4 import org.implab.gradle.variants.sources.VariantSourcesExtension.NamingPolicySpec;
4 5
5 6 public class DefaultCompileUnitNamingPolicy implements NamingPolicySpec {
6 7 private NameCollisionPolicy policy = NameCollisionPolicy.FAIL;
7 8 private boolean policyApplied = false;
8 9
9 10 public NameCollisionPolicy policy() {
10 11 finalizePolicy();
11 12 return policy;
12 13 }
13 14
14 15 public void finalizePolicy() {
15 16 policyApplied = true;
16 17 }
17 18
18 19 @Override
19 20 public void failOnNameCollision() {
20 21 assertApplyOnce();
21 22 policy = NameCollisionPolicy.FAIL;
22 23 }
23 24
24 25 @Override
25 26 public void resolveNameCollision() {
26 27 assertApplyOnce();
27 28 policy = NameCollisionPolicy.RESOLVE;
28 29 }
29 30
30 31 private void assertApplyOnce() {
31 32 if (policyApplied)
32 throw new IllegalStateException("Naming policy already applied");
33 throw new InvalidUserDataException("Naming policy already applied");
33 34 policyApplied = true;
34 35
35 36 }
36 37 }
@@ -1,43 +1,44
1 1 package org.implab.gradle.variants.sources.internal;
2 2
3 import org.gradle.api.InvalidUserDataException;
3 4 import org.implab.gradle.variants.sources.VariantSourcesExtension.LateConfigurationPolicySpec;
4 5
5 6 public class DefaultLateConfigurationPolicySpec implements LateConfigurationPolicySpec {
6 7
7 8 private LateConfigurationMode policyMode = LateConfigurationMode.FAIL;
8 9 private boolean policyApplied = false;
9 10
10 11 public LateConfigurationMode mode() {
11 12 finalizePolicy();
12 13 return policyMode;
13 14 }
14 15
15 16 public void finalizePolicy() {
16 17 policyApplied = true;
17 18 }
18 19
19 20 @Override
20 21 public void failOnLateConfiguration() {
21 22 assertApplyOnce();
22 23 policyMode = LateConfigurationMode.FAIL;
23 24 }
24 25
25 26 @Override
26 27 public void warnOnLateConfiguration() {
27 28 assertApplyOnce();
28 29 policyMode = LateConfigurationMode.WARN;
29 30 }
30 31
31 32 @Override
32 33 public void allowLateConfiguration() {
33 34 assertApplyOnce();
34 35 policyMode = LateConfigurationMode.APPLY;
35 36 }
36 37
37 38 private void assertApplyOnce() {
38 39 if (policyApplied)
39 throw new IllegalStateException("Lazy configuration policy already applied");
40 throw new InvalidUserDataException("Lazy configuration policy already applied");
40 41 policyApplied = true;
41 42 }
42 43
43 44 }
@@ -1,92 +1,107
1 1 package org.implab.gradle.variants.sources.internal;
2 2
3 3 import java.util.HashMap;
4 4 import java.util.Map;
5 import java.util.Objects;
5 6 import org.gradle.api.Action;
6 7 import org.gradle.api.NamedDomainObjectProvider;
7 8 import org.implab.gradle.variants.core.Layer;
8 9 import org.implab.gradle.variants.core.Variant;
9 10 import org.implab.gradle.variants.core.VariantsView;
10 11 import org.implab.gradle.variants.sources.CompileUnit;
11 12 import org.implab.gradle.variants.sources.CompileUnitsView;
12 13 import org.implab.gradle.variants.sources.GenericSourceSet;
13 14 import org.implab.gradle.variants.sources.RoleProjectionsView;
14 15 import org.implab.gradle.variants.sources.SourceSetMaterializer;
15 16 import org.implab.gradle.variants.sources.VariantSourcesContext;
16 17
17 18 public class DefaultVariantSourcesContext implements VariantSourcesContext {
18 19 private final VariantsView variantsView;
19 20 private final CompileUnitsView compileUnitsView;
20 21 private final RoleProjectionsView roleProjectionsView;
21 22 private final SourceSetMaterializer sourceSetMaterializer;
22 23 private final SourceSetRegistry sourceSetRegistry;
23 24 private final CompileUnitNamer compileUnitNamer;
24 25 private final SourceSetConfigurationRegistry sourceSetConfigurationRegistry;
25 26
26 27 public DefaultVariantSourcesContext(
27 28 VariantsView variantsView,
28 29 CompileUnitsView compileUnitsView,
29 30 RoleProjectionsView roleProjectionsView,
30 31 CompileUnitNamer compileUnitNamer,
31 32 SourceSetRegistry sourceSetRegistry,
32 33 SourceSetConfigurationRegistry sourceSetConfigurationRegistry) {
33 34 this.variantsView = variantsView;
34 35 this.compileUnitNamer = compileUnitNamer;
35 36 this.compileUnitsView = compileUnitsView;
36 37 this.roleProjectionsView = roleProjectionsView;
37 38 this.sourceSetRegistry = sourceSetRegistry;
38 39 this.sourceSetConfigurationRegistry = sourceSetConfigurationRegistry;
39 40
40 41 sourceSetMaterializer = new LocalSourceSetMaterializer();
41 42 }
42 43
43 44 @Override
44 45 public VariantsView getVariants() {
45 46 return variantsView;
46 47 }
47 48
48 49 @Override
49 50 public CompileUnitsView getCompileUnits() {
50 51 return compileUnitsView;
51 52 }
52 53
53 54 @Override
54 55 public RoleProjectionsView getRoleProjections() {
55 56 return roleProjectionsView;
56 57 }
57 58
58 59 @Override
59 60 public SourceSetMaterializer getSourceSets() {
60 61 return sourceSetMaterializer;
61 62 }
62 63
63 64 @Override
64 65 public void configureLayer(Layer layer, Action<? super GenericSourceSet> action) {
66 variantsView.assertLayer(layer);
67 Objects.requireNonNull(action, "action can't be null");
65 68 sourceSetConfigurationRegistry.addLayerAction(layer, action);
66 69 }
67 70
68 71 @Override
69 72 public void configureVariant(Variant variant, Action<? super GenericSourceSet> action) {
73 variantsView.assertVariant(variant);
74 Objects.requireNonNull(action, "action can't be null");
70 75 sourceSetConfigurationRegistry.addVariantAction(variant, action);
71 76 }
72 77
73 78 @Override
74 79 public void configureUnit(CompileUnit unit, Action<? super GenericSourceSet> action) {
80 assertCompileUnit(unit);
81 Objects.requireNonNull(action, "action can't be null");
75 82 sourceSetConfigurationRegistry.addCompileUnitAction(unit, action);
76 83 }
77 84
85 private void assertCompileUnit(CompileUnit unit) {
86 Objects.requireNonNull(unit, "unit can't be null");
87 variantsView.assertVariant(unit.variant());
88 variantsView.assertLayer(unit.layer());
89 compileUnitsView.requireUnit(unit.variant(), unit.layer());
90 }
91
78 92 class LocalSourceSetMaterializer implements SourceSetMaterializer {
79 93 private final Map<CompileUnit, NamedDomainObjectProvider<GenericSourceSet>> registeredSources = new HashMap<>();
80 94
81 95 @Override
82 96 public NamedDomainObjectProvider<GenericSourceSet> getSourceSet(CompileUnit unit) {
97 assertCompileUnit(unit);
83 98 return registeredSources.computeIfAbsent(unit, k -> {
84 99 var sourcesName = compileUnitNamer.resolveName(unit);
85 100 sourceSetRegistry.whenMaterialized(sourcesName,
86 101 sourceSet -> sourceSetConfigurationRegistry.applyConfiguration(unit, sourceSet));
87 102 return sourceSetRegistry.sourceSets().register(sourcesName);
88 103 });
89 104 }
90 105
91 106 }
92 107 }
@@ -1,94 +1,95
1 1 package org.implab.gradle.variants.sources.internal;
2 2
3 3 import java.text.MessageFormat;
4 4 import java.util.LinkedHashMap;
5 5 import java.util.List;
6 6 import java.util.Map;
7 7 import java.util.function.Supplier;
8 8 import java.util.stream.Collectors;
9 9
10 10 import org.eclipse.jdt.annotation.NonNullByDefault;
11 11 import org.gradle.api.Action;
12 import org.gradle.api.InvalidUserDataException;
12 13 import org.gradle.api.Named;
13 14 import org.gradle.api.logging.Logger;
14 15 import org.gradle.api.logging.Logging;
15 16 import org.implab.gradle.common.core.lang.ReplayableQueue;
16 17 import org.implab.gradle.variants.core.Layer;
17 18 import org.implab.gradle.variants.core.Variant;
18 19 import org.implab.gradle.variants.sources.CompileUnit;
19 20 import org.implab.gradle.variants.sources.GenericSourceSet;
20 21
21 22 @NonNullByDefault
22 23 public class SourceSetConfigurationRegistry {
23 24 private static final Logger logger = Logging.getLogger(SourceSetConfigurationRegistry.class);
24 25
25 26 private final Map<Layer, ReplayableQueue<GenericSourceSet>> sourcesByLayer = new LinkedHashMap<>();
26 27 private final Map<Variant, ReplayableQueue<GenericSourceSet>> sourcesByVariant = new LinkedHashMap<>();
27 28 private final Map<CompileUnit, ReplayableQueue<GenericSourceSet>> sourcesByUnit = new LinkedHashMap<>();
28 29
29 30 private final Supplier<LateConfigurationMode> lateConfigurationMode;
30 31
31 32 public SourceSetConfigurationRegistry(Supplier<LateConfigurationMode> lateConfigurationMode) {
32 33 this.lateConfigurationMode = lateConfigurationMode;
33 34 }
34 35
35 36 public void addLayerAction(Layer layer, Action<? super GenericSourceSet> action) {
36 37 addToActions(
37 38 sourcesByLayer.computeIfAbsent(layer, key -> new ReplayableQueue<>()),
38 39 action,
39 40 MessageFormat.format(
40 41 "Source sets for [layer={0}] layer already materialized",
41 42 layer.getName()));
42 43 }
43 44
44 45 public void addVariantAction(Variant variant, Action<? super GenericSourceSet> action) {
45 46 addToActions(
46 47 sourcesByVariant.computeIfAbsent(variant, key -> new ReplayableQueue<>()),
47 48 action,
48 49 MessageFormat.format(
49 50 "Source sets for [variant={0}] variant already materialized",
50 51 variant.getName()));
51 52
52 53 }
53 54
54 55 public void addCompileUnitAction(CompileUnit unit, Action<? super GenericSourceSet> action) {
55 56 addToActions(
56 57 sourcesByUnit.computeIfAbsent(unit, key -> new ReplayableQueue<>()),
57 58 action,
58 59 MessageFormat.format(
59 60 "Source set for [variant={0}, layer={1}] already materialized",
60 61 unit.variant().getName(),
61 62 unit.layer().getName()));
62 63 }
63 64
64 65 private void addToActions(
65 66 ReplayableQueue<GenericSourceSet> actions,
66 67 Action<? super GenericSourceSet> action,
67 68 String assertMessage) {
68 69 assertLazyConfiguration(actions.values(), assertMessage);
69 70 actions.forEach(action::execute);
70 71 }
71 72
72 73 void assertLazyConfiguration(List<GenericSourceSet> sets, String message) {
73 74 if (sets.size() == 0)
74 75 return;
75 76
76 77 var names = sets.stream().map(Named::getName).collect(Collectors.joining(", "));
77 78
78 79 switch (lateConfigurationMode.get()) {
79 80 case FAIL:
80 throw new IllegalStateException(message + " [" + names + "]");
81 throw new InvalidUserDataException(message + " [" + names + "]");
81 82 case WARN:
82 83 logger.warn(message + "\n\t" + names);
83 84 break;
84 85 default:
85 86 break;
86 87 }
87 88 }
88 89
89 90 public void applyConfiguration(CompileUnit unit, GenericSourceSet sourceSet) {
90 91 sourcesByVariant.computeIfAbsent(unit.variant(), key -> new ReplayableQueue<>()).add(sourceSet);
91 92 sourcesByLayer.computeIfAbsent(unit.layer(), key -> new ReplayableQueue<>()).add(sourceSet);
92 93 sourcesByUnit.computeIfAbsent(unit, key -> new ReplayableQueue<>()).add(sourceSet);
93 94 }
94 95 }
@@ -1,454 +1,554
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.whenAvailable { 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.requireProjection(browser, production)
39 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.whenAvailable { 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.whenAvailable { 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 116 def browserMain = ctx.sourceSets.getSourceSet(ctx.compileUnits.requireUnit(browser, mainLayer)).get()
117 117 def browserTest = ctx.sourceSets.getSourceSet(ctx.compileUnits.requireUnit(browser, testLayer)).get()
118 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.whenAvailable { ctx ->
150 150 def browser = ctx.variants.variants.find { it.name == 'browser' }
151 151 def mainLayer = ctx.variants.layers.find { it.name == 'main' }
152 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.whenAvailable { ctx ->
186 186 def browser = ctx.variants.variants.find { it.name == 'browser' }
187 187 def mainLayer = ctx.variants.layers.find { it.name == 'main' }
188 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.whenAvailable { ctx ->
225 225 def browser = ctx.variants.variants.find { it.name == 'browser' }
226 226 def mainLayer = ctx.variants.layers.find { it.name == 'main' }
227 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.whenAvailable { 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 324 def later = ctx.compileUnits.requireUnit(fooVariant, bar)
325 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' isn't declared", "help");
360 360 }
361 361
362 362 @Test
363 void failsOnUnknownVariantContextSelectorTarget() throws Exception {
364 writeSettings("variant-sources-context-missing-variant");
365 writeBuildFile("""
366 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
367
368 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
369 variantsExt.layers.create('main')
370 variantsExt.roles.create('production')
371 variantsExt.variant('browser') {
372 role('production') { layers('main') }
373 }
374
375 variantSources.whenAvailable { ctx ->
376 def missing = objects.named(org.implab.gradle.variants.core.Variant, 'missing')
377 ctx.configureVariant(missing) {
378 declareOutputs('js')
379 }
380 }
381 """);
382
383 assertBuildFails("The specified variant 'missing' isn't declared", "help");
384 }
385
386 @Test
363 387 void failsOnUnknownLayerSelectorTarget() throws Exception {
364 388 writeSettings("variant-sources-missing-layer");
365 389 writeBuildFile("""
366 390 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
367 391
368 392 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
369 393 variantsExt.layers.create('main')
370 394 variantsExt.roles.create('production')
371 395 variantsExt.variant('browser') {
372 396 role('production') { layers('main') }
373 397 }
374 398
375 399 variantSources {
376 400 layer('missing') {
377 401 declareOutputs('js')
378 402 }
379 403 }
380 404 """);
381 405
382 406 assertBuildFails("Layer 'missing' isn't declared", "help");
383 407 }
384 408
385 409 @Test
410 void failsOnUnknownLayerContextSelectorTarget() throws Exception {
411 writeSettings("variant-sources-context-missing-layer");
412 writeBuildFile("""
413 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
414
415 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
416 variantsExt.layers.create('main')
417 variantsExt.roles.create('production')
418 variantsExt.variant('browser') {
419 role('production') { layers('main') }
420 }
421
422 variantSources.whenAvailable { ctx ->
423 def missing = objects.named(org.implab.gradle.variants.core.Layer, 'missing')
424 ctx.configureLayer(missing) {
425 declareOutputs('js')
426 }
427 }
428 """);
429
430 assertBuildFails("The specified layer 'missing' isn't declared", "help");
431 }
432
433 @Test
386 434 void failsOnUndeclaredCompileUnitSelectorTarget() throws Exception {
387 435 writeSettings("variant-sources-missing-unit");
388 436 writeBuildFile("""
389 437 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
390 438
391 439 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
392 440 variantsExt.layers.create('main')
393 441 variantsExt.layers.create('test')
394 442 variantsExt.roles.create('production')
395 443 variantsExt.variant('browser') {
396 444 role('production') { layers('main') }
397 445 }
398 446
399 447 variantSources {
400 448 unit('browser', 'test') {
401 449 declareOutputs('js')
402 450 }
403 451 }
404 452 """);
405 453
406 454 assertBuildFails("The CompileUnit isn't declared for variant 'browser', layer 'test'", "help");
407 455 }
408 456
409 457 @Test
458 void failsOnUndeclaredCompileUnitContextSelectorTarget() throws Exception {
459 writeSettings("variant-sources-context-missing-unit");
460 writeBuildFile("""
461 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
462
463 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
464 variantsExt.layers.create('main')
465 variantsExt.layers.create('test')
466 variantsExt.roles.create('production')
467 variantsExt.variant('browser') {
468 role('production') { layers('main') }
469 }
470
471 variantSources.whenAvailable { ctx ->
472 def browser = ctx.variants.variants.find { it.name == 'browser' }
473 def testLayer = ctx.variants.layers.find { it.name == 'test' }
474 def unit = new org.implab.gradle.variants.sources.CompileUnit(browser, testLayer)
475 ctx.configureUnit(unit) {
476 declareOutputs('js')
477 }
478 }
479 """);
480
481 assertBuildFails("Compile unit for variant 'browser' and layer 'test' not found", "help");
482 }
483
484 @Test
485 void failsOnUndeclaredCompileUnitMaterializerTarget() throws Exception {
486 writeSettings("variant-sources-materializer-missing-unit");
487 writeBuildFile("""
488 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
489
490 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
491 variantsExt.layers.create('main')
492 variantsExt.layers.create('test')
493 variantsExt.roles.create('production')
494 variantsExt.variant('browser') {
495 role('production') { layers('main') }
496 }
497
498 variantSources.whenAvailable { ctx ->
499 def browser = ctx.variants.variants.find { it.name == 'browser' }
500 def testLayer = ctx.variants.layers.find { it.name == 'test' }
501 def unit = new org.implab.gradle.variants.sources.CompileUnit(browser, testLayer)
502 ctx.sourceSets.getSourceSet(unit)
503 }
504 """);
505
506 assertBuildFails("Compile unit for variant 'browser' and layer 'test' not found", "help");
507 }
508
509 @Test
410 510 void rejectsChangingNamingPolicyAfterContextBecomesObservable() throws Exception {
411 511 writeSettings("variant-sources-name-policy-fixed");
412 512 writeBuildFile("""
413 513 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
414 514
415 515 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
416 516 variantsExt.layers.create('main')
417 517 variantsExt.roles.create('production')
418 518 variantsExt.variant('browser') {
419 519 role('production') { layers('main') }
420 520 }
421 521
422 522 variantSources.whenAvailable {
423 523 variantSources.namingPolicy {
424 524 resolveNameCollision()
425 525 }
426 526 }
427 527 """);
428 528
429 529 assertBuildFails("Naming policy already applied", "help");
430 530 }
431 531
432 532 @Test
433 533 void rejectsChangingLateConfigurationPolicyAfterContextBecomesObservable() throws Exception {
434 534 writeSettings("variant-sources-late-policy-context-fixed");
435 535 writeBuildFile("""
436 536 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
437 537
438 538 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
439 539 variantsExt.layers.create('main')
440 540 variantsExt.roles.create('production')
441 541 variantsExt.variant('browser') {
442 542 role('production') { layers('main') }
443 543 }
444 544
445 545 variantSources.whenAvailable {
446 546 variantSources.lateConfigurationPolicy {
447 547 allowLateConfiguration()
448 548 }
449 549 }
450 550 """);
451 551
452 552 assertBuildFails("Lazy configuration policy already applied", "help");
453 553 }
454 554 }
@@ -1,104 +1,105
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 import org.gradle.api.InvalidUserDataException;
10 11 import org.implab.gradle.variants.core.Layer;
11 12 import org.implab.gradle.variants.core.Role;
12 13 import org.implab.gradle.variants.core.Variant;
13 14 import org.implab.gradle.variants.core.VariantsView;
14 15 import org.implab.gradle.variants.core.VariantsView.VariantRoleLayer;
15 16 import org.junit.jupiter.api.Test;
16 17
17 18 class CompileUnitsViewTest {
18 19 @Test
19 20 void deduplicatesCompileUnitsAcrossRolesAndExposesParticipatingRoles() {
20 21 var browser = new TestVariant("browser");
21 22 var main = new TestLayer("main");
22 23 var test = new TestLayer("test");
23 24 var production = new TestRole("production");
24 25 var qa = new TestRole("test");
25 26
26 27 var view = view(
27 28 Set.of(main, test),
28 29 Set.of(production, qa),
29 30 Set.of(browser),
30 31 Set.of(
31 32 new VariantRoleLayer(browser, production, main),
32 33 new VariantRoleLayer(browser, qa, main),
33 34 new VariantRoleLayer(browser, qa, test)));
34 35
35 36 var units = CompileUnitsView.of(view);
36 37 var browserMain = units.requireUnit(browser, main);
37 38 var browserTest = units.requireUnit(browser, test);
38 39
39 40 assertEquals(2, units.getUnits().size());
40 41 assertEquals(Set.of(browserMain, browserTest), units.getUnitsForVariant(browser));
41 42 assertEquals(Set.of(production, qa), units.getRoles(browserMain));
42 43 assertEquals(Set.of(qa), units.getRoles(browserTest));
43 44 assertTrue(units.contains(browser, main));
44 45 assertTrue(units.contains(browser, test));
45 46 }
46 47
47 48 @Test
48 49 void rejectsMissingCompileUnitLookup() {
49 50 var browser = new TestVariant("browser");
50 51 var main = new TestLayer("main");
51 52 var missing = new TestLayer("missing");
52 53 var production = new TestRole("production");
53 54
54 55 var view = view(
55 56 Set.of(main),
56 57 Set.of(production),
57 58 Set.of(browser),
58 59 Set.of(new VariantRoleLayer(browser, production, main)));
59 60
60 61 var units = CompileUnitsView.of(view);
61 62
62 var ex = assertThrows(IllegalArgumentException.class, () -> units.requireUnit(browser, missing));
63 var ex = assertThrows(InvalidUserDataException.class, () -> units.requireUnit(browser, missing));
63 64 assertTrue(ex.getMessage().contains("Compile unit for variant 'browser' and layer 'missing' not found"));
64 65 }
65 66
66 67 private static VariantsView view(
67 68 Set<Layer> layers,
68 69 Set<Role> roles,
69 70 Set<Variant> variants,
70 71 Set<VariantRoleLayer> entries) {
71 72 try {
72 73 Constructor<VariantsView> ctor = VariantsView.class.getDeclaredConstructor(
73 74 Set.class,
74 75 Set.class,
75 76 Set.class,
76 77 Set.class);
77 78 ctor.setAccessible(true);
78 79 return ctor.newInstance(layers, roles, variants, entries);
79 80 } catch (Exception e) {
80 81 throw new RuntimeException("Unable to create VariantsView fixture", e);
81 82 }
82 83 }
83 84
84 85 private record TestVariant(String value) implements Variant {
85 86 @Override
86 87 public String getName() {
87 88 return value;
88 89 }
89 90 }
90 91
91 92 private record TestLayer(String value) implements Layer {
92 93 @Override
93 94 public String getName() {
94 95 return value;
95 96 }
96 97 }
97 98
98 99 private record TestRole(String value) implements Role {
99 100 @Override
100 101 public String getName() {
101 102 return value;
102 103 }
103 104 }
104 105 }
@@ -1,105 +1,106
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 import org.gradle.api.InvalidUserDataException;
10 11 import org.implab.gradle.variants.core.Layer;
11 12 import org.implab.gradle.variants.core.Role;
12 13 import org.implab.gradle.variants.core.Variant;
13 14 import org.implab.gradle.variants.core.VariantsView;
14 15 import org.implab.gradle.variants.core.VariantsView.VariantRoleLayer;
15 16 import org.junit.jupiter.api.Test;
16 17
17 18 class RoleProjectionsViewTest {
18 19 @Test
19 20 void exposesRoleProjectionsAndTheirCompileUnits() {
20 21 var browser = new TestVariant("browser");
21 22 var main = new TestLayer("main");
22 23 var test = new TestLayer("test");
23 24 var production = new TestRole("production");
24 25 var qa = new TestRole("test");
25 26
26 27 var view = view(
27 28 Set.of(main, test),
28 29 Set.of(production, qa),
29 30 Set.of(browser),
30 31 Set.of(
31 32 new VariantRoleLayer(browser, production, main),
32 33 new VariantRoleLayer(browser, qa, main),
33 34 new VariantRoleLayer(browser, qa, test)));
34 35
35 36 var projections = RoleProjectionsView.of(view);
36 37 var productionProjection = projections.requireProjection(browser, production);
37 38 var qaProjection = projections.requireProjection(browser, qa);
38 39
39 40 assertEquals(Set.of(productionProjection, qaProjection), projections.getProjections());
40 41 assertEquals(Set.of(productionProjection, qaProjection), projections.getProjectionsForVariant(browser));
41 42 assertEquals(Set.of(qaProjection), projections.getProjectionsForRole(qa));
42 43 assertEquals(Set.of(new CompileUnit(browser, main)), projections.getUnits(productionProjection));
43 44 assertEquals(Set.of(new CompileUnit(browser, main), new CompileUnit(browser, test)), projections.getUnits(qaProjection));
44 45 assertEquals(Set.of(main, test), projections.getLayers(qaProjection));
45 46 assertTrue(projections.contains(browser, production));
46 47 }
47 48
48 49 @Test
49 50 void rejectsMissingProjectionLookup() {
50 51 var browser = new TestVariant("browser");
51 52 var node = new TestVariant("node");
52 53 var main = new TestLayer("main");
53 54 var production = new TestRole("production");
54 55
55 56 var view = view(
56 57 Set.of(main),
57 58 Set.of(production),
58 59 Set.of(browser, node),
59 60 Set.of(new VariantRoleLayer(browser, production, main)));
60 61
61 62 var projections = RoleProjectionsView.of(view);
62 63
63 var ex = assertThrows(IllegalArgumentException.class, () -> projections.requireProjection(node, production));
64 var ex = assertThrows(InvalidUserDataException.class, () -> projections.requireProjection(node, production));
64 65 assertTrue(ex.getMessage().contains("Role projection for variant 'node' and role 'production' not found"));
65 66 }
66 67
67 68 private static VariantsView view(
68 69 Set<Layer> layers,
69 70 Set<Role> roles,
70 71 Set<Variant> variants,
71 72 Set<VariantRoleLayer> entries) {
72 73 try {
73 74 Constructor<VariantsView> ctor = VariantsView.class.getDeclaredConstructor(
74 75 Set.class,
75 76 Set.class,
76 77 Set.class,
77 78 Set.class);
78 79 ctor.setAccessible(true);
79 80 return ctor.newInstance(layers, roles, variants, entries);
80 81 } catch (Exception e) {
81 82 throw new RuntimeException("Unable to create VariantsView fixture", e);
82 83 }
83 84 }
84 85
85 86 private record TestVariant(String value) implements Variant {
86 87 @Override
87 88 public String getName() {
88 89 return value;
89 90 }
90 91 }
91 92
92 93 private record TestLayer(String value) implements Layer {
93 94 @Override
94 95 public String getName() {
95 96 return value;
96 97 }
97 98 }
98 99
99 100 private record TestRole(String value) implements Role {
100 101 @Override
101 102 public String getName() {
102 103 return value;
103 104 }
104 105 }
105 106 }
@@ -1,90 +1,90
1 1 package org.implab.gradle.variants.sources.internal;
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.util.List;
8 8
9 9 import org.gradle.api.InvalidUserDataException;
10 10 import org.implab.gradle.variants.core.Layer;
11 11 import org.implab.gradle.variants.core.Variant;
12 12 import org.implab.gradle.variants.sources.CompileUnit;
13 13 import org.junit.jupiter.api.Test;
14 14
15 15 class CompileUnitNamerTest {
16 16 @Test
17 17 void resolvesProjectedNameForUniqueCompileUnit() {
18 18 var unit = unit("browser", "main");
19 19
20 20 var namer = CompileUnitNamer.builder()
21 21 .addUnits(List.of(unit))
22 22 .build();
23 23
24 24 assertEquals("browserMain", namer.resolveName(unit));
25 25 }
26 26
27 27 @Test
28 28 void failsOnProjectedNameCollisionInFailMode() {
29 29 var left = unit("foo", "variantBar");
30 30 var right = unit("fooVariant", "bar");
31 31
32 32 var ex = assertThrows(
33 33 InvalidUserDataException.class,
34 34 () -> CompileUnitNamer.builder()
35 35 .addUnits(List.of(left, right))
36 36 .nameCollisionPolicy(NameCollisionPolicy.FAIL)
37 37 .build());
38 38
39 39 assertTrue(ex.getMessage().contains("The same source set names are produced by different compile units"));
40 40 assertTrue(ex.getMessage().contains("fooVariantBar"));
41 41 }
42 42
43 43 @Test
44 44 void resolvesProjectedNameCollisionDeterministicallyInCanonicalOrder() {
45 45 var earlier = unit("foo", "variantBar");
46 46 var later = unit("fooVariant", "bar");
47 47
48 48 var namer = CompileUnitNamer.builder()
49 49 .addUnits(List.of(later, earlier))
50 50 .nameCollisionPolicy(NameCollisionPolicy.RESOLVE)
51 51 .build();
52 52
53 53 assertEquals("fooVariantBar", namer.resolveName(earlier));
54 54 assertEquals("fooVariantBar2", namer.resolveName(later));
55 55 }
56 56
57 57 @Test
58 58 void rejectsUnknownCompileUnitLookup() {
59 59 var known = unit("browser", "main");
60 60 var unknown = unit("browser", "test");
61 61
62 62 var namer = CompileUnitNamer.builder()
63 63 .addUnits(List.of(known))
64 64 .build();
65 65
66 var ex = assertThrows(IllegalArgumentException.class, () -> namer.resolveName(unknown));
66 var ex = assertThrows(InvalidUserDataException.class, () -> namer.resolveName(unknown));
67 67 assertTrue(ex.getMessage().contains("Compile unit"));
68 68 assertTrue(ex.getMessage().contains("associated name"));
69 69 }
70 70
71 71 private static CompileUnit unit(String variantName, String layerName) {
72 72 return new CompileUnit(
73 73 new TestVariant(variantName),
74 74 new TestLayer(layerName));
75 75 }
76 76
77 77 private record TestVariant(String value) implements Variant {
78 78 @Override
79 79 public String getName() {
80 80 return value;
81 81 }
82 82 }
83 83
84 84 private record TestLayer(String value) implements Layer {
85 85 @Override
86 86 public String getName() {
87 87 return value;
88 88 }
89 89 }
90 90 }
@@ -1,42 +1,43
1 1 package org.implab.gradle.variants.sources.internal;
2 2
3 3 import static org.junit.jupiter.api.Assertions.assertEquals;
4 4 import static org.junit.jupiter.api.Assertions.assertThrows;
5 5
6 import org.gradle.api.InvalidUserDataException;
6 7 import org.junit.jupiter.api.Test;
7 8
8 9 class DefaultCompileUnitNamingPolicyTest {
9 10 @Test
10 11 void usesFailPolicyByDefault() {
11 12 var policy = new DefaultCompileUnitNamingPolicy();
12 13
13 14 assertEquals(NameCollisionPolicy.FAIL, policy.policy());
14 15 }
15 16
16 17 @Test
17 18 void switchesToResolvePolicyWhenSelected() {
18 19 var policy = new DefaultCompileUnitNamingPolicy();
19 20
20 21 policy.resolveNameCollision();
21 22
22 23 assertEquals(NameCollisionPolicy.RESOLVE, policy.policy());
23 24 }
24 25
25 26 @Test
26 27 void rejectsChangingPolicyAfterItWasSelected() {
27 28 var policy = new DefaultCompileUnitNamingPolicy();
28 29
29 30 policy.resolveNameCollision();
30 31
31 assertThrows(IllegalStateException.class, policy::failOnNameCollision);
32 assertThrows(InvalidUserDataException.class, policy::failOnNameCollision);
32 33 }
33 34
34 35 @Test
35 36 void rejectsChangingPolicyAfterItWasFinalized() {
36 37 var policy = new DefaultCompileUnitNamingPolicy();
37 38
38 39 policy.finalizePolicy();
39 40
40 assertThrows(IllegalStateException.class, policy::resolveNameCollision);
41 assertThrows(InvalidUserDataException.class, policy::resolveNameCollision);
41 42 }
42 43 }
@@ -1,51 +1,52
1 1 package org.implab.gradle.variants.sources.internal;
2 2
3 3 import static org.junit.jupiter.api.Assertions.assertEquals;
4 4 import static org.junit.jupiter.api.Assertions.assertThrows;
5 5
6 import org.gradle.api.InvalidUserDataException;
6 7 import org.junit.jupiter.api.Test;
7 8
8 9 class DefaultLateConfigurationPolicySpecTest {
9 10 @Test
10 11 void usesFailModeByDefault() {
11 12 var policy = new DefaultLateConfigurationPolicySpec();
12 13
13 14 assertEquals(LateConfigurationMode.FAIL, policy.mode());
14 15 }
15 16
16 17 @Test
17 18 void switchesToWarnModeWhenSelected() {
18 19 var policy = new DefaultLateConfigurationPolicySpec();
19 20
20 21 policy.warnOnLateConfiguration();
21 22
22 23 assertEquals(LateConfigurationMode.WARN, policy.mode());
23 24 }
24 25
25 26 @Test
26 27 void switchesToApplyModeWhenSelected() {
27 28 var policy = new DefaultLateConfigurationPolicySpec();
28 29
29 30 policy.allowLateConfiguration();
30 31
31 32 assertEquals(LateConfigurationMode.APPLY, policy.mode());
32 33 }
33 34
34 35 @Test
35 36 void rejectsChangingPolicyAfterItWasSelected() {
36 37 var policy = new DefaultLateConfigurationPolicySpec();
37 38
38 39 policy.warnOnLateConfiguration();
39 40
40 assertThrows(IllegalStateException.class, policy::failOnLateConfiguration);
41 assertThrows(InvalidUserDataException.class, policy::failOnLateConfiguration);
41 42 }
42 43
43 44 @Test
44 45 void rejectsChangingPolicyAfterItWasFinalized() {
45 46 var policy = new DefaultLateConfigurationPolicySpec();
46 47
47 48 policy.finalizePolicy();
48 49
49 assertThrows(IllegalStateException.class, policy::allowLateConfiguration);
50 assertThrows(InvalidUserDataException.class, policy::allowLateConfiguration);
50 51 }
51 52 }
General Comments 0
You need to be logged in to leave comments. Login now