##// END OF EJS Templates
more documentation
cin -
r44:ae7ec3f08ac3 default
parent child
Show More
@@ -1,269 +1,337
1 1 # `variantSources`: selectors and precedence
2 2
3 3 `variantSources` configures source-set materialization over the compile-unit space.
4 4
5 5 A compile unit is defined as:
6 6
7 7 - `(variant, layer)`
8 8
9 9 This means:
10 10
11 11 - `variant` defines compilation semantics
12 12 - `layer` defines compilation partitioning
13 13
14 14 The `variantSources` DSL does not introduce a separate source model.
15 15 Instead, it provides configuration selectors over the existing compile-unit space.
16 16
17 17 ## Selectors
18 18
19 19 Three selectors are available:
20 20
21 21 - `variant(...)`
22 22 - `layer(...)`
23 23 - `unit(...)`
24 24
25 25 They all target the same set of compile units, but at different levels of specificity.
26 26
27 27 ### `variant(...)`
28 28
29 29 `variant(...)` applies configuration to all compile units that belong to the given variant.
30 30
31 31 Example:
32 32
33 33 ```groovy
34 34 variantSources {
35 35 variant("browser") {
36 36 declareOutputs("js", "dts")
37 37 }
38 38 }
39 39 ```
40 40
41 41 This affects all compile units of `browser`, for example:
42 42
43 43 - `(browser, main)`
44 44 - `(browser, rjs)`
45 45 - `(browser, test)`
46 46
47 47 Use this selector for variant-wide conventions.
48 48
49 49 ---
50 50
51 51 ### `layer(...)`
52 52
53 53 `layer(...)` applies configuration to all compile units that use the given layer.
54 54
55 55 Example:
56 56
57 57 ```groovy
58 58 variantSources {
59 59 layer("main") {
60 60 set("ts") {
61 61 srcDir("src/main/ts")
62 62 }
63 63 }
64 64 }
65 65 ```
66 66
67 67 This affects all compile units with layer `main`, for example:
68 68
69 69 - `(browser, main)`
70 70 - `(nodejs, main)`
71 71 - `(electron, main)`
72 72
73 73 Use this selector for cross-variant layer conventions.
74 74
75 75 ---
76 76
77 77 ### `unit(...)`
78 78
79 79 `unit(...)` applies configuration to one exact compile unit.
80 80
81 81 Example:
82 82
83 83 ```groovy
84 84 variantSources {
85 85 unit("browser", "main") {
86 86 set("resources") {
87 87 srcDir("src/browserMain/resources")
88 88 }
89 89 }
90 90 }
91 91 ```
92 92
93 93 This affects only:
94 94
95 95 - `(browser, main)`
96 96
97 97 Use this selector for the most specific adjustments.
98 98
99 99 ---
100 100
101 101 ## Precedence
102 102
103 103 For each compile unit, source-set configuration is applied in the following order:
104 104
105 105 ```text
106 106 variant < layer < unit
107 107 ```
108 108
109 109 This means:
110 110
111 111 1. `variant(...)` actions are applied first
112 112 2. `layer(...)` actions are applied next
113 113 3. `unit(...)` actions are applied last
114 114
115 115 Each next level is allowed to refine or override the previous one.
116 116
117 117 ### Within the same level
118 118
119 119 Within the same selector level, actions are applied in registration order.
120 120
121 121 For example, if two plugins both configure `layer("main")`, their actions are applied in the same order in which they were registered.
122 122
123 123 ### Scope of this guarantee
124 124
125 125 This precedence describes the normal materialization order used by the source-set
126 126 materializer.
127 127
128 128 It is stable for source sets that are configured before they are materialized.
129 129
130 130 If a selector rule is added after a target source set has already been
131 131 materialized, the behavior depends on the selected late-configuration policy.
132 132
133 133 - in `fail` mode, such late configuration is rejected
134 134 - in `warn` and `allow` modes, the late action is applied as an imperative
135 135 follow-up step
136 136 - in `warn` and `allow` modes, selector precedence is not reconstructed
137 137 retroactively for already materialized targets
138 138
139 139 ---
140 140
141 141 ## Late Configuration Policy
142 142
143 143 `variantSources` exposes a policy switch for selector rules that target already
144 144 materialized source sets:
145 145
146 146 ```groovy
147 147 variantSources {
148 148 lateConfigurationPolicy {
149 149 failOnLateConfiguration()
150 150 }
151 151 }
152 152 ```
153 153
154 154 Available modes:
155 155
156 156 - `failOnLateConfiguration()` rejects such rules
157 157 - `warnOnLateConfiguration()` allows them and emits a warning
158 158 - `allowLateConfiguration()` allows them silently
159 159
160 160 Policy rules:
161 161
162 162 - the policy must be chosen before the first selector rule is added
163 163 - selector rules here mean `variant(...)`, `layer(...)`, and `unit(...)`
164 164 - once chosen, the policy cannot be changed later
165 165 - the policy is single-valued; it is not intended to be switched during further
166 166 configuration
167 - the enforcement point is the first selector registration itself; finalization
168 of `variants` alone does not freeze this policy
167 169
168 170 Operationally:
169 171
170 172 - `fail` preserves the strict precedence contract by rejecting late mutation of
171 173 already materialized targets
172 174 - `warn` and `allow` keep compatibility with imperative late mutation
173 175 - in `warn` and `allow`, already materialized targets observe the late action in
174 176 actual registration order, not in reconstructed `variant < layer < unit`
175 177 order
176 178
177 179 ---
178 180
181 ## Compile-Unit Naming Policy
182
183 `variantSources` also exposes a policy for projecting compile units to symbolic
184 source-set names:
185
186 ```groovy
187 variantSources {
188 namingPolicy {
189 failOnNameCollision()
190 }
191 }
192 ```
193
194 Base projected name:
195
196 - `sourceSetName = variantName + capitalize(layerName)`
197
198 Example:
199
200 - `(browser, main)` -> `browserMain`
201 - `(browser, rjs)` -> `browserRjs`
202
203 Available modes:
204
205 - `failOnNameCollision()` rejects finalized compile-unit models that project the
206 same base name for different compile units
207 - `resolveNameCollision()` resolves such collisions deterministically
208
209 ### `resolveNameCollision()` semantics
210
211 Conflicting compile units are ordered canonically by:
212
213 ```text
214 (variant.name, layer.name)
215 ```
216
217 Name assignment in a conflicting group is:
218
219 - the first compile unit keeps the base name
220 - the second gets suffix `2`
221 - the third gets suffix `3`
222 - and so on
223
224 Example:
225
226 - `(foo, variantBar)` and `(fooVariant, bar)` both project to `fooVariantBar`
227 - after canonical ordering:
228 - `(foo, variantBar)` -> `fooVariantBar`
229 - `(fooVariant, bar)` -> `fooVariantBar2`
230
231 ### Fixation Point
232
233 Naming policy is fixed when the finalized `VariantSourcesContext` is created.
234
235 Operationally this means:
236
237 - policy selection must happen before `variantSources.whenFinalized(...)`
238 becomes observable
239 - compile-unit names are projected and validated before queued
240 `whenFinalized(...)` callbacks are replayed
241 - changing naming policy from inside a `whenFinalized(...)` callback is too late
242
243 ---
244
179 245 ## Example
180 246
181 247 ```groovy
182 248 variantSources {
183 249 variant("browser") {
184 250 declareOutputs("js", "dts")
185 251 }
186 252
187 253 layer("main") {
188 254 set("ts") {
189 255 srcDir("src/main/ts")
190 256 }
191 257 }
192 258
193 259 unit("browser", "main") {
194 260 set("resources") {
195 261 srcDir("src/browserMain/resources")
196 262 }
197 263 }
198 264 }
199 265 ```
200 266
201 267 For compile unit `(browser, main)` the effective configuration is built in this order:
202 268
203 269 1. `variant("browser")`
204 270 2. `layer("main")`
205 271 3. `unit("browser", "main")`
206 272
207 273 For compile unit `(browser, rjs)` the effective configuration is built in this order:
208 274
209 275 1. `variant("browser")`
210 276 2. `layer("rjs")` if present
211 277 3. `unit("browser", "rjs")` if present
212 278
213 279 For compile unit `(nodejs, main)` the effective configuration is built in this order:
214 280
215 281 1. `variant("nodejs")` if present
216 282 2. `layer("main")`
217 283 3. `unit("nodejs", "main")` if present
218 284
219 285 ---
220 286
221 287 ## Model boundary
222 288
223 289 These selectors do not define compile units.
224 290 Compile units are derived from finalized `variants`.
225 291
226 292 `variantSources` only configures how source sets are materialized for those units.
227 293
228 294 This means:
229 295
230 296 - `variants` is the source of truth for compile-unit existence
231 297 - `variantSources` is the source of truth for compile-unit source-set configuration
232 298
233 299 ---
234 300
235 301 ## Operational semantics
236 302
237 303 The `variantSources` API is exposed through a finalized context.
238 304
239 305 Conceptually, configuration is registered against finalized model objects, while DSL sugar may still use names for convenience.
240 306
241 307 Internally, selector-based configuration is accumulated and later applied by the
242 308 source-set materializer when a `GenericSourceSet` is created for a compile unit.
243 309
244 310 This guarantees that:
245 311
246 312 - selector precedence is stable before materialization
247 313 - registration order is preserved
248 314 - configuration of already materialized targets is governed by the selected
249 315 late-configuration policy
250 316 - adapters do not need to depend on raw Gradle lifecycle timing
251 317
252 318 ---
253 319
254 320 ## Summary
255 321
256 322 - compile unit space is `(variant, layer)`
257 323 - `variant(...)`, `layer(...)`, and `unit(...)` are selectors over that space
258 324 - precedence is:
259 325
260 326 ```text
261 327 variant < layer < unit
262 328 ```
263 329
264 330 - registration order is preserved within the same selector level
265 331 - already materialized targets are handled by `lateConfigurationPolicy(...)`
266 332 - the late-configuration policy must be selected before the first selector rule
267 333 and cannot be changed later
334 - compile-unit naming is governed by `namingPolicy(...)`
335 - by default, name collisions fail fast during finalized context creation
268 336 - `variants` defines what exists
269 337 - `variantSources` defines how those compile units are materialized as source sets
@@ -1,17 +1,21
1 1 package org.implab.gradle.variants.sources;
2 2
3 3 import org.gradle.api.NamedDomainObjectProvider;
4 4 import org.implab.gradle.common.sources.GenericSourceSet;
5 5
6 6 /**
7 * Materializes symbolic source set names into actual GenericSourceSet
7 * Materializes symbolic source set names into actual {@link GenericSourceSet}
8 8 * instances.
9 *
10 * <p>Symbolic names are assigned from the finalized compile-unit model using
11 * the selected
12 * {@link VariantSourcesExtension#namingPolicy(org.gradle.api.Action)}.
9 13 */
10 14 public interface SourceSetMaterializer {
11 15 /**
12 16 * Returns a lazy provider for the source set corresponding to the compile unit.
13 17 *
14 * The provider is stable and cached per compile unit.
18 * <p>The provider is stable and cached per compile unit.
15 19 */
16 20 NamedDomainObjectProvider<GenericSourceSet> getSourceSet(CompileUnit unit);
17 } No newline at end of file
21 }
@@ -1,71 +1,73
1 1 package org.implab.gradle.variants.sources;
2 2
3 3 import org.gradle.api.Action;
4 4 import org.implab.gradle.common.sources.GenericSourceSet;
5 5 import org.implab.gradle.variants.core.Layer;
6 6 import org.implab.gradle.variants.core.Variant;
7 7 import org.implab.gradle.variants.core.VariantsView;
8 8
9 9 /**
10 10 * Registry of symbolic source set names produced by sources projection.
11 11 *
12 * Identity in this registry is the GenericSourceSet name.
12 * <p>Identity in this registry is the {@link GenericSourceSet} name assigned
13 * by the finalized
14 * {@link VariantSourcesExtension#namingPolicy(org.gradle.api.Action)}.
13 15 */
14 16 public interface VariantSourcesContext {
15 17
16 18 /**
17 19 * Finalized core model.
18 20 */
19 21 VariantsView getVariants();
20 22
21 23 /**
22 24 * Derived compile-side view.
23 25 */
24 26 CompileUnitsView getCompileUnits();
25 27
26 28 /**
27 29 * Derived role-side view.
28 30 */
29 31 RoleProjectionsView getRoleProjections();
30 32
31 33 /**
32 34 * Lazy source set provider service.
33 35 */
34 36 SourceSetMaterializer getSourceSets();
35 37
36 38 /**
37 39 * Configures all GenericSourceSets produced from the given layer.
38 40 *
39 41 * The action is applied:
40 42 * - to already materialized source sets of this layer
41 43 * - to all future source sets of this layer
42 44 *
43 45 * <p>For future source sets, selector precedence and registration order are
44 46 * preserved by the materializer.
45 47 *
46 48 * <p>For already materialized source sets, behavior is governed by
47 49 * {@link VariantSourcesExtension#lateConfigurationPolicy(org.gradle.api.Action)}.
48 50 * In warn/allow modes the action is applied as a late imperative step and does
49 51 * not retroactively restore selector precedence.
50 52 */
51 53 void configureLayer(Layer layer, Action<? super GenericSourceSet> action);
52 54
53 55 /**
54 56 * Configures all GenericSourceSets produced from the given variant.
55 57 *
56 58 * <p>Late application semantics for already materialized source sets are
57 59 * governed by
58 60 * {@link VariantSourcesExtension#lateConfigurationPolicy(org.gradle.api.Action)}.
59 61 */
60 62 void configureVariant(Variant variant, Action<? super GenericSourceSet> action);
61 63
62 64 /**
63 65 * Configures the GenericSourceSet produced from the given compile unit.
64 66 *
65 67 * <p>Late application semantics for already materialized source sets are
66 68 * governed by
67 69 * {@link VariantSourcesExtension#lateConfigurationPolicy(org.gradle.api.Action)}.
68 70 */
69 71 void configureUnit(CompileUnit unit, Action<? super GenericSourceSet> action);
70 72
71 73 }
@@ -1,105 +1,164
1 1 package org.implab.gradle.variants.sources;
2 2
3 3 import org.eclipse.jdt.annotation.NonNullByDefault;
4 4 import org.gradle.api.Action;
5 5 import org.implab.gradle.common.core.lang.Closures;
6 6 import org.implab.gradle.common.sources.GenericSourceSet;
7 7 import groovy.lang.Closure;
8 8
9 9 @NonNullByDefault
10 10 public interface VariantSourcesExtension {
11 11
12 12 /**
13 13 * Selects how selector rules behave when they target an already materialized
14 14 * {@link GenericSourceSet}.
15 15 *
16 16 * <p>This policy is single-valued:
17 17 * <ul>
18 18 * <li>it must be selected before the first selector rule is registered via
19 19 * {@link #variant(String, Action)}, {@link #layer(String, Action)} or
20 20 * {@link #unit(String, String, Action)};</li>
21 21 * <li>once selected, it cannot be changed later;</li>
22 22 * <li>the policy controls both diagnostics and late-application semantics.</li>
23 23 * </ul>
24 *
25 * <p>If not selected explicitly, the default is
26 * {@link LateConfigurationPolicySpec#failOnLateConfiguration()}.
24 27 */
25 28 void lateConfigurationPolicy(Action<? super LateConfigurationPolicySpec> action);
26 29
27 30 default void lateConfigurationPolicy(Closure<?> closure) {
28 31 lateConfigurationPolicy(Closures.action(closure));
29 32 }
30 33
34 /**
35 * Selects how compile-unit name collisions are handled when the finalized
36 * source context is created.
37 *
38 * <p>This policy is single-valued:
39 * <ul>
40 * <li>it must be selected before the finalized
41 * {@link VariantSourcesContext} becomes observable through
42 * {@link #whenFinalized(Action)};</li>
43 * <li>once the context is being created, the policy is fixed and cannot be
44 * changed later;</li>
45 * <li>the policy governs validation of compile-unit names produced by the
46 * source-set materializer.</li>
47 * </ul>
48 *
49 * <p>If not selected explicitly, the default is
50 * {@link NamingPolicySpec#failOnNameCollision()}.
51 */
31 52 void namingPolicy(Action<? super NamingPolicySpec> action);
32 53
33 54 default void namingPolicy(Closure<?> closure) {
34 55 namingPolicy(Closures.action(closure));
35 56 }
36 57
58 /**
59 * Registers a selector rule for all compile units of the given layer.
60 *
61 * <p>Registering the first selector rule fixes the selected
62 * {@link #lateConfigurationPolicy(Action)} for the remaining extension
63 * lifecycle.
64 */
37 65 void layer(String layerName, Action<? super GenericSourceSet> action);
38 66
39 67 default void layer(String layerName, Closure<?> closure) {
40 68 layer(layerName, Closures.action(closure));
41 69 }
42 70
71 /**
72 * Registers a selector rule for all compile units of the given variant.
73 *
74 * <p>Registering the first selector rule fixes the selected
75 * {@link #lateConfigurationPolicy(Action)} for the remaining extension
76 * lifecycle.
77 */
43 78 void variant(String variantName, Action<? super GenericSourceSet> action);
44 79
45 80 default void variant(String variantName, Closure<?> closure) {
46 81 variant(variantName, Closures.action(closure));
47 82 }
48 83
84 /**
85 * Registers a selector rule for one exact compile unit.
86 *
87 * <p>Registering the first selector rule fixes the selected
88 * {@link #lateConfigurationPolicy(Action)} for the remaining extension
89 * lifecycle.
90 */
49 91 void unit(String variantName, String layerName, Action<? super GenericSourceSet> action);
50 92
51 93 /**
52 94 * Invoked when finalized variants-derived source context becomes available.
53 95 *
54 96 * Replayable:
55 97 * <ul>
56 98 * <li>if called before variants finalization, action is queued
57 99 * <li>if called after variants finalization, action is invoked immediately
58 100 * </ul>
101 *
102 * <p>By the time this callback becomes observable, compile-unit naming
103 * policy has already been fixed and symbolic source-set names for finalized
104 * compile units are determined.
59 105 */
60 106 void whenFinalized(Action<? super VariantSourcesContext> action);
61 107
62 108 default void whenFinalized(Closure<?> closure) {
63 109 whenFinalized(Closures.action(closure));
64 110 }
65 111
66 112
67 113 /**
68 114 * Imperative selector for the late-configuration mode.
69 115 *
70 116 * <p>Exactly one mode is expected to be chosen for the extension lifecycle.
71 117 */
72 118 interface LateConfigurationPolicySpec {
73 119 /**
74 120 * Rejects selector registration if it targets any already materialized
75 121 * source set.
76 122 */
77 123 void failOnLateConfiguration();
78 124
79 125 /**
80 126 * Allows late selector registration, but emits a warning when it targets an
81 127 * already materialized source set.
82 128 *
83 129 * <p>For such targets, selector precedence is not re-established
84 130 * retroactively. The action is applied as a late imperative step, after the
85 131 * state already produced at the materialization moment.
86 132 */
87 133 void warnOnLateConfiguration();
88 134
89 135 /**
90 136 * Allows late selector registration without a warning when it targets an
91 137 * already materialized source set.
92 138 *
93 139 * <p>For such targets, selector precedence is not re-established
94 140 * retroactively. The action is applied as a late imperative step, after the
95 141 * state already produced at the materialization moment.
96 142 */
97 143 void allowLateConfiguration();
98 144 }
99 145
100 146 interface NamingPolicySpec {
147 /**
148 * Rejects finalized compile-unit models that project the same source-set
149 * name for different compile units.
150 */
101 151 void failOnNameCollision();
102 152
153 /**
154 * Resolves name collisions deterministically for the finalized
155 * compile-unit model.
156 *
157 * <p>Conflicting compile units are ordered canonically by
158 * {@code (variant.name, layer.name)}. The first unit keeps the base
159 * projected name, and each next unit receives a numeric suffix
160 * ({@code 2}, {@code 3}, ...).
161 */
103 162 void resolveNameCollision();
104 163 }
105 164 }
@@ -1,776 +1,872
1 1 # Variants and Variant Sources
2 2
3 3 ## Overview
4 4
5 5 This document describes a two-layer model for build variants:
6 6
7 7 - `variants` defines the **core domain model**
8 8 - `variantSources` defines **source materialization semantics** for that model
9 9
10 10 The main goal is to keep the core model small, explicit, and stable, while allowing source-related behavior to remain flexible and adapter-friendly.
11 11
12 12 The model is intentionally split into:
13 13
14 14 1. a **closed, finalized domain model**
15 15 2. an **open, runtime-oriented source materialization model**
16 16
17 17 This separation is important because compilation, source aggregation, publication, and adapter-specific behavior do not belong to the same abstraction layer.
18 18
19 19 ---
20 20
21 21 ## Core idea
22 22
23 23 The `variants` model is based on three independent domains:
24 24
25 25 - `Layer`
26 26 - `Role`
27 27 - `Variant`
28 28
29 29 A finalized `VariantsView` contains the normalized relation:
30 30
31 31 - `(variant, role, layer)`
32 32
33 33 This relation is the source of truth.
34 34
35 35 Everything else is derived from it.
36 36
37 37 ---
38 38
39 39 ## `variants`: the core domain model
40 40
41 41 ### Purpose
42 42
43 43 `variants` describes:
44 44
45 45 - what layers exist
46 46 - what roles exist
47 47 - what variants exist
48 48 - which `(variant, role, layer)` combinations are valid
49 49
50 50 It does **not** describe:
51 51
52 52 - source directories
53 53 - source roots
54 54 - source set materialization
55 55 - compilation tasks
56 56 - publication mechanics
57 57 - source set inheritance
58 58 - layer merge behavior for a concrete toolchain
59 59
60 60 Those concerns are intentionally outside the core model.
61 61
62 62 ---
63 63
64 64 ## Core DSL example
65 65
66 66 ```groovy
67 67 variants {
68 68 layers {
69 69 main()
70 70 test()
71 71 generated()
72 72 rjs()
73 73 cjs()
74 74 }
75 75
76 76 roles {
77 77 production()
78 78 test()
79 79 tool()
80 80 }
81 81
82 82 variant("browser") {
83 83 role("production") {
84 84 layers("main", "generated", "rjs")
85 85 }
86 86 role("test") {
87 87 layers("main", "test", "generated", "rjs")
88 88 }
89 89 }
90 90
91 91 variant("nodejs") {
92 92 role("production") {
93 93 layers("main", "generated", "cjs")
94 94 }
95 95 role("test") {
96 96 layers("main", "test", "generated", "cjs")
97 97 }
98 98 role("tool") {
99 99 layers("main", "generated", "cjs")
100 100 }
101 101 }
102 102 }
103 103 ```
104 104
105 105 ### Interpretation
106 106
107 107 This example means:
108 108
109 109 * `browser` production uses `main`, `generated`, `rjs`
110 110 * `browser` test uses `main`, `test`, `generated`, `rjs`
111 111 * `nodejs` production uses `main`, `generated`, `cjs`
112 112 * `nodejs` test uses `main`, `test`, `generated`, `cjs`
113 113 * `nodejs` tool uses `main`, `generated`, `cjs`
114 114
115 115 The model is purely declarative.
116 116
117 117 ---
118 118
119 119 ## Identity and references
120 120
121 121 `Layer`, `Role`, and `Variant` are identity objects.
122 122
123 123 They exist as declared domain values.
124 124
125 125 References between model elements are symbolic:
126 126
127 127 * layers are referenced by layer name
128 128 * roles are referenced by role name
129 129 * variants are referenced by variant name
130 130
131 131 This is intentional.
132 132
133 133 The core model is declarative, not navigation-oriented.
134 134
135 135 It is acceptable for aggregates to hold symbolic references to foreign domain values, as long as identity is clearly defined and validated later.
136 136
137 137 ---
138 138
139 139 ## Finalization
140 140
141 141 The `variants` model is finalized once.
142 142
143 143 Finalization is an internal lifecycle transition. It is typically triggered privately, for example from `afterEvaluate`, but that mechanism is not part of the public API contract.
144 144
145 145 The public contract is:
146 146
147 147 * `variants.whenFinalized(...)`
148 148
149 149 This callback is **replayable**:
150 150
151 151 * if called before finalization, the action is queued
152 152 * if called after finalization, the action is invoked immediately
153 153
154 154 The callback receives a finalized, read-only view of the model.
155 155
156 156 Example:
157 157
158 158 ```java
159 159 variants.whenFinalized(view -> {
160 160 // use finalized VariantsView here
161 161 });
162 162 ```
163 163
164 164 ---
165 165
166 166 ## `VariantsView`
167 167
168 168 `VariantsView` is the finalized representation of the core model.
169 169
170 170 It contains:
171 171
172 172 * all declared `Layer`
173 173 * all declared `Role`
174 174 * all declared `Variant`
175 175 * all normalized entries `(variant, role, layer)`
176 176
177 177 Conceptually:
178 178
179 179 ```java
180 180 interface VariantsView {
181 181 Set<Layer> getLayers();
182 182 Set<Role> getRoles();
183 183 Set<Variant> getVariants();
184 184 Set<VariantRoleLayer> getEntries();
185 185 }
186 186 ```
187 187
188 188 Where:
189 189
190 190 ```java
191 191 record VariantRoleLayer(Variant variant, Role role, Layer layer) {}
192 192 ```
193 193
194 194 This view is:
195 195
196 196 * immutable
197 197 * normalized
198 198 * validated
199 199 * independent from DSL internals
200 200
201 201 ---
202 202
203 203 ## Derived views
204 204
205 205 Two important views can be derived from `VariantsView`:
206 206
207 207 * `CompileUnitsView`
208 208 * `RoleProjectionsView`
209 209
210 210 These views are not part of the raw core model itself, but they are naturally derived from it.
211 211
212 212 ---
213 213
214 214 ## `CompileUnitsView`
215 215
216 216 ### Purpose
217 217
218 218 A compile unit is defined as:
219 219
220 220 * `(variant, layer)`
221 221
222 222 This is based on the following rationale:
223 223
224 224 * `variant` defines compilation semantics
225 225 * `layer` partitions a variant into separate compilation units
226 226 * `role` is not a compilation boundary
227 227
228 228 This is especially useful for toolchains such as TypeScript, where compilation is often more practical or more correct per layer than for the whole variant at once.
229 229
230 230 ### Example
231 231
232 232 From:
233 233
234 234 * `(browser, production, main)`
235 235 * `(browser, production, rjs)`
236 236 * `(browser, test, main)`
237 237 * `(browser, test, test)`
238 238 * `(browser, test, rjs)`
239 239
240 240 we derive compile units:
241 241
242 242 * `(browser, main)`
243 243 * `(browser, rjs)`
244 244 * `(browser, test)`
245 245
246 246 ### Conceptual API
247 247
248 248 ```java
249 249 interface CompileUnitsView {
250 250 Set<CompileUnit> getUnits();
251 251 Set<CompileUnit> getUnitsForVariant(Variant variant);
252 252 boolean contains(Variant variant, Layer layer);
253 253 Set<Role> getRoles(CompileUnit unit);
254 254 }
255 255
256 256 record CompileUnit(Variant variant, Layer layer) {}
257 257 ```
258 258
259 259 ### Meaning
260 260
261 261 `CompileUnitsView` answers:
262 262
263 263 * what can be compiled
264 264 * how a variant is partitioned into compile units
265 265 * which logical roles include a given compile unit
266 266
267 267 ---
268 268
269 269 ## `RoleProjectionsView`
270 270
271 271 ### Purpose
272 272
273 273 A role projection is defined as:
274 274
275 275 * `(variant, role)`
276 276
277 277 This is based on the following rationale:
278 278
279 279 * `role` is not about compilation
280 280 * `role` groups compile units by purpose
281 281 * roles are more closely related to publication, aggregation, assembly, or result grouping
282 282
283 283 ### Example
284 284
285 285 For `browser`:
286 286
287 287 * `production` includes compile units:
288 288
289 289 * `(browser, main)`
290 290 * `(browser, rjs)`
291 291
292 292 * `test` includes compile units:
293 293
294 294 * `(browser, main)`
295 295 * `(browser, test)`
296 296 * `(browser, rjs)`
297 297
298 298 ### Conceptual API
299 299
300 300 ```java
301 301 interface RoleProjectionsView {
302 302 Set<RoleProjection> getProjections();
303 303 Set<RoleProjection> getProjectionsForVariant(Variant variant);
304 304 Set<RoleProjection> getProjectionsForRole(Role role);
305 305 Set<CompileUnit> getUnits(RoleProjection projection);
306 306 }
307 307
308 308 record RoleProjection(Variant variant, Role role) {}
309 309 ```
310 310
311 311 ### Meaning
312 312
313 313 `RoleProjectionsView` answers:
314 314
315 315 * how compile units are grouped by purpose
316 316 * what belongs to `production`, `test`, `tool`, etc.
317 317 * what should be aggregated or published together
318 318
319 319 ---
320 320
321 321 ## Why `CompileUnitsView` and `RoleProjectionsView` are not part of `VariantsView`
322 322
323 323 `VariantsView` is intentionally minimal.
324 324
325 325 It expresses the domain relation:
326 326
327 327 * `(variant, role, layer)`
328 328
329 329 `CompileUnitsView` and `RoleProjectionsView` are **derived interpretations** of that relation.
330 330
331 331 They are natural and useful, but they are still interpretations:
332 332
333 333 * `CompileUnit = (variant, layer)`
334 334 * `RoleProjection = (variant, role)`
335 335
336 336 This is why they are better treated as derived views rather than direct core model primitives.
337 337
338 338 ---
339 339
340 340 ## `variantSources`: source semantics for layers
341 341
342 342 ### Purpose
343 343
344 344 `variantSources` does **not** define variants.
345 345
346 346 It defines how a declared `Layer` contributes sources.
347 347
348 348 In other words:
349 349
350 350 * `variants` defines **what exists**
351 351 * `variantSources` defines **how layers become source inputs**
352 352
353 353 This distinction is important.
354 354
355 355 `variantSources` does not own the variant model. It interprets it.
356 356
357 357 ---
358 358
359 359 ## Main idea
360 360
361 361 A layer source rule describes the source contribution of a layer.
362 362
363 363 This is independent of any concrete variant or role.
364 364
365 365 Conceptually:
366 366
367 367 * `Layer -> source contribution rule`
368 368
369 369 Examples of source contribution semantics:
370 370
371 371 * base directory
372 372 * source directories
373 373 * logical source kinds (`ts`, `js`, `resources`)
374 374 * declared outputs (`js`, `dts`, `resources`)
375 375
376 376 ---
377 377
378 378 ## `variantSources` DSL example
379 379
380 380 ```groovy
381 381 variantSources {
382 382 layerRule("main") {
383 383 from("src/main")
384 384
385 385 set("ts") {
386 386 srcDir("ts")
387 387 }
388 388 set("js") {
389 389 srcDir("js")
390 390 }
391 391 set("resources") {
392 392 srcDir("resources")
393 393 }
394 394
395 395 outputs("js", "dts", "resources")
396 396 }
397 397
398 398 layerRule("test") {
399 399 from("src/test")
400 400
401 401 set("ts") {
402 402 srcDir("ts")
403 403 }
404 404 set("resources") {
405 405 srcDir("resources")
406 406 }
407 407
408 408 outputs("js", "dts", "resources")
409 409 }
410 410
411 411 layerRule("rjs") {
412 412 from("src/rjs")
413 413
414 414 set("ts") {
415 415 srcDir("ts")
416 416 }
417 417
418 418 outputs("js", "dts")
419 419 }
420 420
421 421 layerRule("cjs") {
422 422 from("src/cjs")
423 423
424 424 set("ts") {
425 425 srcDir("ts")
426 426 }
427 427
428 428 outputs("js", "dts")
429 429 }
430 430 }
431 431 ```
432 432
433 433 ### Interpretation
434 434
435 435 This means:
436 436
437 437 * `main` contributes `ts`, `js`, and `resources`
438 438 * `test` contributes `ts` and `resources`
439 439 * `rjs` contributes `ts`
440 440 * `cjs` contributes `ts`
441 441
442 442 These are layer rules only.
443 443
444 444 They do not yet say which variant consumes them.
445 445
446 446 ---
447 447
448 448 ## Why `variantSources` remains open
449 449
450 450 Unlike `variants`, `variantSources` does not need to be closed in the same way.
451 451
452 452 Reasons:
453 453
454 454 * the DSL is internal to source materialization
455 455 * the source of truth for unit existence is already finalized in `VariantsView`
456 * `GenericSourceSetMaterializer` returns `NamedDomainObjectProvider<GenericSourceSet>`
456 * `SourceSetMaterializer` returns `NamedDomainObjectProvider<GenericSourceSet>`
457 457 * adapters may need to refine source-related behavior after `variants` is finalized
458 458
459 459 Therefore:
460 460
461 461 * `variants` is finalized
462 462 * `variantSources` may remain open
463 463
464 464 This is not a contradiction.
465 465
466 466 It reflects the difference between:
467 467
468 468 * a closed domain model
469 469 * an open infrastructure/materialization model
470 470
471 This openness is still constrained by explicit policy fixation points:
472
473 * late-configuration policy is fixed when the first selector rule is registered
474 * naming policy is fixed when the finalized `VariantSourcesContext` is created
475
471 476 ---
472 477
473 478 ## Late configuration policy
474 479
475 480 Openness of `variantSources` does not mean that late configuration is
476 481 semantically neutral.
477 482
478 483 Selector rules may be added after the finalized context becomes available, but
479 484 their behavior against already materialized `GenericSourceSet` objects must be
480 485 controlled explicitly.
481 486
482 487 Conceptually, `variantSources` exposes a policy choice such as:
483 488
484 489 ```groovy
485 490 variantSources {
486 491 lateConfigurationPolicy {
487 492 failOnLateConfiguration()
488 493 }
489 494 }
490 495 ```
491 496
492 497 Available modes are:
493 498
494 499 * `failOnLateConfiguration()`
495 500 * `warnOnLateConfiguration()`
496 501 * `allowLateConfiguration()`
497 502
498 503 Meaning:
499 504
500 505 * `fail` rejects selector rules that target already materialized source sets
501 506 * `warn` allows them but emits a warning
502 507 * `allow` allows them silently
503 508
504 509 This policy is intentionally modeled as an imperative choice, not as a mutable
505 510 property:
506 511
507 512 * it must be chosen before the first selector rule is added
508 513 * selector rules here mean `variant(...)`, `layer(...)`, and `unit(...)`
509 514 * once chosen, it cannot be changed later
510 515 * it controls runtime behavior, not just a stored value
516 * the enforcement point is the first selector registration itself, not variants
517 finalization in isolation
511 518
512 519 For source sets configured before materialization, selector precedence remains:
513 520
514 521 ```text
515 522 variant < layer < unit
516 523 ```
517 524
518 525 For already materialized source sets in `warn` and `allow` modes:
519 526
520 527 * the late action is applied as an imperative follow-up step
521 528 * selector precedence is not reconstructed retroactively
522 529 * actual observation order is the order in which late actions are registered
523 530
524 531 ---
525 532
533 ## Compile-unit naming policy
534
535 Source-set naming is treated as a separate policy concern from selector
536 registration.
537
538 Conceptually, `variantSources` exposes:
539
540 ```groovy
541 variantSources {
542 namingPolicy {
543 failOnNameCollision()
544 }
545 }
546 ```
547
548 The base projected name of a compile unit is:
549
550 ```text
551 variantName + capitalize(layerName)
552 ```
553
554 Examples:
555
556 * `(browser, main)` -> `browserMain`
557 * `(browser, rjs)` -> `browserRjs`
558
559 Available modes are:
560
561 * `failOnNameCollision()` - reject finalized compile-unit models that project
562 the same source-set name for different compile units
563 * `resolveNameCollision()` - resolve such conflicts deterministically
564
565 ### `resolveNameCollision()` semantics
566
567 Conflicting compile units are ordered canonically by:
568
569 ```text
570 (variant.name, layer.name)
571 ```
572
573 Within one conflicting group:
574
575 * the first compile unit keeps the base name
576 * the second gets suffix `2`
577 * the third gets suffix `3`
578 * and so on
579
580 For example, if:
581
582 * `(foo, variantBar)` projects to `fooVariantBar`
583 * `(fooVariant, bar)` also projects to `fooVariantBar`
584
585 then canonical ordering yields:
586
587 * `(foo, variantBar)` -> `fooVariantBar`
588 * `(fooVariant, bar)` -> `fooVariantBar2`
589
590 ### Fixation point
591
592 Naming policy is fixed when the finalized `VariantSourcesContext` is created.
593
594 Operationally this means:
595
596 * naming policy must be selected before `variantSources.whenFinalized(...)`
597 becomes observable
598 * compile-unit names are projected and validated before queued
599 `whenFinalized(...)` callbacks are replayed
600 * changing naming policy from inside a `whenFinalized(...)` callback is too late
601
602 This differs intentionally from late-configuration policy:
603
604 * late-configuration policy is fixed by the first selector rule
605 * naming policy is fixed by finalized-context creation
606
607 ---
608
526 609 ## `VariantSourcesContext`
527 610
528 611 `variantSources.whenFinalized(...)` remains useful, but not because `variantSources` itself is frozen.
529 612
530 613 Its purpose is to provide access to a finalized context derived from `variants`.
531 614
532 615 This context contains:
533 616
534 617 * `CompileUnitsView`
535 618 * `RoleProjectionsView`
536 * `GenericSourceSetMaterializer`
619 * `SourceSetMaterializer`
620
621 By the time the context becomes observable:
622
623 * compile-unit naming policy is already fixed
624 * symbolic source-set names for finalized compile units are already determined
537 625
538 626 Conceptually:
539 627
540 628 ```java
541 629 interface VariantSourcesContext {
542 630 CompileUnitsView getCompileUnits();
543 631 RoleProjectionsView getRoleProjections();
544 GenericSourceSetMaterializer getSourceSets();
632 SourceSetMaterializer getSourceSets();
545 633 }
546 634 ```
547 635
548 636 This callback is also replayable.
549 637
550 638 Example:
551 639
552 640 ```java
553 641 variantSources.whenFinalized(ctx -> {
554 642 var units = ctx.getCompileUnits();
555 643 var roles = ctx.getRoleProjections();
556 644 var sourceSets = ctx.getSourceSets();
557 645 });
558 646 ```
559 647
560 648 ---
561 649
562 ## `GenericSourceSetMaterializer`
650 ## `SourceSetMaterializer`
563 651
564 652 ### Purpose
565 653
566 `GenericSourceSetMaterializer` is the official source of truth for materialized source sets.
654 `SourceSetMaterializer` is the official source of truth for materialized source sets.
567 655
568 656 It is responsible for:
569 657
570 658 * lazy creation of `GenericSourceSet`
659 * projecting finalized compile units to symbolic source-set names
660 * validating or resolving name collisions according to naming policy
571 661 * applying `layerRule`
572 662 * connecting a compile unit to a source set provider
573 663 * exposing source sets to adapters
574 664
575 665 This is the correct place to apply `layerRule`.
576 666
577 667 Adapters should not apply layer rules themselves.
578 668
579 669 ### Conceptual API
580 670
581 671 ```java
582 interface GenericSourceSetMaterializer {
672 interface SourceSetMaterializer {
583 673 NamedDomainObjectProvider<GenericSourceSet> getSourceSet(CompileUnit unit);
584 674 }
585 675 ```
586 676
587 677 ---
588 678
589 ## Why `GenericSourceSetMaterializer` should own `layerRule` application
679 ## Why `SourceSetMaterializer` should own `layerRule` application
590 680
591 681 If adapters applied `layerRule` directly, responsibility would leak across multiple layers:
592 682
593 683 * one component would know compile units
594 684 * another would know source semantics
595 685 * another would know how to configure `GenericSourceSet`
596 686
597 687 This would make the model harder to reason about.
598 688
599 689 Instead:
600 690
601 691 * `layerRule` is DSL/spec-level
602 * `GenericSourceSetMaterializer` is execution/materialization-level
692 * `SourceSetMaterializer` is execution/materialization-level
603 693 * adapters are consumption-level
604 694
605 695 This gives a much cleaner separation.
606 696
607 697 ---
608 698
609 699 ## `GenericSourceSet` as materialization target
610 700
611 701 `GenericSourceSet` is the materialized source aggregation object.
612 702
613 703 It is a good fit because it can represent:
614 704
615 705 * multiple logical source sets
616 706 * aggregated source directories
617 707 * declared outputs
618 708 * lazy registration through providers
619 709
620 710 The materializer is therefore the owner of:
621 711
622 712 * creating `GenericSourceSet`
623 713 * populating its source sets
624 714 * declaring outputs
625 715 * returning a provider for later use
626 716
627 717 ---
628 718
629 719 ## How an adapter should use the model
630 720
631 721 Example:
632 722
633 723 ```java
634 724 variantSources.whenFinalized(ctx -> {
635 725 for (CompileUnit unit : ctx.getCompileUnits().getUnits()) {
636 726 var sourceSetProvider = ctx.getSourceSets().getSourceSet(unit);
637 727
638 728 var variant = unit.variant();
639 729 var layer = unit.layer();
640 730
641 731 // create compile task for this compile unit
642 732 // configure compiler options from variant semantics
643 733 // use sourceSetProvider as task input
644 734 }
645 735
646 736 for (RoleProjection projection : ctx.getRoleProjections().getProjections()) {
647 737 var units = ctx.getRoleProjections().getUnits(projection);
648 738
649 739 // aggregate outputs of included compile units
650 740 // use for publication or assembly
651 741 }
652 742 });
653 743 ```
654 744
655 745 ---
656 746
657 747 ## Why compile unit is `(variant, layer)` and not `(variant, role)` or `(variant, role, layer)`
658 748
659 749 ### Not `(variant, role)`
660 750
661 751 Because role is not a compilation boundary.
662 752
663 753 Role is a logical grouping of results.
664 754
665 755 ### Not `(variant, role, layer)`
666 756
667 757 Because role does not define the compile unit itself.
668 758
669 759 A compile unit is a unit of compilation, not a unit of publication grouping.
670 760
671 761 ### Correct interpretation
672 762
673 763 * `(variant, layer)` = compile unit
674 764 * `(variant, role)` = logical result group
675 765 * `(variant, role, layer)` = membership relation between them
676 766
677 767 This is the most coherent separation.
678 768
679 769 ---
680 770
681 771 ## Model boundaries
682 772
683 773 ### What belongs to `variants`
684 774
685 775 * declared domains: `Layer`, `Role`, `Variant`
686 776 * normalized relation `(variant, role, layer)`
687 777 * finalization lifecycle
688 778 * finalized `VariantsView`
689 779
690 780 ### What belongs to derived views
691 781
692 782 * compile units: `(variant, layer)`
693 783 * role projections: `(variant, role)`
694 784
695 785 ### What belongs to `variantSources`
696 786
697 787 * source semantics of layers
698 788 * source materialization rules
699 789 * lazy `GenericSourceSet` provisioning
700 790 * source adapter integration
701 791
702 792 ### What does not belong to `variants`
703 793
704 794 * source directories
705 795 * base paths
706 796 * output declarations
707 797 * source set layout
708 798 * task registration
709 799 * compiler-specific assumptions
710 800
711 801 ---
712 802
713 803 ## Design principles
714 804
715 805 ### 1. Keep the core model small
716 806
717 807 The core model should only contain domain facts.
718 808
719 809 ### 2. Separate domain truth from materialization
720 810
721 811 The existence of compile units comes from `VariantsView`, not from source rules.
722 812
723 813 ### 3. Treat source materialization as infrastructure
724 814
725 815 `variantSources` is an interpretation layer, not the source of truth.
726 816
727 817 ### 4. Prefer replayable finalized hooks
728 818
729 819 Adapters should not depend on raw Gradle lifecycle callbacks such as `afterEvaluate`.
730 820
731 821 ### 5. Make late behavior explicit
732 822
733 823 Late configuration after materialization is a policy decision, not an implicit
734 824 guarantee.
735 825
736 826 ### 6. Keep heavy runtime objects behind providers
737 827
738 828 Materialized `GenericSourceSet` objects should remain behind a lazy API.
739 829
830 ### 7. Make name-collision behavior explicit
831
832 Compile-unit naming must be governed by an explicit policy, not by incidental
833 materialization order.
834
740 835 ---
741 836
742 837 ## Summary
743 838
744 839 The model is intentionally split into two layers.
745 840
746 841 ### `variants`
747 842
748 843 A closed, finalized domain model:
749 844
750 845 * `Layer`
751 846 * `Role`
752 847 * `Variant`
753 848 * `(variant, role, layer)`
754 849
755 850 ### `variantSources`
756 851
757 852 An open, source-materialization layer:
758 853
759 854 * layer source rules
760 855 * compile-unit source set materialization
856 * compile-unit naming policy
761 857 * adapter-facing `GenericSourceSet` providers
762 858
763 859 ### Derived views
764 860
765 861 From the finalized variant model:
766 862
767 863 * `CompileUnitsView`: `(variant, layer)`
768 864 * `RoleProjectionsView`: `(variant, role)`
769 865
770 866 ### Operational interpretation
771 867
772 868 * `variant` defines compilation semantics
773 869 * `layer` partitions compilation
774 870 * `role` groups results by purpose
775 871
776 872 This keeps the core model stable and minimal, while allowing source handling and adapter integration to remain flexible.
General Comments 0
You need to be logged in to leave comments. Login now