##// 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/
@@ -1,42 +1,52
1 1 # AGENTS.md
2 2
3 3 ## Проектные договоренности
4 4
5 5 ### Публичное API библиотек
6 6
7 7 - Предпочтителен `non-null` подход.
8 8 - Там, где значение живет в Gradle Provider API, возвращается `Provider<T>` (не `null`).
9 9 - Там, где lookup синхронный, возвращается `Optional<T>` (не `null`).
10 10 - `find*` рассматривается как синоним legacy `get*` (поиск без `fail-fast`).
11 11 - `require*` это `find*` + `fail-fast` с понятной ошибкой в месте вызова.
12 12 - Для нового API предпочтительны формы `find/require`; новые `get*` по возможности не добавлять.
13 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:
17 27
18 28 - **identity objects** used for discovery and selection
19 29 - **stateful/materialized objects** obtained through separate API calls
20 30
21 31 ### Rules
22 32
23 33 - Objects intended for replayable observation (`all(...)`, similar collection APIs) should be **identity objects**.
24 34 - Identity objects must be:
25 35 - cheap to create
26 36 - effectively immutable
27 37 - limited to identity and cheap selection metadata
28 38 - Heavy, computed, provider-based, or runtime-bound state must be resolved separately.
29 39 - Aggregate content should be accessed through dedicated lookup/materialization APIs.
30 40 - Eager observation of identity is acceptable.
31 41 - Eager observation of computed state is not.
32 42
33 43 ### Do not
34 44
35 45 - Do not store heavy computed state inside identity objects.
36 46 - Do not store runtime references to foreign domains inside identity objects.
37 47 - Do not use custom events when replayable identity registries plus on-demand lookup are sufficient.
38 48
39 49 ### Rule of thumb
40 50
41 51 **Identity objects contain selection metadata.
42 52 Aggregate content is obtained separately, on demand.**
@@ -1,29 +1,39
1 1 package org.implab.gradle.common.core.lang;
2 2
3 3 import java.util.LinkedList;
4 4 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");
15 25 }
16 26 this.value = value;
17 27 this.resolved = true;
18 28 listeners.forEach(listener -> listener.accept(value));
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 {
26 36 listeners.add(listener);
27 37 }
28 38 }
29 39 }
@@ -1,210 +1,269
1 1 # `variantSources`: selectors and precedence
2 2
3 3 `variantSources` configures source-set materialization over the compile-unit space.
4 4
5 5 A compile unit is defined as:
6 6
7 7 - `(variant, layer)`
8 8
9 9 This means:
10 10
11 11 - `variant` defines compilation semantics
12 12 - `layer` defines compilation partitioning
13 13
14 14 The `variantSources` DSL does not introduce a separate source model.
15 15 Instead, it provides configuration selectors over the existing compile-unit space.
16 16
17 17 ## Selectors
18 18
19 19 Three selectors are available:
20 20
21 21 - `variant(...)`
22 22 - `layer(...)`
23 23 - `unit(...)`
24 24
25 25 They all target the same set of compile units, but at different levels of specificity.
26 26
27 27 ### `variant(...)`
28 28
29 29 `variant(...)` applies configuration to all compile units that belong to the given variant.
30 30
31 31 Example:
32 32
33 33 ```groovy
34 34 variantSources {
35 35 variant("browser") {
36 36 declareOutputs("js", "dts")
37 37 }
38 38 }
39 39 ```
40 40
41 41 This affects all compile units of `browser`, for example:
42 42
43 43 - `(browser, main)`
44 44 - `(browser, rjs)`
45 45 - `(browser, test)`
46 46
47 47 Use this selector for variant-wide conventions.
48 48
49 49 ---
50 50
51 51 ### `layer(...)`
52 52
53 53 `layer(...)` applies configuration to all compile units that use the given layer.
54 54
55 55 Example:
56 56
57 57 ```groovy
58 58 variantSources {
59 59 layer("main") {
60 60 set("ts") {
61 61 srcDir("src/main/ts")
62 62 }
63 63 }
64 64 }
65 65 ```
66 66
67 67 This affects all compile units with layer `main`, for example:
68 68
69 69 - `(browser, main)`
70 70 - `(nodejs, main)`
71 71 - `(electron, main)`
72 72
73 73 Use this selector for cross-variant layer conventions.
74 74
75 75 ---
76 76
77 77 ### `unit(...)`
78 78
79 79 `unit(...)` applies configuration to one exact compile unit.
80 80
81 81 Example:
82 82
83 83 ```groovy
84 84 variantSources {
85 85 unit("browser", "main") {
86 86 set("resources") {
87 87 srcDir("src/browserMain/resources")
88 88 }
89 89 }
90 90 }
91 91 ```
92 92
93 93 This affects only:
94 94
95 95 - `(browser, main)`
96 96
97 97 Use this selector for the most specific adjustments.
98 98
99 99 ---
100 100
101 101 ## Precedence
102 102
103 103 For each compile unit, source-set configuration is applied in the following order:
104 104
105 105 ```text
106 106 variant < layer < unit
107 107 ```
108 108
109 109 This means:
110 110
111 111 1. `variant(...)` actions are applied first
112 112 2. `layer(...)` actions are applied next
113 113 3. `unit(...)` actions are applied last
114 114
115 115 Each next level is allowed to refine or override the previous one.
116 116
117 117 ### Within the same level
118 118
119 119 Within the same selector level, actions are applied in registration order.
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
126 180
127 181 ```groovy
128 182 variantSources {
129 183 variant("browser") {
130 184 declareOutputs("js", "dts")
131 185 }
132 186
133 187 layer("main") {
134 188 set("ts") {
135 189 srcDir("src/main/ts")
136 190 }
137 191 }
138 192
139 193 unit("browser", "main") {
140 194 set("resources") {
141 195 srcDir("src/browserMain/resources")
142 196 }
143 197 }
144 198 }
145 199 ```
146 200
147 201 For compile unit `(browser, main)` the effective configuration is built in this order:
148 202
149 203 1. `variant("browser")`
150 204 2. `layer("main")`
151 205 3. `unit("browser", "main")`
152 206
153 207 For compile unit `(browser, rjs)` the effective configuration is built in this order:
154 208
155 209 1. `variant("browser")`
156 210 2. `layer("rjs")` if present
157 211 3. `unit("browser", "rjs")` if present
158 212
159 213 For compile unit `(nodejs, main)` the effective configuration is built in this order:
160 214
161 215 1. `variant("nodejs")` if present
162 216 2. `layer("main")`
163 217 3. `unit("nodejs", "main")` if present
164 218
165 219 ---
166 220
167 221 ## Model boundary
168 222
169 223 These selectors do not define compile units.
170 224 Compile units are derived from finalized `variants`.
171 225
172 226 `variantSources` only configures how source sets are materialized for those units.
173 227
174 228 This means:
175 229
176 230 - `variants` is the source of truth for compile-unit existence
177 231 - `variantSources` is the source of truth for compile-unit source-set configuration
178 232
179 233 ---
180 234
181 235 ## Operational semantics
182 236
183 237 The `variantSources` API is exposed through a finalized context.
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 ---
197 253
198 254 ## Summary
199 255
200 256 - compile unit space is `(variant, layer)`
201 257 - `variant(...)`, `layer(...)`, and `unit(...)` are selectors over that space
202 258 - precedence is:
203 259
204 260 ```text
205 261 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,102 +1,156
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;
13 18 import org.implab.gradle.variants.core.VariantsView;
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;
68 }
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"));
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'");
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
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"));
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 }
@@ -1,151 +1,220
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 /**
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;
18 28 private final Set<Role> roles;
19 29 private final Set<Variant> variants;
20 30 private final Set<VariantRoleLayer> entries;
21 31
22 32 private final Map<Variant, Set<VariantRoleLayer>> entriesByVariant;
23 33 private final Map<Role, Set<VariantRoleLayer>> entriesByRole;
24 34 private final Map<Layer, Set<VariantRoleLayer>> entriesByLayer;
25 35
26 36 private VariantsView(Set<Layer> layers, Set<Role> roles, Set<Variant> variants, Set<VariantRoleLayer> entries) {
27 37 this.layers = layers;
28 38 this.roles = roles;
29 39 this.variants = variants;
30 40 this.entries = entries;
31 41 this.entriesByVariant = entries.stream()
32 42 .collect(Collectors.groupingBy(VariantRoleLayer::variant, Collectors.toSet()));
33 43 this.entriesByRole = entries.stream()
34 44 .collect(Collectors.groupingBy(VariantRoleLayer::role, Collectors.toSet()));
35 45 this.entriesByLayer = entries.stream()
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<>();
77 128 private final Map<String, Role> roles = new LinkedHashMap<>();
78 129 private final Map<String, Variant> variants = new LinkedHashMap<>();
79 130 private final List<VariantDefinition> definitions = new ArrayList<>();
80 131
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()
111 180 .flatMap(def -> def.getRoleBindings().get().stream()
112 181 .map(layerRole -> createVariantRoleLayer(
113 182 def.getName(), // variantName
114 183 layerRole.roleName(),
115 184 layerRole.layerName())))
116 185 .collect(Collectors.toSet());
117 186
118 187 return new VariantsView(
119 188 Set.copyOf(layers.values()),
120 189 Set.copyOf(roles.values()),
121 190 Set.copyOf(variants.values()),
122 191 entries);
123 192 }
124 193
125 194 private VariantRoleLayer createVariantRoleLayer(String variantName, String roleName, String layerName) {
126 195 return new VariantRoleLayer(
127 196 resolveVariant(variantName,
128 197 "Variant '" + variantName + "' isn't declared"),
129 198 resolveRole(roleName,
130 199 "Role '" + roleName + "' isn't declared, referenced in '" + variantName + "' variant"),
131 200 resolveLayer(layerName,
132 201 "Layer '" + layerName + "' isn't declared, referenced in '" + variantName
133 202 + "' variant with '" + roleName + "' role"));
134 203 }
135 204
136 205 private Layer resolveLayer(String layerName, String errorMessage) {
137 206 return Optional.ofNullable(layers.get(layerName))
138 207 .orElseThrow(() -> new InvalidUserDataException(errorMessage));
139 208 }
140 209
141 210 private Variant resolveVariant(String variantName, String errorMessage) {
142 211 return Optional.ofNullable(variants.get(variantName))
143 212 .orElseThrow(() -> new InvalidUserDataException(errorMessage));
144 213 }
145 214
146 215 private Role resolveRole(String roleName, String errorMessage) {
147 216 return Optional.ofNullable(roles.get(roleName))
148 217 .orElseThrow(() -> new InvalidUserDataException(errorMessage));
149 218 }
150 219 }
151 220 }
@@ -1,51 +1,71
1 1 package org.implab.gradle.variants.sources;
2 2
3 3 import org.gradle.api.Action;
4 4 import org.implab.gradle.common.sources.GenericSourceSet;
5 5 import org.implab.gradle.variants.core.Layer;
6 6 import org.implab.gradle.variants.core.Variant;
7 7 import org.implab.gradle.variants.core.VariantsView;
8 8
9 9 /**
10 10 * Registry of symbolic source set names produced by sources projection.
11 11 *
12 12 * Identity in this registry is the GenericSourceSet name.
13 13 */
14 14 public interface VariantSourcesContext {
15 15
16 16 /**
17 17 * Finalized core model.
18 18 */
19 19 VariantsView getVariants();
20 20
21 21 /**
22 22 * Derived compile-side view.
23 23 */
24 24 CompileUnitsView getCompileUnits();
25 25
26 26 /**
27 27 * Derived role-side view.
28 28 */
29 29 RoleProjectionsView getRoleProjections();
30 30
31 31 /**
32 32 * Lazy source set provider service.
33 33 */
34 34 SourceSetMaterializer getSourceSets();
35 35
36 36 /**
37 37 * Configures all GenericSourceSets produced from the given layer.
38 38 *
39 39 * The action is applied:
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,93 +1,105
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.
55 53 *
56 54 * Replayable:
57 55 * <ul>
58 56 * <li>if called before variants finalization, action is queued
59 57 * <li>if called after variants finalization, action is invoked immediately
60 58 * </ul>
61 59 */
62 60 void whenFinalized(Action<? super VariantSourcesContext> action);
63 61
64 62 default void whenFinalized(Closure<?> closure) {
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 }
93 } No newline at end of file
105 }
@@ -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);
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 }
@@ -1,719 +1,776
1 1 # Variants and Variant Sources
2 2
3 3 ## Overview
4 4
5 5 This document describes a two-layer model for build variants:
6 6
7 7 - `variants` defines the **core domain model**
8 8 - `variantSources` defines **source materialization semantics** for that model
9 9
10 10 The main goal is to keep the core model small, explicit, and stable, while allowing source-related behavior to remain flexible and adapter-friendly.
11 11
12 12 The model is intentionally split into:
13 13
14 14 1. a **closed, finalized domain model**
15 15 2. an **open, runtime-oriented source materialization model**
16 16
17 17 This separation is important because compilation, source aggregation, publication, and adapter-specific behavior do not belong to the same abstraction layer.
18 18
19 19 ---
20 20
21 21 ## Core idea
22 22
23 23 The `variants` model is based on three independent domains:
24 24
25 25 - `Layer`
26 26 - `Role`
27 27 - `Variant`
28 28
29 29 A finalized `VariantsView` contains the normalized relation:
30 30
31 31 - `(variant, role, layer)`
32 32
33 33 This relation is the source of truth.
34 34
35 35 Everything else is derived from it.
36 36
37 37 ---
38 38
39 39 ## `variants`: the core domain model
40 40
41 41 ### Purpose
42 42
43 43 `variants` describes:
44 44
45 45 - what layers exist
46 46 - what roles exist
47 47 - what variants exist
48 48 - which `(variant, role, layer)` combinations are valid
49 49
50 50 It does **not** describe:
51 51
52 52 - source directories
53 53 - source roots
54 54 - source set materialization
55 55 - compilation tasks
56 56 - publication mechanics
57 57 - source set inheritance
58 58 - layer merge behavior for a concrete toolchain
59 59
60 60 Those concerns are intentionally outside the core model.
61 61
62 62 ---
63 63
64 64 ## Core DSL example
65 65
66 66 ```groovy
67 67 variants {
68 68 layers {
69 69 main()
70 70 test()
71 71 generated()
72 72 rjs()
73 73 cjs()
74 74 }
75 75
76 76 roles {
77 77 production()
78 78 test()
79 79 tool()
80 80 }
81 81
82 82 variant("browser") {
83 83 role("production") {
84 84 layers("main", "generated", "rjs")
85 85 }
86 86 role("test") {
87 87 layers("main", "test", "generated", "rjs")
88 88 }
89 89 }
90 90
91 91 variant("nodejs") {
92 92 role("production") {
93 93 layers("main", "generated", "cjs")
94 94 }
95 95 role("test") {
96 96 layers("main", "test", "generated", "cjs")
97 97 }
98 98 role("tool") {
99 99 layers("main", "generated", "cjs")
100 100 }
101 101 }
102 102 }
103 103 ```
104 104
105 105 ### Interpretation
106 106
107 107 This example means:
108 108
109 109 * `browser` production uses `main`, `generated`, `rjs`
110 110 * `browser` test uses `main`, `test`, `generated`, `rjs`
111 111 * `nodejs` production uses `main`, `generated`, `cjs`
112 112 * `nodejs` test uses `main`, `test`, `generated`, `cjs`
113 113 * `nodejs` tool uses `main`, `generated`, `cjs`
114 114
115 115 The model is purely declarative.
116 116
117 117 ---
118 118
119 119 ## Identity and references
120 120
121 121 `Layer`, `Role`, and `Variant` are identity objects.
122 122
123 123 They exist as declared domain values.
124 124
125 125 References between model elements are symbolic:
126 126
127 127 * layers are referenced by layer name
128 128 * roles are referenced by role name
129 129 * variants are referenced by variant name
130 130
131 131 This is intentional.
132 132
133 133 The core model is declarative, not navigation-oriented.
134 134
135 135 It is acceptable for aggregates to hold symbolic references to foreign domain values, as long as identity is clearly defined and validated later.
136 136
137 137 ---
138 138
139 139 ## Finalization
140 140
141 141 The `variants` model is finalized once.
142 142
143 143 Finalization is an internal lifecycle transition. It is typically triggered privately, for example from `afterEvaluate`, but that mechanism is not part of the public API contract.
144 144
145 145 The public contract is:
146 146
147 147 * `variants.whenFinalized(...)`
148 148
149 149 This callback is **replayable**:
150 150
151 151 * if called before finalization, the action is queued
152 152 * if called after finalization, the action is invoked immediately
153 153
154 154 The callback receives a finalized, read-only view of the model.
155 155
156 156 Example:
157 157
158 158 ```java
159 159 variants.whenFinalized(view -> {
160 160 // use finalized VariantsView here
161 161 });
162 162 ```
163 163
164 164 ---
165 165
166 166 ## `VariantsView`
167 167
168 168 `VariantsView` is the finalized representation of the core model.
169 169
170 170 It contains:
171 171
172 172 * all declared `Layer`
173 173 * all declared `Role`
174 174 * all declared `Variant`
175 175 * all normalized entries `(variant, role, layer)`
176 176
177 177 Conceptually:
178 178
179 179 ```java
180 180 interface VariantsView {
181 181 Set<Layer> getLayers();
182 182 Set<Role> getRoles();
183 183 Set<Variant> getVariants();
184 184 Set<VariantRoleLayer> getEntries();
185 185 }
186 186 ```
187 187
188 188 Where:
189 189
190 190 ```java
191 191 record VariantRoleLayer(Variant variant, Role role, Layer layer) {}
192 192 ```
193 193
194 194 This view is:
195 195
196 196 * immutable
197 197 * normalized
198 198 * validated
199 199 * independent from DSL internals
200 200
201 201 ---
202 202
203 203 ## Derived views
204 204
205 205 Two important views can be derived from `VariantsView`:
206 206
207 207 * `CompileUnitsView`
208 208 * `RoleProjectionsView`
209 209
210 210 These views are not part of the raw core model itself, but they are naturally derived from it.
211 211
212 212 ---
213 213
214 214 ## `CompileUnitsView`
215 215
216 216 ### Purpose
217 217
218 218 A compile unit is defined as:
219 219
220 220 * `(variant, layer)`
221 221
222 222 This is based on the following rationale:
223 223
224 224 * `variant` defines compilation semantics
225 225 * `layer` partitions a variant into separate compilation units
226 226 * `role` is not a compilation boundary
227 227
228 228 This is especially useful for toolchains such as TypeScript, where compilation is often more practical or more correct per layer than for the whole variant at once.
229 229
230 230 ### Example
231 231
232 232 From:
233 233
234 234 * `(browser, production, main)`
235 235 * `(browser, production, rjs)`
236 236 * `(browser, test, main)`
237 237 * `(browser, test, test)`
238 238 * `(browser, test, rjs)`
239 239
240 240 we derive compile units:
241 241
242 242 * `(browser, main)`
243 243 * `(browser, rjs)`
244 244 * `(browser, test)`
245 245
246 246 ### Conceptual API
247 247
248 248 ```java
249 249 interface CompileUnitsView {
250 250 Set<CompileUnit> getUnits();
251 251 Set<CompileUnit> getUnitsForVariant(Variant variant);
252 252 boolean contains(Variant variant, Layer layer);
253 253 Set<Role> getRoles(CompileUnit unit);
254 254 }
255 255
256 256 record CompileUnit(Variant variant, Layer layer) {}
257 257 ```
258 258
259 259 ### Meaning
260 260
261 261 `CompileUnitsView` answers:
262 262
263 263 * what can be compiled
264 264 * how a variant is partitioned into compile units
265 265 * which logical roles include a given compile unit
266 266
267 267 ---
268 268
269 269 ## `RoleProjectionsView`
270 270
271 271 ### Purpose
272 272
273 273 A role projection is defined as:
274 274
275 275 * `(variant, role)`
276 276
277 277 This is based on the following rationale:
278 278
279 279 * `role` is not about compilation
280 280 * `role` groups compile units by purpose
281 281 * roles are more closely related to publication, aggregation, assembly, or result grouping
282 282
283 283 ### Example
284 284
285 285 For `browser`:
286 286
287 287 * `production` includes compile units:
288 288
289 289 * `(browser, main)`
290 290 * `(browser, rjs)`
291 291
292 292 * `test` includes compile units:
293 293
294 294 * `(browser, main)`
295 295 * `(browser, test)`
296 296 * `(browser, rjs)`
297 297
298 298 ### Conceptual API
299 299
300 300 ```java
301 301 interface RoleProjectionsView {
302 302 Set<RoleProjection> getProjections();
303 303 Set<RoleProjection> getProjectionsForVariant(Variant variant);
304 304 Set<RoleProjection> getProjectionsForRole(Role role);
305 305 Set<CompileUnit> getUnits(RoleProjection projection);
306 306 }
307 307
308 308 record RoleProjection(Variant variant, Role role) {}
309 309 ```
310 310
311 311 ### Meaning
312 312
313 313 `RoleProjectionsView` answers:
314 314
315 315 * how compile units are grouped by purpose
316 316 * what belongs to `production`, `test`, `tool`, etc.
317 317 * what should be aggregated or published together
318 318
319 319 ---
320 320
321 321 ## Why `CompileUnitsView` and `RoleProjectionsView` are not part of `VariantsView`
322 322
323 323 `VariantsView` is intentionally minimal.
324 324
325 325 It expresses the domain relation:
326 326
327 327 * `(variant, role, layer)`
328 328
329 329 `CompileUnitsView` and `RoleProjectionsView` are **derived interpretations** of that relation.
330 330
331 331 They are natural and useful, but they are still interpretations:
332 332
333 333 * `CompileUnit = (variant, layer)`
334 334 * `RoleProjection = (variant, role)`
335 335
336 336 This is why they are better treated as derived views rather than direct core model primitives.
337 337
338 338 ---
339 339
340 340 ## `variantSources`: source semantics for layers
341 341
342 342 ### Purpose
343 343
344 344 `variantSources` does **not** define variants.
345 345
346 346 It defines how a declared `Layer` contributes sources.
347 347
348 348 In other words:
349 349
350 350 * `variants` defines **what exists**
351 351 * `variantSources` defines **how layers become source inputs**
352 352
353 353 This distinction is important.
354 354
355 355 `variantSources` does not own the variant model. It interprets it.
356 356
357 357 ---
358 358
359 359 ## Main idea
360 360
361 361 A layer source rule describes the source contribution of a layer.
362 362
363 363 This is independent of any concrete variant or role.
364 364
365 365 Conceptually:
366 366
367 367 * `Layer -> source contribution rule`
368 368
369 369 Examples of source contribution semantics:
370 370
371 371 * base directory
372 372 * source directories
373 373 * logical source kinds (`ts`, `js`, `resources`)
374 374 * declared outputs (`js`, `dts`, `resources`)
375 375
376 376 ---
377 377
378 378 ## `variantSources` DSL example
379 379
380 380 ```groovy
381 381 variantSources {
382 382 layerRule("main") {
383 383 from("src/main")
384 384
385 385 set("ts") {
386 386 srcDir("ts")
387 387 }
388 388 set("js") {
389 389 srcDir("js")
390 390 }
391 391 set("resources") {
392 392 srcDir("resources")
393 393 }
394 394
395 395 outputs("js", "dts", "resources")
396 396 }
397 397
398 398 layerRule("test") {
399 399 from("src/test")
400 400
401 401 set("ts") {
402 402 srcDir("ts")
403 403 }
404 404 set("resources") {
405 405 srcDir("resources")
406 406 }
407 407
408 408 outputs("js", "dts", "resources")
409 409 }
410 410
411 411 layerRule("rjs") {
412 412 from("src/rjs")
413 413
414 414 set("ts") {
415 415 srcDir("ts")
416 416 }
417 417
418 418 outputs("js", "dts")
419 419 }
420 420
421 421 layerRule("cjs") {
422 422 from("src/cjs")
423 423
424 424 set("ts") {
425 425 srcDir("ts")
426 426 }
427 427
428 428 outputs("js", "dts")
429 429 }
430 430 }
431 431 ```
432 432
433 433 ### Interpretation
434 434
435 435 This means:
436 436
437 437 * `main` contributes `ts`, `js`, and `resources`
438 438 * `test` contributes `ts` and `resources`
439 439 * `rjs` contributes `ts`
440 440 * `cjs` contributes `ts`
441 441
442 442 These are layer rules only.
443 443
444 444 They do not yet say which variant consumes them.
445 445
446 446 ---
447 447
448 448 ## Why `variantSources` remains open
449 449
450 450 Unlike `variants`, `variantSources` does not need to be closed in the same way.
451 451
452 452 Reasons:
453 453
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:
461 460
462 461 * `variants` is finalized
463 462 * `variantSources` may remain open
464 463
465 464 This is not a contradiction.
466 465
467 466 It reflects the difference between:
468 467
469 468 * a closed domain model
470 469 * an open infrastructure/materialization model
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.
477 529
478 530 Its purpose is to provide access to a finalized context derived from `variants`.
479 531
480 532 This context contains:
481 533
482 534 * `CompileUnitsView`
483 535 * `RoleProjectionsView`
484 536 * `GenericSourceSetMaterializer`
485 537
486 538 Conceptually:
487 539
488 540 ```java
489 541 interface VariantSourcesContext {
490 542 CompileUnitsView getCompileUnits();
491 543 RoleProjectionsView getRoleProjections();
492 544 GenericSourceSetMaterializer getSourceSets();
493 545 }
494 546 ```
495 547
496 548 This callback is also replayable.
497 549
498 550 Example:
499 551
500 552 ```java
501 553 variantSources.whenFinalized(ctx -> {
502 554 var units = ctx.getCompileUnits();
503 555 var roles = ctx.getRoleProjections();
504 556 var sourceSets = ctx.getSourceSets();
505 557 });
506 558 ```
507 559
508 560 ---
509 561
510 562 ## `GenericSourceSetMaterializer`
511 563
512 564 ### Purpose
513 565
514 566 `GenericSourceSetMaterializer` is the official source of truth for materialized source sets.
515 567
516 568 It is responsible for:
517 569
518 570 * lazy creation of `GenericSourceSet`
519 571 * applying `layerRule`
520 572 * connecting a compile unit to a source set provider
521 573 * exposing source sets to adapters
522 574
523 575 This is the correct place to apply `layerRule`.
524 576
525 577 Adapters should not apply layer rules themselves.
526 578
527 579 ### Conceptual API
528 580
529 581 ```java
530 582 interface GenericSourceSetMaterializer {
531 583 NamedDomainObjectProvider<GenericSourceSet> getSourceSet(CompileUnit unit);
532 584 }
533 585 ```
534 586
535 587 ---
536 588
537 589 ## Why `GenericSourceSetMaterializer` should own `layerRule` application
538 590
539 591 If adapters applied `layerRule` directly, responsibility would leak across multiple layers:
540 592
541 593 * one component would know compile units
542 594 * another would know source semantics
543 595 * another would know how to configure `GenericSourceSet`
544 596
545 597 This would make the model harder to reason about.
546 598
547 599 Instead:
548 600
549 601 * `layerRule` is DSL/spec-level
550 602 * `GenericSourceSetMaterializer` is execution/materialization-level
551 603 * adapters are consumption-level
552 604
553 605 This gives a much cleaner separation.
554 606
555 607 ---
556 608
557 609 ## `GenericSourceSet` as materialization target
558 610
559 611 `GenericSourceSet` is the materialized source aggregation object.
560 612
561 613 It is a good fit because it can represent:
562 614
563 615 * multiple logical source sets
564 616 * aggregated source directories
565 617 * declared outputs
566 618 * lazy registration through providers
567 619
568 620 The materializer is therefore the owner of:
569 621
570 622 * creating `GenericSourceSet`
571 623 * populating its source sets
572 624 * declaring outputs
573 625 * returning a provider for later use
574 626
575 627 ---
576 628
577 629 ## How an adapter should use the model
578 630
579 631 Example:
580 632
581 633 ```java
582 634 variantSources.whenFinalized(ctx -> {
583 635 for (CompileUnit unit : ctx.getCompileUnits().getUnits()) {
584 636 var sourceSetProvider = ctx.getSourceSets().getSourceSet(unit);
585 637
586 638 var variant = unit.variant();
587 639 var layer = unit.layer();
588 640
589 641 // create compile task for this compile unit
590 642 // configure compiler options from variant semantics
591 643 // use sourceSetProvider as task input
592 644 }
593 645
594 646 for (RoleProjection projection : ctx.getRoleProjections().getProjections()) {
595 647 var units = ctx.getRoleProjections().getUnits(projection);
596 648
597 649 // aggregate outputs of included compile units
598 650 // use for publication or assembly
599 651 }
600 652 });
601 653 ```
602 654
603 655 ---
604 656
605 657 ## Why compile unit is `(variant, layer)` and not `(variant, role)` or `(variant, role, layer)`
606 658
607 659 ### Not `(variant, role)`
608 660
609 661 Because role is not a compilation boundary.
610 662
611 663 Role is a logical grouping of results.
612 664
613 665 ### Not `(variant, role, layer)`
614 666
615 667 Because role does not define the compile unit itself.
616 668
617 669 A compile unit is a unit of compilation, not a unit of publication grouping.
618 670
619 671 ### Correct interpretation
620 672
621 673 * `(variant, layer)` = compile unit
622 674 * `(variant, role)` = logical result group
623 675 * `(variant, role, layer)` = membership relation between them
624 676
625 677 This is the most coherent separation.
626 678
627 679 ---
628 680
629 681 ## Model boundaries
630 682
631 683 ### What belongs to `variants`
632 684
633 685 * declared domains: `Layer`, `Role`, `Variant`
634 686 * normalized relation `(variant, role, layer)`
635 687 * finalization lifecycle
636 688 * finalized `VariantsView`
637 689
638 690 ### What belongs to derived views
639 691
640 692 * compile units: `(variant, layer)`
641 693 * role projections: `(variant, role)`
642 694
643 695 ### What belongs to `variantSources`
644 696
645 697 * source semantics of layers
646 698 * source materialization rules
647 699 * lazy `GenericSourceSet` provisioning
648 700 * source adapter integration
649 701
650 702 ### What does not belong to `variants`
651 703
652 704 * source directories
653 705 * base paths
654 706 * output declarations
655 707 * source set layout
656 708 * task registration
657 709 * compiler-specific assumptions
658 710
659 711 ---
660 712
661 713 ## Design principles
662 714
663 715 ### 1. Keep the core model small
664 716
665 717 The core model should only contain domain facts.
666 718
667 719 ### 2. Separate domain truth from materialization
668 720
669 721 The existence of compile units comes from `VariantsView`, not from source rules.
670 722
671 723 ### 3. Treat source materialization as infrastructure
672 724
673 725 `variantSources` is an interpretation layer, not the source of truth.
674 726
675 727 ### 4. Prefer replayable finalized hooks
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
683 740 ---
684 741
685 742 ## Summary
686 743
687 744 The model is intentionally split into two layers.
688 745
689 746 ### `variants`
690 747
691 748 A closed, finalized domain model:
692 749
693 750 * `Layer`
694 751 * `Role`
695 752 * `Variant`
696 753 * `(variant, role, layer)`
697 754
698 755 ### `variantSources`
699 756
700 757 An open, source-materialization layer:
701 758
702 759 * layer source rules
703 760 * compile-unit source set materialization
704 761 * adapter-facing `GenericSourceSet` providers
705 762
706 763 ### Derived views
707 764
708 765 From the finalized variant model:
709 766
710 767 * `CompileUnitsView`: `(variant, layer)`
711 768 * `RoleProjectionsView`: `(variant, role)`
712 769
713 770 ### Operational interpretation
714 771
715 772 * `variant` defines compilation semantics
716 773 * `layer` partitions compilation
717 774 * `role` groups results by purpose
718 775
719 776 This keeps the core model stable and minimal, while allowing source handling and adapter integration to remain flexible.
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now