##// END OF EJS Templates
Add compile-unit naming policy and late-configuration enforcement to VariantSourcesPlugin
cin -
r43:3285592a0ee9 default
parent child
Show More
@@ -0,0 +1,91
1 package org.implab.gradle.variants.sources.internal;
2
3 import java.text.MessageFormat;
4 import java.util.Collection;
5 import java.util.Comparator;
6 import java.util.HashMap;
7 import java.util.HashSet;
8 import java.util.Map;
9 import java.util.Optional;
10 import java.util.Set;
11 import java.util.function.Function;
12 import java.util.stream.Collectors;
13
14 import org.gradle.api.InvalidUserDataException;
15 import org.implab.gradle.common.core.lang.Strings;
16 import org.implab.gradle.variants.sources.CompileUnit;
17
18 public interface CompileUnitNamer {
19
20 String resolveName(CompileUnit unit);
21
22 public static Builder builder() {
23 return new Builder();
24 }
25
26 static class Builder {
27 private final Set<CompileUnit> units = new HashSet<>();
28 private NameCollisionPolicy nameCollisionPolicy = NameCollisionPolicy.FAIL;
29
30 private Builder() {
31 }
32
33 public Builder addUnits(Collection<CompileUnit> other) {
34 units.addAll(other);
35 return this;
36 }
37
38 public Builder nameCollisionPolicy(NameCollisionPolicy policy) {
39 nameCollisionPolicy = policy;
40 return this;
41 }
42
43 public CompileUnitNamer build() {
44 Map<String, Integer> seen = new HashMap<>();
45
46 if (nameCollisionPolicy == NameCollisionPolicy.FAIL) {
47 var collisions = units.stream()
48 .collect(Collectors.groupingBy(this::projectName))
49 .entrySet().stream()
50 .filter(pair -> pair.getValue().size() > 1)
51 .map(pair -> MessageFormat.format(
52 "({0}: {1})",
53 pair.getKey(),
54 pair.getValue().stream()
55 .map(Object::toString)
56 .collect(Collectors.joining(","))))
57 .collect(Collectors.joining(","));
58 if (!collisions.isEmpty())
59 throw new InvalidUserDataException(
60 "The same source set names are produced by different compile units: " + collisions);
61 }
62
63 var unitNames = units.stream()
64 .sorted(Comparator
65 .comparing((CompileUnit unit) -> unit.variant().getName())
66 .thenComparing(unit -> unit.layer().getName()))
67 .collect(Collectors.toUnmodifiableMap(Function.identity(), unit -> {
68 var baseName = projectName(unit);
69
70 var c = seen.compute(baseName, (key, count) -> count == null ? 1 : count + 1);
71 return c == 1 ? baseName : baseName + String.valueOf(c);
72 }));
73
74 return new CompileUnitNamer() {
75
76 @Override
77 public String resolveName(CompileUnit unit) {
78 return Optional.ofNullable(unitNames.get(unit)).orElseThrow(
79 () -> new IllegalArgumentException(MessageFormat.format(
80 "Compile unit {0} doesn't have an associated name",
81 unit)));
82 }
83
84 };
85 }
86
87 private String projectName(CompileUnit unit) {
88 return unit.variant().getName() + Strings.capitalize(unit.layer().getName());
89 }
90 }
91 }
@@ -0,0 +1,36
1 package org.implab.gradle.variants.sources.internal;
2
3 import org.implab.gradle.variants.sources.VariantSourcesExtension.NamingPolicySpec;
4
5 public class DefaultCompileUnitNamingPolicy implements NamingPolicySpec {
6 private NameCollisionPolicy policy = NameCollisionPolicy.FAIL;
7 private boolean policyApplied = false;
8
9 public NameCollisionPolicy policy() {
10 finalizePolicy();
11 return policy;
12 }
13
14 public void finalizePolicy() {
15 policyApplied = true;
16 }
17
18 @Override
19 public void failOnNameCollision() {
20 assertApplyOnce();
21 policy = NameCollisionPolicy.FAIL;
22 }
23
24 @Override
25 public void resolveNameCollision() {
26 assertApplyOnce();
27 policy = NameCollisionPolicy.RESOLVE;
28 }
29
30 private void assertApplyOnce() {
31 if (policyApplied)
32 throw new IllegalStateException("Naming policy already applied");
33 policyApplied = true;
34
35 }
36 }
@@ -0,0 +1,42
1 package org.implab.gradle.variants.sources.internal;
2
3 import org.implab.gradle.variants.sources.VariantSourcesExtension.LateConfigurationPolicySpec;
4
5 public class DefaultLateConfigurationPolicySpec implements LateConfigurationPolicySpec {
6
7 private LateConfigurationMode policyMode = LateConfigurationMode.FAIL;
8 private boolean policyApplied = false;
9
10 public LateConfigurationMode mode() {
11 return policyMode;
12 }
13
14 public void finalizePolicy() {
15 policyApplied = true;
16 }
17
18 @Override
19 public void failOnLateConfiguration() {
20 assertApplyOnce();
21 policyMode = LateConfigurationMode.FAIL;
22 }
23
24 @Override
25 public void warnOnLateConfiguration() {
26 assertApplyOnce();
27 policyMode = LateConfigurationMode.WARN;
28 }
29
30 @Override
31 public void allowLateConfiguration() {
32 assertApplyOnce();
33 policyMode = LateConfigurationMode.APPLY;
34 }
35
36 private void assertApplyOnce() {
37 if (policyApplied)
38 throw new IllegalStateException("Lazy configuration policy already applied");
39 policyApplied = true;
40 }
41
42 }
@@ -0,0 +1,92
1 package org.implab.gradle.variants.sources.internal;
2
3 import java.util.HashMap;
4 import java.util.Map;
5 import org.gradle.api.Action;
6 import org.gradle.api.NamedDomainObjectProvider;
7 import org.implab.gradle.common.sources.GenericSourceSet;
8 import org.implab.gradle.variants.core.Layer;
9 import org.implab.gradle.variants.core.Variant;
10 import org.implab.gradle.variants.core.VariantsView;
11 import org.implab.gradle.variants.sources.CompileUnit;
12 import org.implab.gradle.variants.sources.CompileUnitsView;
13 import org.implab.gradle.variants.sources.RoleProjectionsView;
14 import org.implab.gradle.variants.sources.SourceSetMaterializer;
15 import org.implab.gradle.variants.sources.VariantSourcesContext;
16
17 public class DefaultVariantSourcesContext implements VariantSourcesContext {
18 private final VariantsView variantsView;
19 private final CompileUnitsView compileUnitsView;
20 private final RoleProjectionsView roleProjectionsView;
21 private final SourceSetMaterializer sourceSetMaterializer;
22 private final SourceSetRegistry sourceSetRegistry;
23 private final CompileUnitNamer compileUnitNamer;
24 private final SourceSetConfigurationRegistry sourceSetConfigurationRegistry;
25
26 public DefaultVariantSourcesContext(
27 VariantsView variantsView,
28 CompileUnitsView compileUnitsView,
29 RoleProjectionsView roleProjectionsView,
30 CompileUnitNamer compileUnitNamer,
31 SourceSetRegistry sourceSetRegistry,
32 SourceSetConfigurationRegistry sourceSetConfigurationRegistry) {
33 this.variantsView = variantsView;
34 this.compileUnitNamer = compileUnitNamer;
35 this.compileUnitsView = compileUnitsView;
36 this.roleProjectionsView = roleProjectionsView;
37 this.sourceSetRegistry = sourceSetRegistry;
38 this.sourceSetConfigurationRegistry = sourceSetConfigurationRegistry;
39
40 sourceSetMaterializer = new LocalSourceSetMaterializer();
41 }
42
43 @Override
44 public VariantsView getVariants() {
45 return variantsView;
46 }
47
48 @Override
49 public CompileUnitsView getCompileUnits() {
50 return compileUnitsView;
51 }
52
53 @Override
54 public RoleProjectionsView getRoleProjections() {
55 return roleProjectionsView;
56 }
57
58 @Override
59 public SourceSetMaterializer getSourceSets() {
60 return sourceSetMaterializer;
61 }
62
63 @Override
64 public void configureLayer(Layer layer, Action<? super GenericSourceSet> action) {
65 sourceSetConfigurationRegistry.addLayerAction(layer, action);
66 }
67
68 @Override
69 public void configureVariant(Variant variant, Action<? super GenericSourceSet> action) {
70 sourceSetConfigurationRegistry.addVariantAction(variant, action);
71 }
72
73 @Override
74 public void configureUnit(CompileUnit unit, Action<? super GenericSourceSet> action) {
75 sourceSetConfigurationRegistry.addCompileUnitAction(unit, action);
76 }
77
78 class LocalSourceSetMaterializer implements SourceSetMaterializer {
79 private final Map<CompileUnit, NamedDomainObjectProvider<GenericSourceSet>> registeredSources = new HashMap<>();
80
81 @Override
82 public NamedDomainObjectProvider<GenericSourceSet> getSourceSet(CompileUnit unit) {
83 return registeredSources.computeIfAbsent(unit, k -> {
84 var sourcesName = compileUnitNamer.resolveName(unit);
85 sourceSetRegistry.whenMaterialized(sourcesName,
86 sourceSet -> sourceSetConfigurationRegistry.applyConfiguration(unit, sourceSet));
87 return sourceSetRegistry.sourceSets().register(sourcesName);
88 });
89 }
90
91 }
92 }
@@ -0,0 +1,7
1 package org.implab.gradle.variants.sources.internal;
2
3 public enum LateConfigurationMode {
4 FAIL,
5 WARN,
6 APPLY
7 }
@@ -0,0 +1,6
1 package org.implab.gradle.variants.sources.internal;
2
3 public enum NameCollisionPolicy {
4 FAIL,
5 RESOLVE
6 }
@@ -0,0 +1,46
1 package org.implab.gradle.variants.sources.internal;
2
3 import java.util.HashMap;
4 import java.util.Map;
5 import java.util.function.Consumer;
6
7 import org.gradle.api.NamedDomainObjectContainer;
8 import org.gradle.api.model.ObjectFactory;
9 import org.implab.gradle.common.core.lang.Deferred;
10 import org.implab.gradle.common.sources.GenericSourceSet;
11
12 public class SourceSetRegistry {
13 private final Map<String, Deferred<GenericSourceSet>> materialized = new HashMap<>();
14 private final NamedDomainObjectContainer<GenericSourceSet> sourceSets;
15 private final ObjectFactory objectFactory;
16
17 public SourceSetRegistry(ObjectFactory objectFactory) {
18 this.objectFactory = objectFactory;
19 this.sourceSets = objectFactory.domainObjectContainer(GenericSourceSet.class, this::createSourceSet);
20 }
21
22 void forEachMaterialized(Consumer<? super GenericSourceSet> consumer) {
23 materialized.values().stream()
24 .filter(Deferred::resolved)
25 .map(Deferred::value)
26 .forEach(consumer);
27 }
28
29 public NamedDomainObjectContainer<GenericSourceSet> sourceSets() {
30 return sourceSets;
31 }
32
33 public void whenMaterialized(String name, Consumer<? super GenericSourceSet> consumer) {
34 materialized(name).whenResolved(consumer);
35 }
36
37 private GenericSourceSet createSourceSet(String name) {
38 var sourceSet = objectFactory.newInstance(GenericSourceSet.class, name);
39 materialized(name).resolve(sourceSet);
40 return sourceSet;
41 }
42
43 private Deferred<GenericSourceSet> materialized(String name) {
44 return materialized.computeIfAbsent(name, k -> new Deferred<>());
45 }
46 }
@@ -1,4 +1,7
1 1 syntax: glob
2 2 .gradle/
3 .codex/
3 4 common/build/
4 5 common/bin/
6 variants/build/
7 variants/bin/
@@ -11,6 +11,16
11 11 - `require*` это `find*` + `fail-fast` с понятной ошибкой в месте вызова.
12 12 - Для нового API предпочтительны формы `find/require`; новые `get*` по возможности не добавлять.
13 13
14 ### Документация
15
16 - документирование кода должно быть на английском языке
17 - к публичному API
18 - описание должно отражать назначение, где используется и какое влияние оказывает на остальные части программы
19 - давать небольшое описание концепции, а также краткие примеры
20 - к приватному API достаточно давать краткую справку о назначении и использовании
21 - реализацию алгоритмов в коде сопровождать комментариями с пояснениями, тривиальные операции пояснять не требуется.
22 - документация должна формироваться согласно требованиям по форматированию типа javadoc, jsdoc и т.п., в зависимости от используемых в проекте языках и инструментах.
23
14 24 ## Identity-first modeling
15 25
16 26 Prefer an **identity-first** split between:
@@ -5,10 +5,20 import java.util.List;
5 5 import java.util.function.Consumer;
6 6
7 7 public final class Deferred<T> {
8 private final List<Consumer<T>> listeners = new LinkedList<>();
8 private final List<Consumer<? super T>> listeners = new LinkedList<>();
9 9 private T value;
10 10 private boolean resolved = false;
11 11
12 public boolean resolved() {
13 return resolved;
14 }
15
16 public T value() {
17 if (!resolved)
18 throw new IllegalStateException();
19 return value;
20 }
21
12 22 public void resolve(T value) {
13 23 if (resolved) {
14 24 throw new IllegalStateException("Already resolved");
@@ -19,7 +29,7 public final class Deferred<T> {
19 29 listeners.clear();
20 30 }
21 31
22 public void whenResolved(Consumer<T> listener) {
32 public void whenResolved(Consumer<? super T> listener) {
23 33 if (resolved) {
24 34 listener.accept(value);
25 35 } else {
@@ -120,6 +120,60 Within the same selector level, actions
120 120
121 121 For example, if two plugins both configure `layer("main")`, their actions are applied in the same order in which they were registered.
122 122
123 ### Scope of this guarantee
124
125 This precedence describes the normal materialization order used by the source-set
126 materializer.
127
128 It is stable for source sets that are configured before they are materialized.
129
130 If a selector rule is added after a target source set has already been
131 materialized, the behavior depends on the selected late-configuration policy.
132
133 - in `fail` mode, such late configuration is rejected
134 - in `warn` and `allow` modes, the late action is applied as an imperative
135 follow-up step
136 - in `warn` and `allow` modes, selector precedence is not reconstructed
137 retroactively for already materialized targets
138
139 ---
140
141 ## Late Configuration Policy
142
143 `variantSources` exposes a policy switch for selector rules that target already
144 materialized source sets:
145
146 ```groovy
147 variantSources {
148 lateConfigurationPolicy {
149 failOnLateConfiguration()
150 }
151 }
152 ```
153
154 Available modes:
155
156 - `failOnLateConfiguration()` rejects such rules
157 - `warnOnLateConfiguration()` allows them and emits a warning
158 - `allowLateConfiguration()` allows them silently
159
160 Policy rules:
161
162 - the policy must be chosen before the first selector rule is added
163 - selector rules here mean `variant(...)`, `layer(...)`, and `unit(...)`
164 - once chosen, the policy cannot be changed later
165 - the policy is single-valued; it is not intended to be switched during further
166 configuration
167
168 Operationally:
169
170 - `fail` preserves the strict precedence contract by rejecting late mutation of
171 already materialized targets
172 - `warn` and `allow` keep compatibility with imperative late mutation
173 - in `warn` and `allow`, already materialized targets observe the late action in
174 actual registration order, not in reconstructed `variant < layer < unit`
175 order
176
123 177 ---
124 178
125 179 ## Example
@@ -184,13 +238,15 The `variantSources` API is exposed thro
184 238
185 239 Conceptually, configuration is registered against finalized model objects, while DSL sugar may still use names for convenience.
186 240
187 Internally, selector-based configuration is accumulated and later applied by the source-set materializer when a `GenericSourceSet` is created for a compile unit.
241 Internally, selector-based configuration is accumulated and later applied by the
242 source-set materializer when a `GenericSourceSet` is created for a compile unit.
188 243
189 244 This guarantees that:
190 245
191 - selector precedence is stable
246 - selector precedence is stable before materialization
192 247 - registration order is preserved
193 - configuration does not depend on the materialization moment
248 - configuration of already materialized targets is governed by the selected
249 late-configuration policy
194 250 - adapters do not need to depend on raw Gradle lifecycle timing
195 251
196 252 ---
@@ -206,5 +262,8 variant < layer < unit
206 262 ```
207 263
208 264 - registration order is preserved within the same selector level
265 - already materialized targets are handled by `lateConfigurationPolicy(...)`
266 - the late-configuration policy must be selected before the first selector rule
267 and cannot be changed later
209 268 - `variants` defines what exists
210 269 - `variantSources` defines how those compile units are materialized as source sets
@@ -1,12 +1,17
1 1 package org.implab.gradle.variants;
2 2
3 import java.util.Objects;
4 import java.util.function.Predicate;
5
3 6 import org.eclipse.jdt.annotation.NonNullByDefault;
4 7 import org.gradle.api.Action;
8 import org.gradle.api.InvalidUserDataException;
9 import org.gradle.api.Named;
5 10 import org.gradle.api.Plugin;
6 11 import org.gradle.api.Project;
7 12 import org.implab.gradle.common.core.lang.Deferred;
13 import org.implab.gradle.common.core.lang.Strings;
8 14 import org.implab.gradle.common.sources.GenericSourceSet;
9 import org.implab.gradle.common.sources.SourcesPlugin;
10 15 import org.implab.gradle.variants.core.Layer;
11 16 import org.implab.gradle.variants.core.Variant;
12 17 import org.implab.gradle.variants.core.VariantsExtension;
@@ -14,89 +19,138 import org.implab.gradle.variants.core.V
14 19 import org.implab.gradle.variants.sources.CompileUnit;
15 20 import org.implab.gradle.variants.sources.CompileUnitsView;
16 21 import org.implab.gradle.variants.sources.RoleProjectionsView;
17 import org.implab.gradle.variants.sources.SourceSetMaterializer;
18 22 import org.implab.gradle.variants.sources.VariantSourcesContext;
23 import org.implab.gradle.variants.sources.VariantSourcesExtension;
24 import org.implab.gradle.variants.sources.internal.CompileUnitNamer;
25 import org.implab.gradle.variants.sources.internal.DefaultCompileUnitNamingPolicy;
26 import org.implab.gradle.variants.sources.internal.DefaultLateConfigurationPolicySpec;
27 import org.implab.gradle.variants.sources.internal.DefaultVariantSourcesContext;
28 import org.implab.gradle.variants.sources.internal.SourceSetConfigurationRegistry;
29 import org.implab.gradle.variants.sources.internal.SourceSetRegistry;
19 30
20 31 @NonNullByDefault
21 32 public abstract class VariantSourcesPlugin implements Plugin<Project> {
33 public static final String VARIANT_SOURCES_EXTENSION = "variantSources";
34
22 35 @Override
23 36 public void apply(Project target) {
37 var extensions = target.getExtensions();
38
24 39 // Apply the main VariantsPlugin to ensure the core variant model is available.
25 40 target.getPlugins().apply(VariantsPlugin.class);
26 target.getPlugins().apply(SourcesPlugin.class);
27 41 // Access the VariantsExtension to configure variant sources.
28 var variantsExtension = target.getExtensions().getByType(VariantsExtension.class);
42 var variantsExtension = extensions.getByType(VariantsExtension.class);
29 43 var objectFactory = target.getObjects();
30 44
31 var sources = SourcesPlugin.getSourcesExtension(target);
32
33 45 var deferred = new Deferred<VariantSourcesContext>();
34 46
47 var lateConfigurationPolicy = new DefaultLateConfigurationPolicySpec();
48 var namingPolicy = new DefaultCompileUnitNamingPolicy();
49
35 50 variantsExtension.whenFinalized(variants -> {
51 // create variant views
36 52 var compileUnits = CompileUnitsView.of(variants);
37 53 var roleProjections = RoleProjectionsView.of(variants);
38 54
39 var context = new Context(variants, compileUnits, roleProjections);
55 // create registries
56 var sourceSetRegistry = new SourceSetRegistry(objectFactory);
57 var sourceSetConfiguration = new SourceSetConfigurationRegistry(lateConfigurationPolicy::mode);
58
59 // build compile unit namer
60 var compileUnitNamer = CompileUnitNamer.builder()
61 .addUnits(compileUnits.getUnits())
62 .nameCollisionPolicy(namingPolicy.policy())
63 .build();
40 64
65 // create the context
66 var context = new DefaultVariantSourcesContext(
67 variants,
68 compileUnits,
69 roleProjections,
70 compileUnitNamer,
71 sourceSetRegistry,
72 sourceSetConfiguration
73 );
41 74 deferred.resolve(context);
42 75 });
43 76
44 // var
77 var variantSourcesExtension = new VariantSourcesExtension() {
78 @Override
79 public void whenFinalized(Action<? super VariantSourcesContext> action) {
80 deferred.whenResolved(action::execute);
81 }
82
83 @Override
84 public void lateConfigurationPolicy(Action<? super LateConfigurationPolicySpec> action) {
85 action.execute(lateConfigurationPolicy);
86 }
87
88 @Override
89 public void namingPolicy(Action<? super NamingPolicySpec> action) {
90 action.execute(namingPolicy);
91 }
92
93 @Override
94 public void variant(String variantName, Action<? super GenericSourceSet> action) {
95 Strings.argumentNotNullOrBlank(variantName, "variantName");
96 Objects.requireNonNull(action, "action can't be null");
97
98 lateConfigurationPolicy.finalizePolicy();
99
100 whenFinalized(ctx -> ctx.configureVariant(resolveVariant(ctx.getVariants(), variantName), action));
101 }
102
103 @Override
104 public void layer(String layerName, Action<? super GenericSourceSet> action) {
105 // protect external DSL
106 Strings.argumentNotNullOrBlank(layerName, "layerName");
107 Objects.requireNonNull(action, "action can't be null");
108
109 lateConfigurationPolicy.finalizePolicy();
110
111 whenFinalized(ctx -> ctx.configureLayer(resolveLayer(ctx.getVariants(), layerName), action));
112 }
113
114 @Override
115 public void unit(String variantName, String layerName, Action<? super GenericSourceSet> action) {
116 Strings.argumentNotNullOrBlank(layerName, "layerName");
117 Strings.argumentNotNullOrBlank(variantName, "variantName");
118 Objects.requireNonNull(action, "action can't be null");
119
120 lateConfigurationPolicy.finalizePolicy();
121
122 whenFinalized(ctx -> ctx.configureUnit(resolveCompileUnit(ctx, variantName, layerName), action));
123 }
124 };
125
126 extensions.add(VariantSourcesExtension.class, VARIANT_SOURCES_EXTENSION, variantSourcesExtension);
45 127
46 128 }
47 129
48 private static class Context implements VariantSourcesContext {
49
50 private final VariantsView variantsView;
51 private final CompileUnitsView compileUnitsView;
52 private final RoleProjectionsView roleProjectionsView;
53
54 Context(VariantsView variantsView, CompileUnitsView compileUnitsView, RoleProjectionsView roleProjectionsView) {
55 this.variantsView = variantsView;
56 this.compileUnitsView = compileUnitsView;
57 this.roleProjectionsView = roleProjectionsView;
58 }
59
60 @Override
61 public VariantsView getVariants() {
62 return variantsView;
63 }
64
65 @Override
66 public CompileUnitsView getCompileUnits() {
67 return compileUnitsView;
130 private static Layer resolveLayer(VariantsView variants, String name) {
131 return variants.getLayers().stream()
132 .filter(named(name))
133 .findAny()
134 .orElseThrow(() -> new IllegalArgumentException("Layer '" + name + "' isn't declared"));
68 135 }
69 136
70 @Override
71 public RoleProjectionsView getRoleProjections() {
72 return roleProjectionsView;
73 }
74
75 @Override
76 public SourceSetMaterializer getSourceSets() {
77 // TODO Auto-generated method stub
78 throw new UnsupportedOperationException("Unimplemented method 'getSourceSets'");
137 private static Variant resolveVariant(VariantsView variants, String name) {
138 return variants.getVariants().stream()
139 .filter(named(name))
140 .findAny()
141 .orElseThrow(() -> new IllegalArgumentException("Variant '" + name + "' is't declared"));
79 142 }
80 143
81 @Override
82 public void configureLayer(Layer layer, Action<? super GenericSourceSet> action) {
83 // TODO Auto-generated method stub
84 throw new UnsupportedOperationException("Unimplemented method 'configureLayer'");
144 private static CompileUnit resolveCompileUnit(VariantSourcesContext ctx, String variantName, String layerName) {
145 return ctx.getCompileUnits().findUnit(
146 resolveVariant(ctx.getVariants(), variantName),
147 resolveLayer(ctx.getVariants(), layerName))
148 .orElseThrow(() -> new InvalidUserDataException(
149 "The CompileUnit isn't declared for variant '" + variantName + "', layer '" + layerName + "'"));
85 150 }
86 151
87 @Override
88 public void configureVariant(Variant variant, Action<? super GenericSourceSet> action) {
89 // TODO Auto-generated method stub
90 throw new UnsupportedOperationException("Unimplemented method 'configureVariant'");
91 }
92
93 @Override
94 public void configureUnit(CompileUnit unit, Action<? super GenericSourceSet> action) {
95 // TODO Auto-generated method stub
96 throw new UnsupportedOperationException("Unimplemented method 'configureUnit'");
152 private static Predicate<Named> named(String name) {
153 return named -> named.getName().equals(name);
97 154 }
98 155
99 156 }
100
101
102 }
@@ -12,6 +12,16 import java.util.stream.Collectors;
12 12 import org.eclipse.jdt.annotation.NonNullByDefault;
13 13 import org.gradle.api.InvalidUserDataException;
14 14
15 /**
16 * A resolved view of declared variants, roles, layers, and their bindings.
17 *
18 * Built from {@link VariantDefinition} instances, this class materializes validated
19 * {@link VariantRoleLayer} entries and provides lookup APIs grouped by variant,
20 * role, or layer.
21 *
22 * Typical usage is to collect identities and variant definitions through
23 * {@link Builder}, then use the resulting view to traverse resolved bindings.
24 */
15 25 @NonNullByDefault
16 26 public class VariantsView {
17 27 private final Set<Layer> layers;
@@ -36,41 +46,82 public class VariantsView {
36 46 .collect(Collectors.groupingBy(VariantRoleLayer::layer, Collectors.toSet()));
37 47 }
38 48
49 /**
50 * Returns all declared layers included in this view.
51 */
39 52 public Set<Layer> getLayers() {
40 53 return layers;
41 54 }
42 55
56 /**
57 * Returns all declared roles included in this view.
58 */
43 59 public Set<Role> getRoles() {
44 60 return roles;
45 61 }
46 62
63 /**
64 * Returns all declared variants included in this view.
65 */
47 66 public Set<Variant> getVariants() {
48 67 return variants;
49 68 }
50 69
70 /**
71 * Returns all resolved variant-role-layer bindings.
72 */
51 73 public Set<VariantRoleLayer> getEntries() {
52 74 return entries;
53 75 }
54 76
77 /**
78 * Returns all bindings associated with the specified variant.
79 *
80 * An empty set is returned when the variant has no bindings in this view.
81 */
55 82 public Set<VariantRoleLayer> getEntriesForVariant(Variant variant) {
56 83 return entriesByVariant.getOrDefault(variant, Set.of());
57 84 }
58 85
86 /**
87 * Returns all bindings associated with the specified layer.
88 *
89 * An empty set is returned when the layer has no bindings in this view.
90 */
59 91 public Set<VariantRoleLayer> getEntriesForLayer(Layer layer) {
60 92 return entriesByLayer.getOrDefault(layer, Set.of());
61 93 }
62 94
95 /**
96 * Returns all bindings associated with the specified role.
97 *
98 * An empty set is returned when the role has no bindings in this view.
99 */
63 100 public Set<VariantRoleLayer> getEntriesForRole(Role role) {
64 101 return entriesByRole.getOrDefault(role, Set.of());
65 102 }
66 103
104 /**
105 * A resolved binding between a variant, a role, and a layer.
106 *
107 * @param variant the resolved variant
108 * @param role the resolved role
109 * @param layer the resolved layer
110 */
67 111 public record VariantRoleLayer(Variant variant, Role role, Layer layer) {
68 112 }
69 113
114 /**
115 * Creates a builder for assembling a {@link VariantsView}.
116 */
70 117 public static Builder builder() {
71 118 return new Builder();
72 119 }
73 120
121 /**
122 * Collects declared identities and variant definitions, then resolves them
123 * into a {@link VariantsView}.
124 */
74 125 public static class Builder {
75 126
76 127 private final Map<String, Layer> layers = new LinkedHashMap<>();
@@ -81,30 +132,48 public class VariantsView {
81 132 private Builder() {
82 133 }
83 134
135 /**
136 * Adds or replaces a role by its name.
137 */
84 138 public Builder addRole(Role role) {
85 139 Objects.requireNonNull(role, "role can't be null");
86 140 roles.put(role.getName(), role);
87 141 return this;
88 142 }
89 143
144 /**
145 * Adds or replaces a layer by its name.
146 */
90 147 public Builder addLayer(Layer layer) {
91 148 Objects.requireNonNull(layer, "layer can't be null");
92 149 layers.put(layer.getName(), layer);
93 150 return this;
94 151 }
95 152
153 /**
154 * Adds or replaces a variant by its name.
155 */
96 156 public Builder addVariant(Variant variant) {
97 157 Objects.requireNonNull(variant, "variant can't be null");
98 158 variants.put(variant.getName(), variant);
99 159 return this;
100 160 }
101 161
162 /**
163 * Adds a variant definition to be resolved during {@link #build()}.
164 */
102 165 public Builder addDefinition(VariantDefinition definition) {
103 166 Objects.requireNonNull(definition, "definition can't be null");
104 167 definitions.add(definition);
105 168 return this;
106 169 }
107 170
171 /**
172 * Resolves collected identities and definitions into an immutable view.
173 *
174 * Missing variant, role, or layer declarations referenced by definitions
175 * cause {@link InvalidUserDataException}.
176 */
108 177 public VariantsView build() {
109 178
110 179 var entries = definitions.stream()
@@ -40,12 +40,32 public interface VariantSourcesContext {
40 40 * - to already materialized source sets of this layer
41 41 * - to all future source sets of this layer
42 42 *
43 * Actions are applied in registration order.
43 * <p>For future source sets, selector precedence and registration order are
44 * preserved by the materializer.
45 *
46 * <p>For already materialized source sets, behavior is governed by
47 * {@link VariantSourcesExtension#lateConfigurationPolicy(org.gradle.api.Action)}.
48 * In warn/allow modes the action is applied as a late imperative step and does
49 * not retroactively restore selector precedence.
44 50 */
45 51 void configureLayer(Layer layer, Action<? super GenericSourceSet> action);
46 52
53 /**
54 * Configures all GenericSourceSets produced from the given variant.
55 *
56 * <p>Late application semantics for already materialized source sets are
57 * governed by
58 * {@link VariantSourcesExtension#lateConfigurationPolicy(org.gradle.api.Action)}.
59 */
47 60 void configureVariant(Variant variant, Action<? super GenericSourceSet> action);
48 61
62 /**
63 * Configures the GenericSourceSet produced from the given compile unit.
64 *
65 * <p>Late application semantics for already materialized source sets are
66 * governed by
67 * {@link VariantSourcesExtension#lateConfigurationPolicy(org.gradle.api.Action)}.
68 */
49 69 void configureUnit(CompileUnit unit, Action<? super GenericSourceSet> action);
50 70
51 } No newline at end of file
71 }
@@ -1,54 +1,52
1 1 package org.implab.gradle.variants.sources;
2 2
3 import java.util.Objects;
4 import java.util.function.Predicate;
5
6 3 import org.eclipse.jdt.annotation.NonNullByDefault;
7 4 import org.gradle.api.Action;
8 import org.gradle.api.InvalidUserDataException;
9 import org.gradle.api.Named;
10 5 import org.implab.gradle.common.core.lang.Closures;
11 import org.implab.gradle.common.core.lang.Strings;
12 6 import org.implab.gradle.common.sources.GenericSourceSet;
13 import org.implab.gradle.variants.core.Layer;
14 import org.implab.gradle.variants.core.Variant;
15 import org.implab.gradle.variants.core.VariantsView;
16
17 7 import groovy.lang.Closure;
18 8
19 9 @NonNullByDefault
20 10 public interface VariantSourcesExtension {
21 11
22 default void layer(String layerName, Action<? super GenericSourceSet> action) {
23 // protect external DSL
24 Strings.argumentNotNullOrBlank(layerName, "layerName");
25 Objects.requireNonNull(action, "action can't be null");
12 /**
13 * Selects how selector rules behave when they target an already materialized
14 * {@link GenericSourceSet}.
15 *
16 * <p>This policy is single-valued:
17 * <ul>
18 * <li>it must be selected before the first selector rule is registered via
19 * {@link #variant(String, Action)}, {@link #layer(String, Action)} or
20 * {@link #unit(String, String, Action)};</li>
21 * <li>once selected, it cannot be changed later;</li>
22 * <li>the policy controls both diagnostics and late-application semantics.</li>
23 * </ul>
24 */
25 void lateConfigurationPolicy(Action<? super LateConfigurationPolicySpec> action);
26 26
27 whenFinalized(ctx -> ctx.configureLayer(resolveLayer(ctx.getVariants(), layerName), action));
27 default void lateConfigurationPolicy(Closure<?> closure) {
28 lateConfigurationPolicy(Closures.action(closure));
28 29 }
29 30
31 void namingPolicy(Action<? super NamingPolicySpec> action);
32
33 default void namingPolicy(Closure<?> closure) {
34 namingPolicy(Closures.action(closure));
35 }
36
37 void layer(String layerName, Action<? super GenericSourceSet> action);
38
30 39 default void layer(String layerName, Closure<?> closure) {
31 40 layer(layerName, Closures.action(closure));
32 41 }
33 42
34 default void variant(String variantName, Action<? super GenericSourceSet> action) {
35 Strings.argumentNotNullOrBlank(variantName, "variantName");
36 Objects.requireNonNull(action, "action can't be null");
37
38 whenFinalized(ctx -> ctx.configureVariant(resolveVariant(ctx.getVariants(), variantName), action));
39 }
43 void variant(String variantName, Action<? super GenericSourceSet> action);
40 44
41 45 default void variant(String variantName, Closure<?> closure) {
42 46 variant(variantName, Closures.action(closure));
43 47 }
44 48
45 default void unit(String variantName, String layerName, Action<? super GenericSourceSet> action) {
46 Strings.argumentNotNullOrBlank(layerName, "layerName");
47 Strings.argumentNotNullOrBlank(variantName, "variantName");
48 Objects.requireNonNull(action, "action can't be null");
49
50 whenFinalized(ctx -> ctx.configureUnit(resolveCompileUnit(ctx, variantName, layerName), action));
51 }
49 void unit(String variantName, String layerName, Action<? super GenericSourceSet> action);
52 50
53 51 /**
54 52 * Invoked when finalized variants-derived source context becomes available.
@@ -65,29 +63,43 public interface VariantSourcesExtension
65 63 whenFinalized(Closures.action(closure));
66 64 }
67 65
68 private static Layer resolveLayer(VariantsView variants, String name) {
69 return variants.getLayers().stream()
70 .filter(named(name))
71 .findAny()
72 .orElseThrow(() -> new IllegalArgumentException("Layer '" + name + "' isn't declared"));
66
67 /**
68 * Imperative selector for the late-configuration mode.
69 *
70 * <p>Exactly one mode is expected to be chosen for the extension lifecycle.
71 */
72 interface LateConfigurationPolicySpec {
73 /**
74 * Rejects selector registration if it targets any already materialized
75 * source set.
76 */
77 void failOnLateConfiguration();
78
79 /**
80 * Allows late selector registration, but emits a warning when it targets an
81 * already materialized source set.
82 *
83 * <p>For such targets, selector precedence is not re-established
84 * retroactively. The action is applied as a late imperative step, after the
85 * state already produced at the materialization moment.
86 */
87 void warnOnLateConfiguration();
88
89 /**
90 * Allows late selector registration without a warning when it targets an
91 * already materialized source set.
92 *
93 * <p>For such targets, selector precedence is not re-established
94 * retroactively. The action is applied as a late imperative step, after the
95 * state already produced at the materialization moment.
96 */
97 void allowLateConfiguration();
73 98 }
74 99
75 private static Variant resolveVariant(VariantsView variants, String name) {
76 return variants.getVariants().stream()
77 .filter(named(name))
78 .findAny()
79 .orElseThrow(() -> new IllegalArgumentException("Variant '" + name + "' is't declared"));
80 }
100 interface NamingPolicySpec {
101 void failOnNameCollision();
81 102
82 private static CompileUnit resolveCompileUnit(VariantSourcesContext ctx, String variantName, String layerName) {
83 return ctx.getCompileUnits().findUnit(
84 resolveVariant(ctx.getVariants(), variantName),
85 resolveLayer(ctx.getVariants(), layerName))
86 .orElseThrow(() -> new InvalidUserDataException(
87 "The CompileUnit isn't declared for variant '" + variantName + "', layer '" + layerName + "'"));
103 void resolveNameCollision();
88 104 }
89
90 private static Predicate<Named> named(String name) {
91 return named -> named.getName().equals(name);
92 105 }
93 } No newline at end of file
@@ -1,15 +1,114
1 1 package org.implab.gradle.variants.sources.internal;
2 2
3 import java.text.MessageFormat;
4 import java.util.LinkedHashMap;
5 import java.util.LinkedList;
6 import java.util.List;
7 import java.util.Map;
8 import java.util.function.Consumer;
9 import java.util.function.Supplier;
10 import java.util.stream.Collectors;
11
12 import org.eclipse.jdt.annotation.NonNullByDefault;
3 13 import org.gradle.api.Action;
14 import org.gradle.api.Named;
15 import org.gradle.api.logging.Logger;
16 import org.gradle.api.logging.Logging;
4 17 import org.implab.gradle.common.sources.GenericSourceSet;
5 18 import org.implab.gradle.variants.core.Layer;
19 import org.implab.gradle.variants.core.Variant;
6 20 import org.implab.gradle.variants.sources.CompileUnit;
7 21
8 public interface LayerConfigurationRegistry {
22 @NonNullByDefault
23 public class SourceSetConfigurationRegistry {
24 private static final Logger logger = Logging.getLogger(SourceSetConfigurationRegistry.class);
25
26 private final Map<Layer, ReplayableQueue<GenericSourceSet>> sourcesByLayer = new LinkedHashMap<>();
27 private final Map<Variant, ReplayableQueue<GenericSourceSet>> sourcesByVariant = new LinkedHashMap<>();
28 private final Map<CompileUnit, ReplayableQueue<GenericSourceSet>> sourcesByUnit = new LinkedHashMap<>();
29
30 private final Supplier<LateConfigurationMode> lateConfigurationMode;
31
32 public SourceSetConfigurationRegistry(Supplier<LateConfigurationMode> lateConfigurationMode) {
33 this.lateConfigurationMode = lateConfigurationMode;
34 }
9 35
10 void addLayerAction(Layer layer, Action<? super GenericSourceSet> action);
36 public void addLayerAction(Layer layer, Action<? super GenericSourceSet> action) {
37 addToActions(
38 sourcesByLayer.computeIfAbsent(layer, key -> new ReplayableQueue<>()),
39 action,
40 MessageFormat.format(
41 "Source sets for [layer={0}] layer already materialized",
42 layer.getName()));
43 }
44
45 public void addVariantAction(Variant variant, Action<? super GenericSourceSet> action) {
46 addToActions(
47 sourcesByVariant.computeIfAbsent(variant, key -> new ReplayableQueue<>()),
48 action,
49 MessageFormat.format(
50 "Source sets for [variant={0}] variant already materialized",
51 variant.getName()));
52
53 }
54
55 public void addCompileUnitAction(CompileUnit unit, Action<? super GenericSourceSet> action) {
56 addToActions(
57 sourcesByUnit.computeIfAbsent(unit, key -> new ReplayableQueue<>()),
58 action,
59 MessageFormat.format(
60 "Source set for [variant={0}, layer={1}] already materialed",
61 unit.variant().getName(),
62 unit.layer().getName()));
63 }
11 64
12 void applyLayer(Layer layer, GenericSourceSet sourceSet);
65 private void addToActions(
66 ReplayableQueue<GenericSourceSet> actions,
67 Action<? super GenericSourceSet> action,
68 String assertMessage) {
69 assertLazyConfiguration(actions.values(), assertMessage);
70 actions.forEach(action::execute);
71 }
72
73 void assertLazyConfiguration(List<GenericSourceSet> sets, String message) {
74 if (sets.size() == 0)
75 return;
76
77 var names = sets.stream().map(Named::getName).collect(Collectors.joining(", "));
78
79 switch (lateConfigurationMode.get()) {
80 case FAIL:
81 throw new IllegalStateException(message + " [" + names + "]");
82 case WARN:
83 logger.warn(message + "\n\t" + names);
84 break;
85 default:
86 break;
87 }
88 }
13 89
14 void applyUnit(CompileUnit unit, GenericSourceSet sourceSet);
90 public void applyConfiguration(CompileUnit unit, GenericSourceSet sourceSet) {
91 sourcesByVariant.computeIfAbsent(unit.variant(), key -> new ReplayableQueue<>()).add(sourceSet);
92 sourcesByLayer.computeIfAbsent(unit.layer(), key -> new ReplayableQueue<>()).add(sourceSet);
93 sourcesByUnit.computeIfAbsent(unit, key -> new ReplayableQueue<>()).add(sourceSet);
15 94 }
95
96 class ReplayableQueue<T> {
97 private final List<Consumer<? super T>> consumers = new LinkedList<>();
98 private final List<T> values = new LinkedList<>();
99
100 public void add(T value) {
101 consumers.forEach(consumer -> consumer.accept(value));
102 values.add(value);
103 }
104
105 List<T> values() {
106 return List.copyOf(values);
107 }
108
109 public void forEach(Consumer<? super T> consumer) {
110 values.forEach(consumer);
111 consumers.add(consumer);
112 }
113 }
114 }
@@ -454,7 +454,6 Reasons:
454 454 * the DSL is internal to source materialization
455 455 * the source of truth for unit existence is already finalized in `VariantsView`
456 456 * `GenericSourceSetMaterializer` returns `NamedDomainObjectProvider<GenericSourceSet>`
457 * late configuration of providers is expected and acceptable
458 457 * adapters may need to refine source-related behavior after `variants` is finalized
459 458
460 459 Therefore:
@@ -471,6 +470,59 It reflects the difference between:
471 470
472 471 ---
473 472
473 ## Late configuration policy
474
475 Openness of `variantSources` does not mean that late configuration is
476 semantically neutral.
477
478 Selector rules may be added after the finalized context becomes available, but
479 their behavior against already materialized `GenericSourceSet` objects must be
480 controlled explicitly.
481
482 Conceptually, `variantSources` exposes a policy choice such as:
483
484 ```groovy
485 variantSources {
486 lateConfigurationPolicy {
487 failOnLateConfiguration()
488 }
489 }
490 ```
491
492 Available modes are:
493
494 * `failOnLateConfiguration()`
495 * `warnOnLateConfiguration()`
496 * `allowLateConfiguration()`
497
498 Meaning:
499
500 * `fail` rejects selector rules that target already materialized source sets
501 * `warn` allows them but emits a warning
502 * `allow` allows them silently
503
504 This policy is intentionally modeled as an imperative choice, not as a mutable
505 property:
506
507 * it must be chosen before the first selector rule is added
508 * selector rules here mean `variant(...)`, `layer(...)`, and `unit(...)`
509 * once chosen, it cannot be changed later
510 * it controls runtime behavior, not just a stored value
511
512 For source sets configured before materialization, selector precedence remains:
513
514 ```text
515 variant < layer < unit
516 ```
517
518 For already materialized source sets in `warn` and `allow` modes:
519
520 * the late action is applied as an imperative follow-up step
521 * selector precedence is not reconstructed retroactively
522 * actual observation order is the order in which late actions are registered
523
524 ---
525
474 526 ## `VariantSourcesContext`
475 527
476 528 `variantSources.whenFinalized(...)` remains useful, but not because `variantSources` itself is frozen.
@@ -676,7 +728,12 The existence of compile units comes fro
676 728
677 729 Adapters should not depend on raw Gradle lifecycle callbacks such as `afterEvaluate`.
678 730
679 ### 5. Keep heavy runtime objects behind providers
731 ### 5. Make late behavior explicit
732
733 Late configuration after materialization is a policy decision, not an implicit
734 guarantee.
735
736 ### 6. Keep heavy runtime objects behind providers
680 737
681 738 Materialized `GenericSourceSet` objects should remain behind a lazy API.
682 739
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now