| @@ -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 |
|
|
|
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 |
* ` |
|
|
|
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 |
* ` |
|
|
|
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 |
|
|
|
|
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 |
## ` |
|
|
|
650 | ## `SourceSetMaterializer` | |
|
|
563 | 651 | |
|
|
564 | 652 | ### Purpose |
|
|
565 | 653 | |
|
|
566 |
` |
|
|
|
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 |
|
|
|
672 | interface SourceSetMaterializer { | |
|
|
583 | 673 | NamedDomainObjectProvider<GenericSourceSet> getSourceSet(CompileUnit unit); |
|
|
584 | 674 | } |
|
|
585 | 675 | ``` |
|
|
586 | 676 | |
|
|
587 | 677 | --- |
|
|
588 | 678 | |
|
|
589 |
## Why ` |
|
|
|
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 |
* ` |
|
|
|
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
