##// 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 syntax: glob
1 syntax: glob
2 .gradle/
2 .gradle/
3 .codex/
3 common/build/
4 common/build/
4 common/bin/
5 common/bin/
6 variants/build/
7 variants/bin/
@@ -11,6 +11,16
11 - `require*` это `find*` + `fail-fast` с понятной ошибкой в месте вызова.
11 - `require*` это `find*` + `fail-fast` с понятной ошибкой в месте вызова.
12 - Для нового API предпочтительны формы `find/require`; новые `get*` по возможности не добавлять.
12 - Для нового API предпочтительны формы `find/require`; новые `get*` по возможности не добавлять.
13
13
14 ### Документация
15
16 - документирование кода должно быть на английском языке
17 - к публичному API
18 - описание должно отражать назначение, где используется и какое влияние оказывает на остальные части программы
19 - давать небольшое описание концепции, а также краткие примеры
20 - к приватному API достаточно давать краткую справку о назначении и использовании
21 - реализацию алгоритмов в коде сопровождать комментариями с пояснениями, тривиальные операции пояснять не требуется.
22 - документация должна формироваться согласно требованиям по форматированию типа javadoc, jsdoc и т.п., в зависимости от используемых в проекте языках и инструментах.
23
14 ## Identity-first modeling
24 ## Identity-first modeling
15
25
16 Prefer an **identity-first** split between:
26 Prefer an **identity-first** split between:
@@ -5,10 +5,20 import java.util.List;
5 import java.util.function.Consumer;
5 import java.util.function.Consumer;
6
6
7 public final class Deferred<T> {
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 private T value;
9 private T value;
10 private boolean resolved = false;
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 public void resolve(T value) {
22 public void resolve(T value) {
13 if (resolved) {
23 if (resolved) {
14 throw new IllegalStateException("Already resolved");
24 throw new IllegalStateException("Already resolved");
@@ -19,7 +29,7 public final class Deferred<T> {
19 listeners.clear();
29 listeners.clear();
20 }
30 }
21
31
22 public void whenResolved(Consumer<T> listener) {
32 public void whenResolved(Consumer<? super T> listener) {
23 if (resolved) {
33 if (resolved) {
24 listener.accept(value);
34 listener.accept(value);
25 } else {
35 } else {
@@ -120,6 +120,60 Within the same selector level, actions
120
120
121 For example, if two plugins both configure `layer("main")`, their actions are applied in the same order in which they were registered.
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 ## Example
179 ## Example
@@ -184,13 +238,15 The `variantSources` API is exposed thro
184
238
185 Conceptually, configuration is registered against finalized model objects, while DSL sugar may still use names for convenience.
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 This guarantees that:
244 This guarantees that:
190
245
191 - selector precedence is stable
246 - selector precedence is stable before materialization
192 - registration order is preserved
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 - adapters do not need to depend on raw Gradle lifecycle timing
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 - registration order is preserved within the same selector level
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 - `variants` defines what exists
268 - `variants` defines what exists
210 - `variantSources` defines how those compile units are materialized as source sets
269 - `variantSources` defines how those compile units are materialized as source sets
@@ -1,12 +1,17
1 package org.implab.gradle.variants;
1 package org.implab.gradle.variants;
2
2
3 import java.util.Objects;
4 import java.util.function.Predicate;
5
3 import org.eclipse.jdt.annotation.NonNullByDefault;
6 import org.eclipse.jdt.annotation.NonNullByDefault;
4 import org.gradle.api.Action;
7 import org.gradle.api.Action;
8 import org.gradle.api.InvalidUserDataException;
9 import org.gradle.api.Named;
5 import org.gradle.api.Plugin;
10 import org.gradle.api.Plugin;
6 import org.gradle.api.Project;
11 import org.gradle.api.Project;
7 import org.implab.gradle.common.core.lang.Deferred;
12 import org.implab.gradle.common.core.lang.Deferred;
13 import org.implab.gradle.common.core.lang.Strings;
8 import org.implab.gradle.common.sources.GenericSourceSet;
14 import org.implab.gradle.common.sources.GenericSourceSet;
9 import org.implab.gradle.common.sources.SourcesPlugin;
10 import org.implab.gradle.variants.core.Layer;
15 import org.implab.gradle.variants.core.Layer;
11 import org.implab.gradle.variants.core.Variant;
16 import org.implab.gradle.variants.core.Variant;
12 import org.implab.gradle.variants.core.VariantsExtension;
17 import org.implab.gradle.variants.core.VariantsExtension;
@@ -14,89 +19,138 import org.implab.gradle.variants.core.V
14 import org.implab.gradle.variants.sources.CompileUnit;
19 import org.implab.gradle.variants.sources.CompileUnit;
15 import org.implab.gradle.variants.sources.CompileUnitsView;
20 import org.implab.gradle.variants.sources.CompileUnitsView;
16 import org.implab.gradle.variants.sources.RoleProjectionsView;
21 import org.implab.gradle.variants.sources.RoleProjectionsView;
17 import org.implab.gradle.variants.sources.SourceSetMaterializer;
18 import org.implab.gradle.variants.sources.VariantSourcesContext;
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 @NonNullByDefault
31 @NonNullByDefault
21 public abstract class VariantSourcesPlugin implements Plugin<Project> {
32 public abstract class VariantSourcesPlugin implements Plugin<Project> {
33 public static final String VARIANT_SOURCES_EXTENSION = "variantSources";
34
22 @Override
35 @Override
23 public void apply(Project target) {
36 public void apply(Project target) {
37 var extensions = target.getExtensions();
38
24 // Apply the main VariantsPlugin to ensure the core variant model is available.
39 // Apply the main VariantsPlugin to ensure the core variant model is available.
25 target.getPlugins().apply(VariantsPlugin.class);
40 target.getPlugins().apply(VariantsPlugin.class);
26 target.getPlugins().apply(SourcesPlugin.class);
27 // Access the VariantsExtension to configure variant sources.
41 // Access the VariantsExtension to configure variant sources.
28 var variantsExtension = target.getExtensions().getByType(VariantsExtension.class);
42 var variantsExtension = extensions.getByType(VariantsExtension.class);
29 var objectFactory = target.getObjects();
43 var objectFactory = target.getObjects();
30
44
31 var sources = SourcesPlugin.getSourcesExtension(target);
32
33 var deferred = new Deferred<VariantSourcesContext>();
45 var deferred = new Deferred<VariantSourcesContext>();
34
46
47 var lateConfigurationPolicy = new DefaultLateConfigurationPolicySpec();
48 var namingPolicy = new DefaultCompileUnitNamingPolicy();
49
35 variantsExtension.whenFinalized(variants -> {
50 variantsExtension.whenFinalized(variants -> {
51 // create variant views
36 var compileUnits = CompileUnitsView.of(variants);
52 var compileUnits = CompileUnitsView.of(variants);
37 var roleProjections = RoleProjectionsView.of(variants);
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 deferred.resolve(context);
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 {
130 private static Layer resolveLayer(VariantsView variants, String name) {
49
131 return variants.getLayers().stream()
50 private final VariantsView variantsView;
132 .filter(named(name))
51 private final CompileUnitsView compileUnitsView;
133 .findAny()
52 private final RoleProjectionsView roleProjectionsView;
134 .orElseThrow(() -> new IllegalArgumentException("Layer '" + name + "' isn't declared"));
53
135 }
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;
68 }
69
136
70 @Override
137 private static Variant resolveVariant(VariantsView variants, String name) {
71 public RoleProjectionsView getRoleProjections() {
138 return variants.getVariants().stream()
72 return roleProjectionsView;
139 .filter(named(name))
73 }
140 .findAny()
74
141 .orElseThrow(() -> new IllegalArgumentException("Variant '" + name + "' is't declared"));
75 @Override
76 public SourceSetMaterializer getSourceSets() {
77 // TODO Auto-generated method stub
78 throw new UnsupportedOperationException("Unimplemented method 'getSourceSets'");
79 }
80
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'");
85 }
86
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'");
97 }
98
99 }
142 }
100
143
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 + "'"));
150 }
151
152 private static Predicate<Named> named(String name) {
153 return named -> named.getName().equals(name);
154 }
101
155
102 }
156 }
@@ -12,6 +12,16 import java.util.stream.Collectors;
12 import org.eclipse.jdt.annotation.NonNullByDefault;
12 import org.eclipse.jdt.annotation.NonNullByDefault;
13 import org.gradle.api.InvalidUserDataException;
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 @NonNullByDefault
25 @NonNullByDefault
16 public class VariantsView {
26 public class VariantsView {
17 private final Set<Layer> layers;
27 private final Set<Layer> layers;
@@ -36,41 +46,82 public class VariantsView {
36 .collect(Collectors.groupingBy(VariantRoleLayer::layer, Collectors.toSet()));
46 .collect(Collectors.groupingBy(VariantRoleLayer::layer, Collectors.toSet()));
37 }
47 }
38
48
49 /**
50 * Returns all declared layers included in this view.
51 */
39 public Set<Layer> getLayers() {
52 public Set<Layer> getLayers() {
40 return layers;
53 return layers;
41 }
54 }
42
55
56 /**
57 * Returns all declared roles included in this view.
58 */
43 public Set<Role> getRoles() {
59 public Set<Role> getRoles() {
44 return roles;
60 return roles;
45 }
61 }
46
62
63 /**
64 * Returns all declared variants included in this view.
65 */
47 public Set<Variant> getVariants() {
66 public Set<Variant> getVariants() {
48 return variants;
67 return variants;
49 }
68 }
50
69
70 /**
71 * Returns all resolved variant-role-layer bindings.
72 */
51 public Set<VariantRoleLayer> getEntries() {
73 public Set<VariantRoleLayer> getEntries() {
52 return entries;
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 public Set<VariantRoleLayer> getEntriesForVariant(Variant variant) {
82 public Set<VariantRoleLayer> getEntriesForVariant(Variant variant) {
56 return entriesByVariant.getOrDefault(variant, Set.of());
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 public Set<VariantRoleLayer> getEntriesForLayer(Layer layer) {
91 public Set<VariantRoleLayer> getEntriesForLayer(Layer layer) {
60 return entriesByLayer.getOrDefault(layer, Set.of());
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 public Set<VariantRoleLayer> getEntriesForRole(Role role) {
100 public Set<VariantRoleLayer> getEntriesForRole(Role role) {
64 return entriesByRole.getOrDefault(role, Set.of());
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 public record VariantRoleLayer(Variant variant, Role role, Layer layer) {
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 public static Builder builder() {
117 public static Builder builder() {
71 return new Builder();
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 public static class Builder {
125 public static class Builder {
75
126
76 private final Map<String, Layer> layers = new LinkedHashMap<>();
127 private final Map<String, Layer> layers = new LinkedHashMap<>();
@@ -81,30 +132,48 public class VariantsView {
81 private Builder() {
132 private Builder() {
82 }
133 }
83
134
135 /**
136 * Adds or replaces a role by its name.
137 */
84 public Builder addRole(Role role) {
138 public Builder addRole(Role role) {
85 Objects.requireNonNull(role, "role can't be null");
139 Objects.requireNonNull(role, "role can't be null");
86 roles.put(role.getName(), role);
140 roles.put(role.getName(), role);
87 return this;
141 return this;
88 }
142 }
89
143
144 /**
145 * Adds or replaces a layer by its name.
146 */
90 public Builder addLayer(Layer layer) {
147 public Builder addLayer(Layer layer) {
91 Objects.requireNonNull(layer, "layer can't be null");
148 Objects.requireNonNull(layer, "layer can't be null");
92 layers.put(layer.getName(), layer);
149 layers.put(layer.getName(), layer);
93 return this;
150 return this;
94 }
151 }
95
152
153 /**
154 * Adds or replaces a variant by its name.
155 */
96 public Builder addVariant(Variant variant) {
156 public Builder addVariant(Variant variant) {
97 Objects.requireNonNull(variant, "variant can't be null");
157 Objects.requireNonNull(variant, "variant can't be null");
98 variants.put(variant.getName(), variant);
158 variants.put(variant.getName(), variant);
99 return this;
159 return this;
100 }
160 }
101
161
162 /**
163 * Adds a variant definition to be resolved during {@link #build()}.
164 */
102 public Builder addDefinition(VariantDefinition definition) {
165 public Builder addDefinition(VariantDefinition definition) {
103 Objects.requireNonNull(definition, "definition can't be null");
166 Objects.requireNonNull(definition, "definition can't be null");
104 definitions.add(definition);
167 definitions.add(definition);
105 return this;
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 public VariantsView build() {
177 public VariantsView build() {
109
178
110 var entries = definitions.stream()
179 var entries = definitions.stream()
@@ -40,12 +40,32 public interface VariantSourcesContext {
40 * - to already materialized source sets of this layer
40 * - to already materialized source sets of this layer
41 * - to all future source sets of this layer
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 void configureLayer(Layer layer, Action<? super GenericSourceSet> action);
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 void configureVariant(Variant variant, Action<? super GenericSourceSet> action);
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 void configureUnit(CompileUnit unit, Action<? super GenericSourceSet> action);
69 void configureUnit(CompileUnit unit, Action<? super GenericSourceSet> action);
50
70
51 } No newline at end of file
71 }
@@ -1,54 +1,52
1 package org.implab.gradle.variants.sources;
1 package org.implab.gradle.variants.sources;
2
2
3 import java.util.Objects;
4 import java.util.function.Predicate;
5
6 import org.eclipse.jdt.annotation.NonNullByDefault;
3 import org.eclipse.jdt.annotation.NonNullByDefault;
7 import org.gradle.api.Action;
4 import org.gradle.api.Action;
8 import org.gradle.api.InvalidUserDataException;
9 import org.gradle.api.Named;
10 import org.implab.gradle.common.core.lang.Closures;
5 import org.implab.gradle.common.core.lang.Closures;
11 import org.implab.gradle.common.core.lang.Strings;
12 import org.implab.gradle.common.sources.GenericSourceSet;
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 import groovy.lang.Closure;
7 import groovy.lang.Closure;
18
8
19 @NonNullByDefault
9 @NonNullByDefault
20 public interface VariantSourcesExtension {
10 public interface VariantSourcesExtension {
21
11
22 default void layer(String layerName, Action<? super GenericSourceSet> action) {
12 /**
23 // protect external DSL
13 * Selects how selector rules behave when they target an already materialized
24 Strings.argumentNotNullOrBlank(layerName, "layerName");
14 * {@link GenericSourceSet}.
25 Objects.requireNonNull(action, "action can't be null");
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 default void layer(String layerName, Closure<?> closure) {
39 default void layer(String layerName, Closure<?> closure) {
31 layer(layerName, Closures.action(closure));
40 layer(layerName, Closures.action(closure));
32 }
41 }
33
42
34 default void variant(String variantName, Action<? super GenericSourceSet> action) {
43 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 }
40
44
41 default void variant(String variantName, Closure<?> closure) {
45 default void variant(String variantName, Closure<?> closure) {
42 variant(variantName, Closures.action(closure));
46 variant(variantName, Closures.action(closure));
43 }
47 }
44
48
45 default void unit(String variantName, String layerName, Action<? super GenericSourceSet> action) {
49 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 }
52
50
53 /**
51 /**
54 * Invoked when finalized variants-derived source context becomes available.
52 * Invoked when finalized variants-derived source context becomes available.
@@ -65,29 +63,43 public interface VariantSourcesExtension
65 whenFinalized(Closures.action(closure));
63 whenFinalized(Closures.action(closure));
66 }
64 }
67
65
68 private static Layer resolveLayer(VariantsView variants, String name) {
66
69 return variants.getLayers().stream()
67 /**
70 .filter(named(name))
68 * Imperative selector for the late-configuration mode.
71 .findAny()
69 *
72 .orElseThrow(() -> new IllegalArgumentException("Layer '" + name + "' isn't declared"));
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) {
100 interface NamingPolicySpec {
76 return variants.getVariants().stream()
101 void failOnNameCollision();
77 .filter(named(name))
78 .findAny()
79 .orElseThrow(() -> new IllegalArgumentException("Variant '" + name + "' is't declared"));
80 }
81
102
82 private static CompileUnit resolveCompileUnit(VariantSourcesContext ctx, String variantName, String layerName) {
103 void resolveNameCollision();
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 + "'"));
88 }
104 }
89
105 }
90 private static Predicate<Named> named(String name) {
91 return named -> named.getName().equals(name);
92 }
93 } No newline at end of file
@@ -1,15 +1,114
1 package org.implab.gradle.variants.sources.internal;
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 import org.gradle.api.Action;
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 import org.implab.gradle.common.sources.GenericSourceSet;
17 import org.implab.gradle.common.sources.GenericSourceSet;
5 import org.implab.gradle.variants.core.Layer;
18 import org.implab.gradle.variants.core.Layer;
19 import org.implab.gradle.variants.core.Variant;
6 import org.implab.gradle.variants.sources.CompileUnit;
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);
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 }
15 }
114 }
@@ -454,7 +454,6 Reasons:
454 * the DSL is internal to source materialization
454 * the DSL is internal to source materialization
455 * the source of truth for unit existence is already finalized in `VariantsView`
455 * the source of truth for unit existence is already finalized in `VariantsView`
456 * `GenericSourceSetMaterializer` returns `NamedDomainObjectProvider<GenericSourceSet>`
456 * `GenericSourceSetMaterializer` returns `NamedDomainObjectProvider<GenericSourceSet>`
457 * late configuration of providers is expected and acceptable
458 * adapters may need to refine source-related behavior after `variants` is finalized
457 * adapters may need to refine source-related behavior after `variants` is finalized
459
458
460 Therefore:
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 ## `VariantSourcesContext`
526 ## `VariantSourcesContext`
475
527
476 `variantSources.whenFinalized(...)` remains useful, but not because `variantSources` itself is frozen.
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 Adapters should not depend on raw Gradle lifecycle callbacks such as `afterEvaluate`.
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 Materialized `GenericSourceSet` objects should remain behind a lazy API.
738 Materialized `GenericSourceSet` objects should remain behind a lazy API.
682
739
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now