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