| @@ -0,0 +1,91 | |||
|
|
1 | package org.implab.gradle.variants.sources.internal; | |
|
|
2 | ||
|
|
3 | import java.text.MessageFormat; | |
|
|
4 | import java.util.Collection; | |
|
|
5 | import java.util.Comparator; | |
|
|
6 | import java.util.HashMap; | |
|
|
7 | import java.util.HashSet; | |
|
|
8 | import java.util.Map; | |
|
|
9 | import java.util.Optional; | |
|
|
10 | import java.util.Set; | |
|
|
11 | import java.util.function.Function; | |
|
|
12 | import java.util.stream.Collectors; | |
|
|
13 | ||
|
|
14 | import org.gradle.api.InvalidUserDataException; | |
|
|
15 | import org.implab.gradle.common.core.lang.Strings; | |
|
|
16 | import org.implab.gradle.variants.sources.CompileUnit; | |
|
|
17 | ||
|
|
18 | public interface CompileUnitNamer { | |
|
|
19 | ||
|
|
20 | String resolveName(CompileUnit unit); | |
|
|
21 | ||
|
|
22 | public static Builder builder() { | |
|
|
23 | return new Builder(); | |
|
|
24 | } | |
|
|
25 | ||
|
|
26 | static class Builder { | |
|
|
27 | private final Set<CompileUnit> units = new HashSet<>(); | |
|
|
28 | private NameCollisionPolicy nameCollisionPolicy = NameCollisionPolicy.FAIL; | |
|
|
29 | ||
|
|
30 | private Builder() { | |
|
|
31 | } | |
|
|
32 | ||
|
|
33 | public Builder addUnits(Collection<CompileUnit> other) { | |
|
|
34 | units.addAll(other); | |
|
|
35 | return this; | |
|
|
36 | } | |
|
|
37 | ||
|
|
38 | public Builder nameCollisionPolicy(NameCollisionPolicy policy) { | |
|
|
39 | nameCollisionPolicy = policy; | |
|
|
40 | return this; | |
|
|
41 | } | |
|
|
42 | ||
|
|
43 | public CompileUnitNamer build() { | |
|
|
44 | Map<String, Integer> seen = new HashMap<>(); | |
|
|
45 | ||
|
|
46 | if (nameCollisionPolicy == NameCollisionPolicy.FAIL) { | |
|
|
47 | var collisions = units.stream() | |
|
|
48 | .collect(Collectors.groupingBy(this::projectName)) | |
|
|
49 | .entrySet().stream() | |
|
|
50 | .filter(pair -> pair.getValue().size() > 1) | |
|
|
51 | .map(pair -> MessageFormat.format( | |
|
|
52 | "({0}: {1})", | |
|
|
53 | pair.getKey(), | |
|
|
54 | pair.getValue().stream() | |
|
|
55 | .map(Object::toString) | |
|
|
56 | .collect(Collectors.joining(",")))) | |
|
|
57 | .collect(Collectors.joining(",")); | |
|
|
58 | if (!collisions.isEmpty()) | |
|
|
59 | throw new InvalidUserDataException( | |
|
|
60 | "The same source set names are produced by different compile units: " + collisions); | |
|
|
61 | } | |
|
|
62 | ||
|
|
63 | var unitNames = units.stream() | |
|
|
64 | .sorted(Comparator | |
|
|
65 | .comparing((CompileUnit unit) -> unit.variant().getName()) | |
|
|
66 | .thenComparing(unit -> unit.layer().getName())) | |
|
|
67 | .collect(Collectors.toUnmodifiableMap(Function.identity(), unit -> { | |
|
|
68 | var baseName = projectName(unit); | |
|
|
69 | ||
|
|
70 | var c = seen.compute(baseName, (key, count) -> count == null ? 1 : count + 1); | |
|
|
71 | return c == 1 ? baseName : baseName + String.valueOf(c); | |
|
|
72 | })); | |
|
|
73 | ||
|
|
74 | return new CompileUnitNamer() { | |
|
|
75 | ||
|
|
76 | @Override | |
|
|
77 | public String resolveName(CompileUnit unit) { | |
|
|
78 | return Optional.ofNullable(unitNames.get(unit)).orElseThrow( | |
|
|
79 | () -> new IllegalArgumentException(MessageFormat.format( | |
|
|
80 | "Compile unit {0} doesn't have an associated name", | |
|
|
81 | unit))); | |
|
|
82 | } | |
|
|
83 | ||
|
|
84 | }; | |
|
|
85 | } | |
|
|
86 | ||
|
|
87 | private String projectName(CompileUnit unit) { | |
|
|
88 | return unit.variant().getName() + Strings.capitalize(unit.layer().getName()); | |
|
|
89 | } | |
|
|
90 | } | |
|
|
91 | } | |
| @@ -0,0 +1,36 | |||
|
|
1 | package org.implab.gradle.variants.sources.internal; | |
|
|
2 | ||
|
|
3 | import org.implab.gradle.variants.sources.VariantSourcesExtension.NamingPolicySpec; | |
|
|
4 | ||
|
|
5 | public class DefaultCompileUnitNamingPolicy implements NamingPolicySpec { | |
|
|
6 | private NameCollisionPolicy policy = NameCollisionPolicy.FAIL; | |
|
|
7 | private boolean policyApplied = false; | |
|
|
8 | ||
|
|
9 | public NameCollisionPolicy policy() { | |
|
|
10 | finalizePolicy(); | |
|
|
11 | return policy; | |
|
|
12 | } | |
|
|
13 | ||
|
|
14 | public void finalizePolicy() { | |
|
|
15 | policyApplied = true; | |
|
|
16 | } | |
|
|
17 | ||
|
|
18 | @Override | |
|
|
19 | public void failOnNameCollision() { | |
|
|
20 | assertApplyOnce(); | |
|
|
21 | policy = NameCollisionPolicy.FAIL; | |
|
|
22 | } | |
|
|
23 | ||
|
|
24 | @Override | |
|
|
25 | public void resolveNameCollision() { | |
|
|
26 | assertApplyOnce(); | |
|
|
27 | policy = NameCollisionPolicy.RESOLVE; | |
|
|
28 | } | |
|
|
29 | ||
|
|
30 | private void assertApplyOnce() { | |
|
|
31 | if (policyApplied) | |
|
|
32 | throw new IllegalStateException("Naming policy already applied"); | |
|
|
33 | policyApplied = true; | |
|
|
34 | ||
|
|
35 | } | |
|
|
36 | } | |
| @@ -0,0 +1,42 | |||
|
|
1 | package org.implab.gradle.variants.sources.internal; | |
|
|
2 | ||
|
|
3 | import org.implab.gradle.variants.sources.VariantSourcesExtension.LateConfigurationPolicySpec; | |
|
|
4 | ||
|
|
5 | public class DefaultLateConfigurationPolicySpec implements LateConfigurationPolicySpec { | |
|
|
6 | ||
|
|
7 | private LateConfigurationMode policyMode = LateConfigurationMode.FAIL; | |
|
|
8 | private boolean policyApplied = false; | |
|
|
9 | ||
|
|
10 | public LateConfigurationMode mode() { | |
|
|
11 | return policyMode; | |
|
|
12 | } | |
|
|
13 | ||
|
|
14 | public void finalizePolicy() { | |
|
|
15 | policyApplied = true; | |
|
|
16 | } | |
|
|
17 | ||
|
|
18 | @Override | |
|
|
19 | public void failOnLateConfiguration() { | |
|
|
20 | assertApplyOnce(); | |
|
|
21 | policyMode = LateConfigurationMode.FAIL; | |
|
|
22 | } | |
|
|
23 | ||
|
|
24 | @Override | |
|
|
25 | public void warnOnLateConfiguration() { | |
|
|
26 | assertApplyOnce(); | |
|
|
27 | policyMode = LateConfigurationMode.WARN; | |
|
|
28 | } | |
|
|
29 | ||
|
|
30 | @Override | |
|
|
31 | public void allowLateConfiguration() { | |
|
|
32 | assertApplyOnce(); | |
|
|
33 | policyMode = LateConfigurationMode.APPLY; | |
|
|
34 | } | |
|
|
35 | ||
|
|
36 | private void assertApplyOnce() { | |
|
|
37 | if (policyApplied) | |
|
|
38 | throw new IllegalStateException("Lazy configuration policy already applied"); | |
|
|
39 | policyApplied = true; | |
|
|
40 | } | |
|
|
41 | ||
|
|
42 | } | |
| @@ -0,0 +1,92 | |||
|
|
1 | package org.implab.gradle.variants.sources.internal; | |
|
|
2 | ||
|
|
3 | import java.util.HashMap; | |
|
|
4 | import java.util.Map; | |
|
|
5 | import org.gradle.api.Action; | |
|
|
6 | import org.gradle.api.NamedDomainObjectProvider; | |
|
|
7 | import org.implab.gradle.common.sources.GenericSourceSet; | |
|
|
8 | import org.implab.gradle.variants.core.Layer; | |
|
|
9 | import org.implab.gradle.variants.core.Variant; | |
|
|
10 | import org.implab.gradle.variants.core.VariantsView; | |
|
|
11 | import org.implab.gradle.variants.sources.CompileUnit; | |
|
|
12 | import org.implab.gradle.variants.sources.CompileUnitsView; | |
|
|
13 | import org.implab.gradle.variants.sources.RoleProjectionsView; | |
|
|
14 | import org.implab.gradle.variants.sources.SourceSetMaterializer; | |
|
|
15 | import org.implab.gradle.variants.sources.VariantSourcesContext; | |
|
|
16 | ||
|
|
17 | public class DefaultVariantSourcesContext implements VariantSourcesContext { | |
|
|
18 | private final VariantsView variantsView; | |
|
|
19 | private final CompileUnitsView compileUnitsView; | |
|
|
20 | private final RoleProjectionsView roleProjectionsView; | |
|
|
21 | private final SourceSetMaterializer sourceSetMaterializer; | |
|
|
22 | private final SourceSetRegistry sourceSetRegistry; | |
|
|
23 | private final CompileUnitNamer compileUnitNamer; | |
|
|
24 | private final SourceSetConfigurationRegistry sourceSetConfigurationRegistry; | |
|
|
25 | ||
|
|
26 | public DefaultVariantSourcesContext( | |
|
|
27 | VariantsView variantsView, | |
|
|
28 | CompileUnitsView compileUnitsView, | |
|
|
29 | RoleProjectionsView roleProjectionsView, | |
|
|
30 | CompileUnitNamer compileUnitNamer, | |
|
|
31 | SourceSetRegistry sourceSetRegistry, | |
|
|
32 | SourceSetConfigurationRegistry sourceSetConfigurationRegistry) { | |
|
|
33 | this.variantsView = variantsView; | |
|
|
34 | this.compileUnitNamer = compileUnitNamer; | |
|
|
35 | this.compileUnitsView = compileUnitsView; | |
|
|
36 | this.roleProjectionsView = roleProjectionsView; | |
|
|
37 | this.sourceSetRegistry = sourceSetRegistry; | |
|
|
38 | this.sourceSetConfigurationRegistry = sourceSetConfigurationRegistry; | |
|
|
39 | ||
|
|
40 | sourceSetMaterializer = new LocalSourceSetMaterializer(); | |
|
|
41 | } | |
|
|
42 | ||
|
|
43 | @Override | |
|
|
44 | public VariantsView getVariants() { | |
|
|
45 | return variantsView; | |
|
|
46 | } | |
|
|
47 | ||
|
|
48 | @Override | |
|
|
49 | public CompileUnitsView getCompileUnits() { | |
|
|
50 | return compileUnitsView; | |
|
|
51 | } | |
|
|
52 | ||
|
|
53 | @Override | |
|
|
54 | public RoleProjectionsView getRoleProjections() { | |
|
|
55 | return roleProjectionsView; | |
|
|
56 | } | |
|
|
57 | ||
|
|
58 | @Override | |
|
|
59 | public SourceSetMaterializer getSourceSets() { | |
|
|
60 | return sourceSetMaterializer; | |
|
|
61 | } | |
|
|
62 | ||
|
|
63 | @Override | |
|
|
64 | public void configureLayer(Layer layer, Action<? super GenericSourceSet> action) { | |
|
|
65 | sourceSetConfigurationRegistry.addLayerAction(layer, action); | |
|
|
66 | } | |
|
|
67 | ||
|
|
68 | @Override | |
|
|
69 | public void configureVariant(Variant variant, Action<? super GenericSourceSet> action) { | |
|
|
70 | sourceSetConfigurationRegistry.addVariantAction(variant, action); | |
|
|
71 | } | |
|
|
72 | ||
|
|
73 | @Override | |
|
|
74 | public void configureUnit(CompileUnit unit, Action<? super GenericSourceSet> action) { | |
|
|
75 | sourceSetConfigurationRegistry.addCompileUnitAction(unit, action); | |
|
|
76 | } | |
|
|
77 | ||
|
|
78 | class LocalSourceSetMaterializer implements SourceSetMaterializer { | |
|
|
79 | private final Map<CompileUnit, NamedDomainObjectProvider<GenericSourceSet>> registeredSources = new HashMap<>(); | |
|
|
80 | ||
|
|
81 | @Override | |
|
|
82 | public NamedDomainObjectProvider<GenericSourceSet> getSourceSet(CompileUnit unit) { | |
|
|
83 | return registeredSources.computeIfAbsent(unit, k -> { | |
|
|
84 | var sourcesName = compileUnitNamer.resolveName(unit); | |
|
|
85 | sourceSetRegistry.whenMaterialized(sourcesName, | |
|
|
86 | sourceSet -> sourceSetConfigurationRegistry.applyConfiguration(unit, sourceSet)); | |
|
|
87 | return sourceSetRegistry.sourceSets().register(sourcesName); | |
|
|
88 | }); | |
|
|
89 | } | |
|
|
90 | ||
|
|
91 | } | |
|
|
92 | } | |
| @@ -0,0 +1,7 | |||
|
|
1 | package org.implab.gradle.variants.sources.internal; | |
|
|
2 | ||
|
|
3 | public enum LateConfigurationMode { | |
|
|
4 | FAIL, | |
|
|
5 | WARN, | |
|
|
6 | APPLY | |
|
|
7 | } | |
| @@ -0,0 +1,6 | |||
|
|
1 | package org.implab.gradle.variants.sources.internal; | |
|
|
2 | ||
|
|
3 | public enum NameCollisionPolicy { | |
|
|
4 | FAIL, | |
|
|
5 | RESOLVE | |
|
|
6 | } | |
| @@ -0,0 +1,46 | |||
|
|
1 | package org.implab.gradle.variants.sources.internal; | |
|
|
2 | ||
|
|
3 | import java.util.HashMap; | |
|
|
4 | import java.util.Map; | |
|
|
5 | import java.util.function.Consumer; | |
|
|
6 | ||
|
|
7 | import org.gradle.api.NamedDomainObjectContainer; | |
|
|
8 | import org.gradle.api.model.ObjectFactory; | |
|
|
9 | import org.implab.gradle.common.core.lang.Deferred; | |
|
|
10 | import org.implab.gradle.common.sources.GenericSourceSet; | |
|
|
11 | ||
|
|
12 | public class SourceSetRegistry { | |
|
|
13 | private final Map<String, Deferred<GenericSourceSet>> materialized = new HashMap<>(); | |
|
|
14 | private final NamedDomainObjectContainer<GenericSourceSet> sourceSets; | |
|
|
15 | private final ObjectFactory objectFactory; | |
|
|
16 | ||
|
|
17 | public SourceSetRegistry(ObjectFactory objectFactory) { | |
|
|
18 | this.objectFactory = objectFactory; | |
|
|
19 | this.sourceSets = objectFactory.domainObjectContainer(GenericSourceSet.class, this::createSourceSet); | |
|
|
20 | } | |
|
|
21 | ||
|
|
22 | void forEachMaterialized(Consumer<? super GenericSourceSet> consumer) { | |
|
|
23 | materialized.values().stream() | |
|
|
24 | .filter(Deferred::resolved) | |
|
|
25 | .map(Deferred::value) | |
|
|
26 | .forEach(consumer); | |
|
|
27 | } | |
|
|
28 | ||
|
|
29 | public NamedDomainObjectContainer<GenericSourceSet> sourceSets() { | |
|
|
30 | return sourceSets; | |
|
|
31 | } | |
|
|
32 | ||
|
|
33 | public void whenMaterialized(String name, Consumer<? super GenericSourceSet> consumer) { | |
|
|
34 | materialized(name).whenResolved(consumer); | |
|
|
35 | } | |
|
|
36 | ||
|
|
37 | private GenericSourceSet createSourceSet(String name) { | |
|
|
38 | var sourceSet = objectFactory.newInstance(GenericSourceSet.class, name); | |
|
|
39 | materialized(name).resolve(sourceSet); | |
|
|
40 | return sourceSet; | |
|
|
41 | } | |
|
|
42 | ||
|
|
43 | private Deferred<GenericSourceSet> materialized(String name) { | |
|
|
44 | return materialized.computeIfAbsent(name, k -> new Deferred<>()); | |
|
|
45 | } | |
|
|
46 | } | |
| @@ -1,4 +1,7 | |||
|
|
1 | 1 | syntax: glob |
|
|
2 | 2 | .gradle/ |
|
|
3 | .codex/ | |
|
|
3 | 4 | common/build/ |
|
|
4 | 5 | common/bin/ |
|
|
6 | variants/build/ | |
|
|
7 | variants/bin/ | |
| @@ -1,42 +1,52 | |||
|
|
1 | 1 | # AGENTS.md |
|
|
2 | 2 | |
|
|
3 | 3 | ## Проектные договоренности |
|
|
4 | 4 | |
|
|
5 | 5 | ### Публичное API библиотек |
|
|
6 | 6 | |
|
|
7 | 7 | - Предпочтителен `non-null` подход. |
|
|
8 | 8 | - Там, где значение живет в Gradle Provider API, возвращается `Provider<T>` (не `null`). |
|
|
9 | 9 | - Там, где lookup синхронный, возвращается `Optional<T>` (не `null`). |
|
|
10 | 10 | - `find*` рассматривается как синоним legacy `get*` (поиск без `fail-fast`). |
|
|
11 | 11 | - `require*` это `find*` + `fail-fast` с понятной ошибкой в месте вызова. |
|
|
12 | 12 | - Для нового API предпочтительны формы `find/require`; новые `get*` по возможности не добавлять. |
|
|
13 | 13 | |
|
|
14 | ### Документация | |
|
|
15 | ||
|
|
16 | - документирование кода должно быть на английском языке | |
|
|
17 | - к публичному API | |
|
|
18 | - описание должно отражать назначение, где используется и какое влияние оказывает на остальные части программы | |
|
|
19 | - давать небольшое описание концепции, а также краткие примеры | |
|
|
20 | - к приватному API достаточно давать краткую справку о назначении и использовании | |
|
|
21 | - реализацию алгоритмов в коде сопровождать комментариями с пояснениями, тривиальные операции пояснять не требуется. | |
|
|
22 | - документация должна формироваться согласно требованиям по форматированию типа javadoc, jsdoc и т.п., в зависимости от используемых в проекте языках и инструментах. | |
|
|
23 | ||
|
|
14 | 24 | ## Identity-first modeling |
|
|
15 | 25 | |
|
|
16 | 26 | Prefer an **identity-first** split between: |
|
|
17 | 27 | |
|
|
18 | 28 | - **identity objects** used for discovery and selection |
|
|
19 | 29 | - **stateful/materialized objects** obtained through separate API calls |
|
|
20 | 30 | |
|
|
21 | 31 | ### Rules |
|
|
22 | 32 | |
|
|
23 | 33 | - Objects intended for replayable observation (`all(...)`, similar collection APIs) should be **identity objects**. |
|
|
24 | 34 | - Identity objects must be: |
|
|
25 | 35 | - cheap to create |
|
|
26 | 36 | - effectively immutable |
|
|
27 | 37 | - limited to identity and cheap selection metadata |
|
|
28 | 38 | - Heavy, computed, provider-based, or runtime-bound state must be resolved separately. |
|
|
29 | 39 | - Aggregate content should be accessed through dedicated lookup/materialization APIs. |
|
|
30 | 40 | - Eager observation of identity is acceptable. |
|
|
31 | 41 | - Eager observation of computed state is not. |
|
|
32 | 42 | |
|
|
33 | 43 | ### Do not |
|
|
34 | 44 | |
|
|
35 | 45 | - Do not store heavy computed state inside identity objects. |
|
|
36 | 46 | - Do not store runtime references to foreign domains inside identity objects. |
|
|
37 | 47 | - Do not use custom events when replayable identity registries plus on-demand lookup are sufficient. |
|
|
38 | 48 | |
|
|
39 | 49 | ### Rule of thumb |
|
|
40 | 50 | |
|
|
41 | 51 | **Identity objects contain selection metadata. |
|
|
42 | 52 | Aggregate content is obtained separately, on demand.** |
| @@ -1,29 +1,39 | |||
|
|
1 | 1 | package org.implab.gradle.common.core.lang; |
|
|
2 | 2 | |
|
|
3 | 3 | import java.util.LinkedList; |
|
|
4 | 4 | import java.util.List; |
|
|
5 | 5 | import java.util.function.Consumer; |
|
|
6 | 6 | |
|
|
7 | 7 | public final class Deferred<T> { |
|
|
8 | private final List<Consumer<T>> listeners = new LinkedList<>(); | |
|
|
8 | private final List<Consumer<? super T>> listeners = new LinkedList<>(); | |
|
|
9 | 9 | private T value; |
|
|
10 | 10 | private boolean resolved = false; |
|
|
11 | 11 | |
|
|
12 | public boolean resolved() { | |
|
|
13 | return resolved; | |
|
|
14 | } | |
|
|
15 | ||
|
|
16 | public T value() { | |
|
|
17 | if (!resolved) | |
|
|
18 | throw new IllegalStateException(); | |
|
|
19 | return value; | |
|
|
20 | } | |
|
|
21 | ||
|
|
12 | 22 | public void resolve(T value) { |
|
|
13 | 23 | if (resolved) { |
|
|
14 | 24 | throw new IllegalStateException("Already resolved"); |
|
|
15 | 25 | } |
|
|
16 | 26 | this.value = value; |
|
|
17 | 27 | this.resolved = true; |
|
|
18 | 28 | listeners.forEach(listener -> listener.accept(value)); |
|
|
19 | 29 | listeners.clear(); |
|
|
20 | 30 | } |
|
|
21 | 31 | |
|
|
22 | public void whenResolved(Consumer<T> listener) { | |
|
|
32 | public void whenResolved(Consumer<? super T> listener) { | |
|
|
23 | 33 | if (resolved) { |
|
|
24 | 34 | listener.accept(value); |
|
|
25 | 35 | } else { |
|
|
26 | 36 | listeners.add(listener); |
|
|
27 | 37 | } |
|
|
28 | 38 | } |
|
|
29 | 39 | } |
| @@ -1,210 +1,269 | |||
|
|
1 | 1 | # `variantSources`: selectors and precedence |
|
|
2 | 2 | |
|
|
3 | 3 | `variantSources` configures source-set materialization over the compile-unit space. |
|
|
4 | 4 | |
|
|
5 | 5 | A compile unit is defined as: |
|
|
6 | 6 | |
|
|
7 | 7 | - `(variant, layer)` |
|
|
8 | 8 | |
|
|
9 | 9 | This means: |
|
|
10 | 10 | |
|
|
11 | 11 | - `variant` defines compilation semantics |
|
|
12 | 12 | - `layer` defines compilation partitioning |
|
|
13 | 13 | |
|
|
14 | 14 | The `variantSources` DSL does not introduce a separate source model. |
|
|
15 | 15 | Instead, it provides configuration selectors over the existing compile-unit space. |
|
|
16 | 16 | |
|
|
17 | 17 | ## Selectors |
|
|
18 | 18 | |
|
|
19 | 19 | Three selectors are available: |
|
|
20 | 20 | |
|
|
21 | 21 | - `variant(...)` |
|
|
22 | 22 | - `layer(...)` |
|
|
23 | 23 | - `unit(...)` |
|
|
24 | 24 | |
|
|
25 | 25 | They all target the same set of compile units, but at different levels of specificity. |
|
|
26 | 26 | |
|
|
27 | 27 | ### `variant(...)` |
|
|
28 | 28 | |
|
|
29 | 29 | `variant(...)` applies configuration to all compile units that belong to the given variant. |
|
|
30 | 30 | |
|
|
31 | 31 | Example: |
|
|
32 | 32 | |
|
|
33 | 33 | ```groovy |
|
|
34 | 34 | variantSources { |
|
|
35 | 35 | variant("browser") { |
|
|
36 | 36 | declareOutputs("js", "dts") |
|
|
37 | 37 | } |
|
|
38 | 38 | } |
|
|
39 | 39 | ``` |
|
|
40 | 40 | |
|
|
41 | 41 | This affects all compile units of `browser`, for example: |
|
|
42 | 42 | |
|
|
43 | 43 | - `(browser, main)` |
|
|
44 | 44 | - `(browser, rjs)` |
|
|
45 | 45 | - `(browser, test)` |
|
|
46 | 46 | |
|
|
47 | 47 | Use this selector for variant-wide conventions. |
|
|
48 | 48 | |
|
|
49 | 49 | --- |
|
|
50 | 50 | |
|
|
51 | 51 | ### `layer(...)` |
|
|
52 | 52 | |
|
|
53 | 53 | `layer(...)` applies configuration to all compile units that use the given layer. |
|
|
54 | 54 | |
|
|
55 | 55 | Example: |
|
|
56 | 56 | |
|
|
57 | 57 | ```groovy |
|
|
58 | 58 | variantSources { |
|
|
59 | 59 | layer("main") { |
|
|
60 | 60 | set("ts") { |
|
|
61 | 61 | srcDir("src/main/ts") |
|
|
62 | 62 | } |
|
|
63 | 63 | } |
|
|
64 | 64 | } |
|
|
65 | 65 | ``` |
|
|
66 | 66 | |
|
|
67 | 67 | This affects all compile units with layer `main`, for example: |
|
|
68 | 68 | |
|
|
69 | 69 | - `(browser, main)` |
|
|
70 | 70 | - `(nodejs, main)` |
|
|
71 | 71 | - `(electron, main)` |
|
|
72 | 72 | |
|
|
73 | 73 | Use this selector for cross-variant layer conventions. |
|
|
74 | 74 | |
|
|
75 | 75 | --- |
|
|
76 | 76 | |
|
|
77 | 77 | ### `unit(...)` |
|
|
78 | 78 | |
|
|
79 | 79 | `unit(...)` applies configuration to one exact compile unit. |
|
|
80 | 80 | |
|
|
81 | 81 | Example: |
|
|
82 | 82 | |
|
|
83 | 83 | ```groovy |
|
|
84 | 84 | variantSources { |
|
|
85 | 85 | unit("browser", "main") { |
|
|
86 | 86 | set("resources") { |
|
|
87 | 87 | srcDir("src/browserMain/resources") |
|
|
88 | 88 | } |
|
|
89 | 89 | } |
|
|
90 | 90 | } |
|
|
91 | 91 | ``` |
|
|
92 | 92 | |
|
|
93 | 93 | This affects only: |
|
|
94 | 94 | |
|
|
95 | 95 | - `(browser, main)` |
|
|
96 | 96 | |
|
|
97 | 97 | Use this selector for the most specific adjustments. |
|
|
98 | 98 | |
|
|
99 | 99 | --- |
|
|
100 | 100 | |
|
|
101 | 101 | ## Precedence |
|
|
102 | 102 | |
|
|
103 | 103 | For each compile unit, source-set configuration is applied in the following order: |
|
|
104 | 104 | |
|
|
105 | 105 | ```text |
|
|
106 | 106 | variant < layer < unit |
|
|
107 | 107 | ``` |
|
|
108 | 108 | |
|
|
109 | 109 | This means: |
|
|
110 | 110 | |
|
|
111 | 111 | 1. `variant(...)` actions are applied first |
|
|
112 | 112 | 2. `layer(...)` actions are applied next |
|
|
113 | 113 | 3. `unit(...)` actions are applied last |
|
|
114 | 114 | |
|
|
115 | 115 | Each next level is allowed to refine or override the previous one. |
|
|
116 | 116 | |
|
|
117 | 117 | ### Within the same level |
|
|
118 | 118 | |
|
|
119 | 119 | Within the same selector level, actions are applied in registration order. |
|
|
120 | 120 | |
|
|
121 | 121 | For example, if two plugins both configure `layer("main")`, their actions are applied in the same order in which they were registered. |
|
|
122 | 122 | |
|
|
123 | ### Scope of this guarantee | |
|
|
124 | ||
|
|
125 | This precedence describes the normal materialization order used by the source-set | |
|
|
126 | materializer. | |
|
|
127 | ||
|
|
128 | It is stable for source sets that are configured before they are materialized. | |
|
|
129 | ||
|
|
130 | If a selector rule is added after a target source set has already been | |
|
|
131 | materialized, the behavior depends on the selected late-configuration policy. | |
|
|
132 | ||
|
|
133 | - in `fail` mode, such late configuration is rejected | |
|
|
134 | - in `warn` and `allow` modes, the late action is applied as an imperative | |
|
|
135 | follow-up step | |
|
|
136 | - in `warn` and `allow` modes, selector precedence is not reconstructed | |
|
|
137 | retroactively for already materialized targets | |
|
|
138 | ||
|
|
139 | --- | |
|
|
140 | ||
|
|
141 | ## Late Configuration Policy | |
|
|
142 | ||
|
|
143 | `variantSources` exposes a policy switch for selector rules that target already | |
|
|
144 | materialized source sets: | |
|
|
145 | ||
|
|
146 | ```groovy | |
|
|
147 | variantSources { | |
|
|
148 | lateConfigurationPolicy { | |
|
|
149 | failOnLateConfiguration() | |
|
|
150 | } | |
|
|
151 | } | |
|
|
152 | ``` | |
|
|
153 | ||
|
|
154 | Available modes: | |
|
|
155 | ||
|
|
156 | - `failOnLateConfiguration()` rejects such rules | |
|
|
157 | - `warnOnLateConfiguration()` allows them and emits a warning | |
|
|
158 | - `allowLateConfiguration()` allows them silently | |
|
|
159 | ||
|
|
160 | Policy rules: | |
|
|
161 | ||
|
|
162 | - the policy must be chosen before the first selector rule is added | |
|
|
163 | - selector rules here mean `variant(...)`, `layer(...)`, and `unit(...)` | |
|
|
164 | - once chosen, the policy cannot be changed later | |
|
|
165 | - the policy is single-valued; it is not intended to be switched during further | |
|
|
166 | configuration | |
|
|
167 | ||
|
|
168 | Operationally: | |
|
|
169 | ||
|
|
170 | - `fail` preserves the strict precedence contract by rejecting late mutation of | |
|
|
171 | already materialized targets | |
|
|
172 | - `warn` and `allow` keep compatibility with imperative late mutation | |
|
|
173 | - in `warn` and `allow`, already materialized targets observe the late action in | |
|
|
174 | actual registration order, not in reconstructed `variant < layer < unit` | |
|
|
175 | order | |
|
|
176 | ||
|
|
123 | 177 | --- |
|
|
124 | 178 | |
|
|
125 | 179 | ## Example |
|
|
126 | 180 | |
|
|
127 | 181 | ```groovy |
|
|
128 | 182 | variantSources { |
|
|
129 | 183 | variant("browser") { |
|
|
130 | 184 | declareOutputs("js", "dts") |
|
|
131 | 185 | } |
|
|
132 | 186 | |
|
|
133 | 187 | layer("main") { |
|
|
134 | 188 | set("ts") { |
|
|
135 | 189 | srcDir("src/main/ts") |
|
|
136 | 190 | } |
|
|
137 | 191 | } |
|
|
138 | 192 | |
|
|
139 | 193 | unit("browser", "main") { |
|
|
140 | 194 | set("resources") { |
|
|
141 | 195 | srcDir("src/browserMain/resources") |
|
|
142 | 196 | } |
|
|
143 | 197 | } |
|
|
144 | 198 | } |
|
|
145 | 199 | ``` |
|
|
146 | 200 | |
|
|
147 | 201 | For compile unit `(browser, main)` the effective configuration is built in this order: |
|
|
148 | 202 | |
|
|
149 | 203 | 1. `variant("browser")` |
|
|
150 | 204 | 2. `layer("main")` |
|
|
151 | 205 | 3. `unit("browser", "main")` |
|
|
152 | 206 | |
|
|
153 | 207 | For compile unit `(browser, rjs)` the effective configuration is built in this order: |
|
|
154 | 208 | |
|
|
155 | 209 | 1. `variant("browser")` |
|
|
156 | 210 | 2. `layer("rjs")` if present |
|
|
157 | 211 | 3. `unit("browser", "rjs")` if present |
|
|
158 | 212 | |
|
|
159 | 213 | For compile unit `(nodejs, main)` the effective configuration is built in this order: |
|
|
160 | 214 | |
|
|
161 | 215 | 1. `variant("nodejs")` if present |
|
|
162 | 216 | 2. `layer("main")` |
|
|
163 | 217 | 3. `unit("nodejs", "main")` if present |
|
|
164 | 218 | |
|
|
165 | 219 | --- |
|
|
166 | 220 | |
|
|
167 | 221 | ## Model boundary |
|
|
168 | 222 | |
|
|
169 | 223 | These selectors do not define compile units. |
|
|
170 | 224 | Compile units are derived from finalized `variants`. |
|
|
171 | 225 | |
|
|
172 | 226 | `variantSources` only configures how source sets are materialized for those units. |
|
|
173 | 227 | |
|
|
174 | 228 | This means: |
|
|
175 | 229 | |
|
|
176 | 230 | - `variants` is the source of truth for compile-unit existence |
|
|
177 | 231 | - `variantSources` is the source of truth for compile-unit source-set configuration |
|
|
178 | 232 | |
|
|
179 | 233 | --- |
|
|
180 | 234 | |
|
|
181 | 235 | ## Operational semantics |
|
|
182 | 236 | |
|
|
183 | 237 | The `variantSources` API is exposed through a finalized context. |
|
|
184 | 238 | |
|
|
185 | 239 | Conceptually, configuration is registered against finalized model objects, while DSL sugar may still use names for convenience. |
|
|
186 | 240 | |
|
|
187 |
Internally, selector-based configuration is accumulated and later applied by the |
|
|
|
241 | Internally, selector-based configuration is accumulated and later applied by the | |
|
|
242 | source-set materializer when a `GenericSourceSet` is created for a compile unit. | |
|
|
188 | 243 | |
|
|
189 | 244 | This guarantees that: |
|
|
190 | 245 | |
|
|
191 | - selector precedence is stable | |
|
|
246 | - selector precedence is stable before materialization | |
|
|
192 | 247 | - registration order is preserved |
|
|
193 | - configuration does not depend on the materialization moment | |
|
|
248 | - configuration of already materialized targets is governed by the selected | |
|
|
249 | late-configuration policy | |
|
|
194 | 250 | - adapters do not need to depend on raw Gradle lifecycle timing |
|
|
195 | 251 | |
|
|
196 | 252 | --- |
|
|
197 | 253 | |
|
|
198 | 254 | ## Summary |
|
|
199 | 255 | |
|
|
200 | 256 | - compile unit space is `(variant, layer)` |
|
|
201 | 257 | - `variant(...)`, `layer(...)`, and `unit(...)` are selectors over that space |
|
|
202 | 258 | - precedence is: |
|
|
203 | 259 | |
|
|
204 | 260 | ```text |
|
|
205 | 261 | variant < layer < unit |
|
|
206 | 262 | ``` |
|
|
207 | 263 | |
|
|
208 | 264 | - registration order is preserved within the same selector level |
|
|
265 | - already materialized targets are handled by `lateConfigurationPolicy(...)` | |
|
|
266 | - the late-configuration policy must be selected before the first selector rule | |
|
|
267 | and cannot be changed later | |
|
|
209 | 268 | - `variants` defines what exists |
|
|
210 | 269 | - `variantSources` defines how those compile units are materialized as source sets |
| @@ -1,102 +1,156 | |||
|
|
1 | 1 | package org.implab.gradle.variants; |
|
|
2 | 2 | |
|
|
3 | import java.util.Objects; | |
|
|
4 | import java.util.function.Predicate; | |
|
|
5 | ||
|
|
3 | 6 | import org.eclipse.jdt.annotation.NonNullByDefault; |
|
|
4 | 7 | import org.gradle.api.Action; |
|
|
8 | import org.gradle.api.InvalidUserDataException; | |
|
|
9 | import org.gradle.api.Named; | |
|
|
5 | 10 | import org.gradle.api.Plugin; |
|
|
6 | 11 | import org.gradle.api.Project; |
|
|
7 | 12 | import org.implab.gradle.common.core.lang.Deferred; |
|
|
13 | import org.implab.gradle.common.core.lang.Strings; | |
|
|
8 | 14 | import org.implab.gradle.common.sources.GenericSourceSet; |
|
|
9 | import org.implab.gradle.common.sources.SourcesPlugin; | |
|
|
10 | 15 | import org.implab.gradle.variants.core.Layer; |
|
|
11 | 16 | import org.implab.gradle.variants.core.Variant; |
|
|
12 | 17 | import org.implab.gradle.variants.core.VariantsExtension; |
|
|
13 | 18 | import org.implab.gradle.variants.core.VariantsView; |
|
|
14 | 19 | import org.implab.gradle.variants.sources.CompileUnit; |
|
|
15 | 20 | import org.implab.gradle.variants.sources.CompileUnitsView; |
|
|
16 | 21 | import org.implab.gradle.variants.sources.RoleProjectionsView; |
|
|
17 | import org.implab.gradle.variants.sources.SourceSetMaterializer; | |
|
|
18 | 22 | import org.implab.gradle.variants.sources.VariantSourcesContext; |
|
|
23 | import org.implab.gradle.variants.sources.VariantSourcesExtension; | |
|
|
24 | import org.implab.gradle.variants.sources.internal.CompileUnitNamer; | |
|
|
25 | import org.implab.gradle.variants.sources.internal.DefaultCompileUnitNamingPolicy; | |
|
|
26 | import org.implab.gradle.variants.sources.internal.DefaultLateConfigurationPolicySpec; | |
|
|
27 | import org.implab.gradle.variants.sources.internal.DefaultVariantSourcesContext; | |
|
|
28 | import org.implab.gradle.variants.sources.internal.SourceSetConfigurationRegistry; | |
|
|
29 | import org.implab.gradle.variants.sources.internal.SourceSetRegistry; | |
|
|
19 | 30 | |
|
|
20 | 31 | @NonNullByDefault |
|
|
21 | 32 | public abstract class VariantSourcesPlugin implements Plugin<Project> { |
|
|
33 | public static final String VARIANT_SOURCES_EXTENSION = "variantSources"; | |
|
|
34 | ||
|
|
22 | 35 | @Override |
|
|
23 | 36 | public void apply(Project target) { |
|
|
37 | var extensions = target.getExtensions(); | |
|
|
38 | ||
|
|
24 | 39 | // Apply the main VariantsPlugin to ensure the core variant model is available. |
|
|
25 | 40 | target.getPlugins().apply(VariantsPlugin.class); |
|
|
26 | target.getPlugins().apply(SourcesPlugin.class); | |
|
|
27 | 41 | // Access the VariantsExtension to configure variant sources. |
|
|
28 |
var variantsExtension = |
|
|
|
42 | var variantsExtension = extensions.getByType(VariantsExtension.class); | |
|
|
29 | 43 | var objectFactory = target.getObjects(); |
|
|
30 | 44 | |
|
|
31 | var sources = SourcesPlugin.getSourcesExtension(target); | |
|
|
32 | ||
|
|
33 | 45 | var deferred = new Deferred<VariantSourcesContext>(); |
|
|
34 | 46 | |
|
|
47 | var lateConfigurationPolicy = new DefaultLateConfigurationPolicySpec(); | |
|
|
48 | var namingPolicy = new DefaultCompileUnitNamingPolicy(); | |
|
|
49 | ||
|
|
35 | 50 | variantsExtension.whenFinalized(variants -> { |
|
|
51 | // create variant views | |
|
|
36 | 52 | var compileUnits = CompileUnitsView.of(variants); |
|
|
37 | 53 | var roleProjections = RoleProjectionsView.of(variants); |
|
|
38 | 54 | |
|
|
39 | var context = new Context(variants, compileUnits, roleProjections); | |
|
|
55 | // create registries | |
|
|
56 | var sourceSetRegistry = new SourceSetRegistry(objectFactory); | |
|
|
57 | var sourceSetConfiguration = new SourceSetConfigurationRegistry(lateConfigurationPolicy::mode); | |
|
|
58 | ||
|
|
59 | // build compile unit namer | |
|
|
60 | var compileUnitNamer = CompileUnitNamer.builder() | |
|
|
61 | .addUnits(compileUnits.getUnits()) | |
|
|
62 | .nameCollisionPolicy(namingPolicy.policy()) | |
|
|
63 | .build(); | |
|
|
40 | 64 | |
|
|
65 | // create the context | |
|
|
66 | var context = new DefaultVariantSourcesContext( | |
|
|
67 | variants, | |
|
|
68 | compileUnits, | |
|
|
69 | roleProjections, | |
|
|
70 | compileUnitNamer, | |
|
|
71 | sourceSetRegistry, | |
|
|
72 | sourceSetConfiguration | |
|
|
73 | ); | |
|
|
41 | 74 | deferred.resolve(context); |
|
|
42 | 75 | }); |
|
|
43 | 76 | |
|
|
44 | // var | |
|
|
77 | var variantSourcesExtension = new VariantSourcesExtension() { | |
|
|
78 | @Override | |
|
|
79 | public void whenFinalized(Action<? super VariantSourcesContext> action) { | |
|
|
80 | deferred.whenResolved(action::execute); | |
|
|
81 | } | |
|
|
82 | ||
|
|
83 | @Override | |
|
|
84 | public void lateConfigurationPolicy(Action<? super LateConfigurationPolicySpec> action) { | |
|
|
85 | action.execute(lateConfigurationPolicy); | |
|
|
86 | } | |
|
|
87 | ||
|
|
88 | @Override | |
|
|
89 | public void namingPolicy(Action<? super NamingPolicySpec> action) { | |
|
|
90 | action.execute(namingPolicy); | |
|
|
91 | } | |
|
|
92 | ||
|
|
93 | @Override | |
|
|
94 | public void variant(String variantName, Action<? super GenericSourceSet> action) { | |
|
|
95 | Strings.argumentNotNullOrBlank(variantName, "variantName"); | |
|
|
96 | Objects.requireNonNull(action, "action can't be null"); | |
|
|
97 | ||
|
|
98 | lateConfigurationPolicy.finalizePolicy(); | |
|
|
99 | ||
|
|
100 | whenFinalized(ctx -> ctx.configureVariant(resolveVariant(ctx.getVariants(), variantName), action)); | |
|
|
101 | } | |
|
|
102 | ||
|
|
103 | @Override | |
|
|
104 | public void layer(String layerName, Action<? super GenericSourceSet> action) { | |
|
|
105 | // protect external DSL | |
|
|
106 | Strings.argumentNotNullOrBlank(layerName, "layerName"); | |
|
|
107 | Objects.requireNonNull(action, "action can't be null"); | |
|
|
108 | ||
|
|
109 | lateConfigurationPolicy.finalizePolicy(); | |
|
|
110 | ||
|
|
111 | whenFinalized(ctx -> ctx.configureLayer(resolveLayer(ctx.getVariants(), layerName), action)); | |
|
|
112 | } | |
|
|
113 | ||
|
|
114 | @Override | |
|
|
115 | public void unit(String variantName, String layerName, Action<? super GenericSourceSet> action) { | |
|
|
116 | Strings.argumentNotNullOrBlank(layerName, "layerName"); | |
|
|
117 | Strings.argumentNotNullOrBlank(variantName, "variantName"); | |
|
|
118 | Objects.requireNonNull(action, "action can't be null"); | |
|
|
119 | ||
|
|
120 | lateConfigurationPolicy.finalizePolicy(); | |
|
|
121 | ||
|
|
122 | whenFinalized(ctx -> ctx.configureUnit(resolveCompileUnit(ctx, variantName, layerName), action)); | |
|
|
123 | } | |
|
|
124 | }; | |
|
|
125 | ||
|
|
126 | extensions.add(VariantSourcesExtension.class, VARIANT_SOURCES_EXTENSION, variantSourcesExtension); | |
|
|
45 | 127 | |
|
|
46 | 128 | } |
|
|
47 | 129 | |
|
|
48 | private static class Context implements VariantSourcesContext { | |
|
|
49 | ||
|
|
50 | private final VariantsView variantsView; | |
|
|
51 | private final CompileUnitsView compileUnitsView; | |
|
|
52 | private final RoleProjectionsView roleProjectionsView; | |
|
|
53 | ||
|
|
54 | Context(VariantsView variantsView, CompileUnitsView compileUnitsView, RoleProjectionsView roleProjectionsView) { | |
|
|
55 | this.variantsView = variantsView; | |
|
|
56 | this.compileUnitsView = compileUnitsView; | |
|
|
57 | this.roleProjectionsView = roleProjectionsView; | |
|
|
58 | } | |
|
|
59 | ||
|
|
60 | @Override | |
|
|
61 | public VariantsView getVariants() { | |
|
|
62 | return variantsView; | |
|
|
63 | } | |
|
|
64 | ||
|
|
65 | @Override | |
|
|
66 | public CompileUnitsView getCompileUnits() { | |
|
|
67 | return compileUnitsView; | |
|
|
68 | } | |
|
|
130 | private static Layer resolveLayer(VariantsView variants, String name) { | |
|
|
131 | return variants.getLayers().stream() | |
|
|
132 | .filter(named(name)) | |
|
|
133 | .findAny() | |
|
|
134 | .orElseThrow(() -> new IllegalArgumentException("Layer '" + name + "' isn't declared")); | |
|
|
135 | } | |
|
|
69 | 136 | |
|
|
70 | @Override | |
|
|
71 | public RoleProjectionsView getRoleProjections() { | |
|
|
72 | return roleProjectionsView; | |
|
|
73 | } | |
|
|
74 | ||
|
|
75 | @Override | |
|
|
76 | public SourceSetMaterializer getSourceSets() { | |
|
|
77 | // TODO Auto-generated method stub | |
|
|
78 | throw new UnsupportedOperationException("Unimplemented method 'getSourceSets'"); | |
|
|
79 | } | |
|
|
80 | ||
|
|
81 | @Override | |
|
|
82 | public void configureLayer(Layer layer, Action<? super GenericSourceSet> action) { | |
|
|
83 | // TODO Auto-generated method stub | |
|
|
84 | throw new UnsupportedOperationException("Unimplemented method 'configureLayer'"); | |
|
|
85 | } | |
|
|
86 | ||
|
|
87 | @Override | |
|
|
88 | public void configureVariant(Variant variant, Action<? super GenericSourceSet> action) { | |
|
|
89 | // TODO Auto-generated method stub | |
|
|
90 | throw new UnsupportedOperationException("Unimplemented method 'configureVariant'"); | |
|
|
91 | } | |
|
|
92 | ||
|
|
93 | @Override | |
|
|
94 | public void configureUnit(CompileUnit unit, Action<? super GenericSourceSet> action) { | |
|
|
95 | // TODO Auto-generated method stub | |
|
|
96 | throw new UnsupportedOperationException("Unimplemented method 'configureUnit'"); | |
|
|
97 | } | |
|
|
98 | ||
|
|
137 | private static Variant resolveVariant(VariantsView variants, String name) { | |
|
|
138 | return variants.getVariants().stream() | |
|
|
139 | .filter(named(name)) | |
|
|
140 | .findAny() | |
|
|
141 | .orElseThrow(() -> new IllegalArgumentException("Variant '" + name + "' is't declared")); | |
|
|
99 | 142 | } |
|
|
100 | 143 | |
|
|
144 | private static CompileUnit resolveCompileUnit(VariantSourcesContext ctx, String variantName, String layerName) { | |
|
|
145 | return ctx.getCompileUnits().findUnit( | |
|
|
146 | resolveVariant(ctx.getVariants(), variantName), | |
|
|
147 | resolveLayer(ctx.getVariants(), layerName)) | |
|
|
148 | .orElseThrow(() -> new InvalidUserDataException( | |
|
|
149 | "The CompileUnit isn't declared for variant '" + variantName + "', layer '" + layerName + "'")); | |
|
|
150 | } | |
|
|
151 | ||
|
|
152 | private static Predicate<Named> named(String name) { | |
|
|
153 | return named -> named.getName().equals(name); | |
|
|
154 | } | |
|
|
101 | 155 | |
|
|
102 | 156 | } |
| @@ -1,151 +1,220 | |||
|
|
1 | 1 | package org.implab.gradle.variants.core; |
|
|
2 | 2 | |
|
|
3 | 3 | import java.util.ArrayList; |
|
|
4 | 4 | import java.util.LinkedHashMap; |
|
|
5 | 5 | import java.util.List; |
|
|
6 | 6 | import java.util.Map; |
|
|
7 | 7 | import java.util.Objects; |
|
|
8 | 8 | import java.util.Optional; |
|
|
9 | 9 | import java.util.Set; |
|
|
10 | 10 | import java.util.stream.Collectors; |
|
|
11 | 11 | |
|
|
12 | 12 | import org.eclipse.jdt.annotation.NonNullByDefault; |
|
|
13 | 13 | import org.gradle.api.InvalidUserDataException; |
|
|
14 | 14 | |
|
|
15 | /** | |
|
|
16 | * A resolved view of declared variants, roles, layers, and their bindings. | |
|
|
17 | * | |
|
|
18 | * Built from {@link VariantDefinition} instances, this class materializes validated | |
|
|
19 | * {@link VariantRoleLayer} entries and provides lookup APIs grouped by variant, | |
|
|
20 | * role, or layer. | |
|
|
21 | * | |
|
|
22 | * Typical usage is to collect identities and variant definitions through | |
|
|
23 | * {@link Builder}, then use the resulting view to traverse resolved bindings. | |
|
|
24 | */ | |
|
|
15 | 25 | @NonNullByDefault |
|
|
16 | 26 | public class VariantsView { |
|
|
17 | 27 | private final Set<Layer> layers; |
|
|
18 | 28 | private final Set<Role> roles; |
|
|
19 | 29 | private final Set<Variant> variants; |
|
|
20 | 30 | private final Set<VariantRoleLayer> entries; |
|
|
21 | 31 | |
|
|
22 | 32 | private final Map<Variant, Set<VariantRoleLayer>> entriesByVariant; |
|
|
23 | 33 | private final Map<Role, Set<VariantRoleLayer>> entriesByRole; |
|
|
24 | 34 | private final Map<Layer, Set<VariantRoleLayer>> entriesByLayer; |
|
|
25 | 35 | |
|
|
26 | 36 | private VariantsView(Set<Layer> layers, Set<Role> roles, Set<Variant> variants, Set<VariantRoleLayer> entries) { |
|
|
27 | 37 | this.layers = layers; |
|
|
28 | 38 | this.roles = roles; |
|
|
29 | 39 | this.variants = variants; |
|
|
30 | 40 | this.entries = entries; |
|
|
31 | 41 | this.entriesByVariant = entries.stream() |
|
|
32 | 42 | .collect(Collectors.groupingBy(VariantRoleLayer::variant, Collectors.toSet())); |
|
|
33 | 43 | this.entriesByRole = entries.stream() |
|
|
34 | 44 | .collect(Collectors.groupingBy(VariantRoleLayer::role, Collectors.toSet())); |
|
|
35 | 45 | this.entriesByLayer = entries.stream() |
|
|
36 | 46 | .collect(Collectors.groupingBy(VariantRoleLayer::layer, Collectors.toSet())); |
|
|
37 | 47 | } |
|
|
38 | 48 | |
|
|
49 | /** | |
|
|
50 | * Returns all declared layers included in this view. | |
|
|
51 | */ | |
|
|
39 | 52 | public Set<Layer> getLayers() { |
|
|
40 | 53 | return layers; |
|
|
41 | 54 | } |
|
|
42 | 55 | |
|
|
56 | /** | |
|
|
57 | * Returns all declared roles included in this view. | |
|
|
58 | */ | |
|
|
43 | 59 | public Set<Role> getRoles() { |
|
|
44 | 60 | return roles; |
|
|
45 | 61 | } |
|
|
46 | 62 | |
|
|
63 | /** | |
|
|
64 | * Returns all declared variants included in this view. | |
|
|
65 | */ | |
|
|
47 | 66 | public Set<Variant> getVariants() { |
|
|
48 | 67 | return variants; |
|
|
49 | 68 | } |
|
|
50 | 69 | |
|
|
70 | /** | |
|
|
71 | * Returns all resolved variant-role-layer bindings. | |
|
|
72 | */ | |
|
|
51 | 73 | public Set<VariantRoleLayer> getEntries() { |
|
|
52 | 74 | return entries; |
|
|
53 | 75 | } |
|
|
54 | 76 | |
|
|
77 | /** | |
|
|
78 | * Returns all bindings associated with the specified variant. | |
|
|
79 | * | |
|
|
80 | * An empty set is returned when the variant has no bindings in this view. | |
|
|
81 | */ | |
|
|
55 | 82 | public Set<VariantRoleLayer> getEntriesForVariant(Variant variant) { |
|
|
56 | 83 | return entriesByVariant.getOrDefault(variant, Set.of()); |
|
|
57 | 84 | } |
|
|
58 | 85 | |
|
|
86 | /** | |
|
|
87 | * Returns all bindings associated with the specified layer. | |
|
|
88 | * | |
|
|
89 | * An empty set is returned when the layer has no bindings in this view. | |
|
|
90 | */ | |
|
|
59 | 91 | public Set<VariantRoleLayer> getEntriesForLayer(Layer layer) { |
|
|
60 | 92 | return entriesByLayer.getOrDefault(layer, Set.of()); |
|
|
61 | 93 | } |
|
|
62 | 94 | |
|
|
95 | /** | |
|
|
96 | * Returns all bindings associated with the specified role. | |
|
|
97 | * | |
|
|
98 | * An empty set is returned when the role has no bindings in this view. | |
|
|
99 | */ | |
|
|
63 | 100 | public Set<VariantRoleLayer> getEntriesForRole(Role role) { |
|
|
64 | 101 | return entriesByRole.getOrDefault(role, Set.of()); |
|
|
65 | 102 | } |
|
|
66 | 103 | |
|
|
104 | /** | |
|
|
105 | * A resolved binding between a variant, a role, and a layer. | |
|
|
106 | * | |
|
|
107 | * @param variant the resolved variant | |
|
|
108 | * @param role the resolved role | |
|
|
109 | * @param layer the resolved layer | |
|
|
110 | */ | |
|
|
67 | 111 | public record VariantRoleLayer(Variant variant, Role role, Layer layer) { |
|
|
68 | 112 | } |
|
|
69 | 113 | |
|
|
114 | /** | |
|
|
115 | * Creates a builder for assembling a {@link VariantsView}. | |
|
|
116 | */ | |
|
|
70 | 117 | public static Builder builder() { |
|
|
71 | 118 | return new Builder(); |
|
|
72 | 119 | } |
|
|
73 | 120 | |
|
|
121 | /** | |
|
|
122 | * Collects declared identities and variant definitions, then resolves them | |
|
|
123 | * into a {@link VariantsView}. | |
|
|
124 | */ | |
|
|
74 | 125 | public static class Builder { |
|
|
75 | 126 | |
|
|
76 | 127 | private final Map<String, Layer> layers = new LinkedHashMap<>(); |
|
|
77 | 128 | private final Map<String, Role> roles = new LinkedHashMap<>(); |
|
|
78 | 129 | private final Map<String, Variant> variants = new LinkedHashMap<>(); |
|
|
79 | 130 | private final List<VariantDefinition> definitions = new ArrayList<>(); |
|
|
80 | 131 | |
|
|
81 | 132 | private Builder() { |
|
|
82 | 133 | } |
|
|
83 | 134 | |
|
|
135 | /** | |
|
|
136 | * Adds or replaces a role by its name. | |
|
|
137 | */ | |
|
|
84 | 138 | public Builder addRole(Role role) { |
|
|
85 | 139 | Objects.requireNonNull(role, "role can't be null"); |
|
|
86 | 140 | roles.put(role.getName(), role); |
|
|
87 | 141 | return this; |
|
|
88 | 142 | } |
|
|
89 | 143 | |
|
|
144 | /** | |
|
|
145 | * Adds or replaces a layer by its name. | |
|
|
146 | */ | |
|
|
90 | 147 | public Builder addLayer(Layer layer) { |
|
|
91 | 148 | Objects.requireNonNull(layer, "layer can't be null"); |
|
|
92 | 149 | layers.put(layer.getName(), layer); |
|
|
93 | 150 | return this; |
|
|
94 | 151 | } |
|
|
95 | 152 | |
|
|
153 | /** | |
|
|
154 | * Adds or replaces a variant by its name. | |
|
|
155 | */ | |
|
|
96 | 156 | public Builder addVariant(Variant variant) { |
|
|
97 | 157 | Objects.requireNonNull(variant, "variant can't be null"); |
|
|
98 | 158 | variants.put(variant.getName(), variant); |
|
|
99 | 159 | return this; |
|
|
100 | 160 | } |
|
|
101 | 161 | |
|
|
162 | /** | |
|
|
163 | * Adds a variant definition to be resolved during {@link #build()}. | |
|
|
164 | */ | |
|
|
102 | 165 | public Builder addDefinition(VariantDefinition definition) { |
|
|
103 | 166 | Objects.requireNonNull(definition, "definition can't be null"); |
|
|
104 | 167 | definitions.add(definition); |
|
|
105 | 168 | return this; |
|
|
106 | 169 | } |
|
|
107 | 170 | |
|
|
171 | /** | |
|
|
172 | * Resolves collected identities and definitions into an immutable view. | |
|
|
173 | * | |
|
|
174 | * Missing variant, role, or layer declarations referenced by definitions | |
|
|
175 | * cause {@link InvalidUserDataException}. | |
|
|
176 | */ | |
|
|
108 | 177 | public VariantsView build() { |
|
|
109 | 178 | |
|
|
110 | 179 | var entries = definitions.stream() |
|
|
111 | 180 | .flatMap(def -> def.getRoleBindings().get().stream() |
|
|
112 | 181 | .map(layerRole -> createVariantRoleLayer( |
|
|
113 | 182 | def.getName(), // variantName |
|
|
114 | 183 | layerRole.roleName(), |
|
|
115 | 184 | layerRole.layerName()))) |
|
|
116 | 185 | .collect(Collectors.toSet()); |
|
|
117 | 186 | |
|
|
118 | 187 | return new VariantsView( |
|
|
119 | 188 | Set.copyOf(layers.values()), |
|
|
120 | 189 | Set.copyOf(roles.values()), |
|
|
121 | 190 | Set.copyOf(variants.values()), |
|
|
122 | 191 | entries); |
|
|
123 | 192 | } |
|
|
124 | 193 | |
|
|
125 | 194 | private VariantRoleLayer createVariantRoleLayer(String variantName, String roleName, String layerName) { |
|
|
126 | 195 | return new VariantRoleLayer( |
|
|
127 | 196 | resolveVariant(variantName, |
|
|
128 | 197 | "Variant '" + variantName + "' isn't declared"), |
|
|
129 | 198 | resolveRole(roleName, |
|
|
130 | 199 | "Role '" + roleName + "' isn't declared, referenced in '" + variantName + "' variant"), |
|
|
131 | 200 | resolveLayer(layerName, |
|
|
132 | 201 | "Layer '" + layerName + "' isn't declared, referenced in '" + variantName |
|
|
133 | 202 | + "' variant with '" + roleName + "' role")); |
|
|
134 | 203 | } |
|
|
135 | 204 | |
|
|
136 | 205 | private Layer resolveLayer(String layerName, String errorMessage) { |
|
|
137 | 206 | return Optional.ofNullable(layers.get(layerName)) |
|
|
138 | 207 | .orElseThrow(() -> new InvalidUserDataException(errorMessage)); |
|
|
139 | 208 | } |
|
|
140 | 209 | |
|
|
141 | 210 | private Variant resolveVariant(String variantName, String errorMessage) { |
|
|
142 | 211 | return Optional.ofNullable(variants.get(variantName)) |
|
|
143 | 212 | .orElseThrow(() -> new InvalidUserDataException(errorMessage)); |
|
|
144 | 213 | } |
|
|
145 | 214 | |
|
|
146 | 215 | private Role resolveRole(String roleName, String errorMessage) { |
|
|
147 | 216 | return Optional.ofNullable(roles.get(roleName)) |
|
|
148 | 217 | .orElseThrow(() -> new InvalidUserDataException(errorMessage)); |
|
|
149 | 218 | } |
|
|
150 | 219 | } |
|
|
151 | 220 | } |
| @@ -1,51 +1,71 | |||
|
|
1 | 1 | package org.implab.gradle.variants.sources; |
|
|
2 | 2 | |
|
|
3 | 3 | import org.gradle.api.Action; |
|
|
4 | 4 | import org.implab.gradle.common.sources.GenericSourceSet; |
|
|
5 | 5 | import org.implab.gradle.variants.core.Layer; |
|
|
6 | 6 | import org.implab.gradle.variants.core.Variant; |
|
|
7 | 7 | import org.implab.gradle.variants.core.VariantsView; |
|
|
8 | 8 | |
|
|
9 | 9 | /** |
|
|
10 | 10 | * Registry of symbolic source set names produced by sources projection. |
|
|
11 | 11 | * |
|
|
12 | 12 | * Identity in this registry is the GenericSourceSet name. |
|
|
13 | 13 | */ |
|
|
14 | 14 | public interface VariantSourcesContext { |
|
|
15 | 15 | |
|
|
16 | 16 | /** |
|
|
17 | 17 | * Finalized core model. |
|
|
18 | 18 | */ |
|
|
19 | 19 | VariantsView getVariants(); |
|
|
20 | 20 | |
|
|
21 | 21 | /** |
|
|
22 | 22 | * Derived compile-side view. |
|
|
23 | 23 | */ |
|
|
24 | 24 | CompileUnitsView getCompileUnits(); |
|
|
25 | 25 | |
|
|
26 | 26 | /** |
|
|
27 | 27 | * Derived role-side view. |
|
|
28 | 28 | */ |
|
|
29 | 29 | RoleProjectionsView getRoleProjections(); |
|
|
30 | 30 | |
|
|
31 | 31 | /** |
|
|
32 | 32 | * Lazy source set provider service. |
|
|
33 | 33 | */ |
|
|
34 | 34 | SourceSetMaterializer getSourceSets(); |
|
|
35 | 35 | |
|
|
36 | 36 | /** |
|
|
37 | 37 | * Configures all GenericSourceSets produced from the given layer. |
|
|
38 | 38 | * |
|
|
39 | 39 | * The action is applied: |
|
|
40 | 40 | * - to already materialized source sets of this layer |
|
|
41 | 41 | * - to all future source sets of this layer |
|
|
42 | 42 | * |
|
|
43 | * Actions are applied in registration order. | |
|
|
43 | * <p>For future source sets, selector precedence and registration order are | |
|
|
44 | * preserved by the materializer. | |
|
|
45 | * | |
|
|
46 | * <p>For already materialized source sets, behavior is governed by | |
|
|
47 | * {@link VariantSourcesExtension#lateConfigurationPolicy(org.gradle.api.Action)}. | |
|
|
48 | * In warn/allow modes the action is applied as a late imperative step and does | |
|
|
49 | * not retroactively restore selector precedence. | |
|
|
44 | 50 | */ |
|
|
45 | 51 | void configureLayer(Layer layer, Action<? super GenericSourceSet> action); |
|
|
46 | 52 | |
|
|
53 | /** | |
|
|
54 | * Configures all GenericSourceSets produced from the given variant. | |
|
|
55 | * | |
|
|
56 | * <p>Late application semantics for already materialized source sets are | |
|
|
57 | * governed by | |
|
|
58 | * {@link VariantSourcesExtension#lateConfigurationPolicy(org.gradle.api.Action)}. | |
|
|
59 | */ | |
|
|
47 | 60 | void configureVariant(Variant variant, Action<? super GenericSourceSet> action); |
|
|
48 | 61 | |
|
|
62 | /** | |
|
|
63 | * Configures the GenericSourceSet produced from the given compile unit. | |
|
|
64 | * | |
|
|
65 | * <p>Late application semantics for already materialized source sets are | |
|
|
66 | * governed by | |
|
|
67 | * {@link VariantSourcesExtension#lateConfigurationPolicy(org.gradle.api.Action)}. | |
|
|
68 | */ | |
|
|
49 | 69 | void configureUnit(CompileUnit unit, Action<? super GenericSourceSet> action); |
|
|
50 | 70 | |
|
|
51 | } No newline at end of file | |
|
|
71 | } | |
| @@ -1,93 +1,105 | |||
|
|
1 | 1 | package org.implab.gradle.variants.sources; |
|
|
2 | 2 | |
|
|
3 | import java.util.Objects; | |
|
|
4 | import java.util.function.Predicate; | |
|
|
5 | ||
|
|
6 | 3 | import org.eclipse.jdt.annotation.NonNullByDefault; |
|
|
7 | 4 | import org.gradle.api.Action; |
|
|
8 | import org.gradle.api.InvalidUserDataException; | |
|
|
9 | import org.gradle.api.Named; | |
|
|
10 | 5 | import org.implab.gradle.common.core.lang.Closures; |
|
|
11 | import org.implab.gradle.common.core.lang.Strings; | |
|
|
12 | 6 | import org.implab.gradle.common.sources.GenericSourceSet; |
|
|
13 | import org.implab.gradle.variants.core.Layer; | |
|
|
14 | import org.implab.gradle.variants.core.Variant; | |
|
|
15 | import org.implab.gradle.variants.core.VariantsView; | |
|
|
16 | ||
|
|
17 | 7 | import groovy.lang.Closure; |
|
|
18 | 8 | |
|
|
19 | 9 | @NonNullByDefault |
|
|
20 | 10 | public interface VariantSourcesExtension { |
|
|
21 | 11 | |
|
|
22 | default void layer(String layerName, Action<? super GenericSourceSet> action) { | |
|
|
23 | // protect external DSL | |
|
|
24 | Strings.argumentNotNullOrBlank(layerName, "layerName"); | |
|
|
25 | Objects.requireNonNull(action, "action can't be null"); | |
|
|
12 | /** | |
|
|
13 | * Selects how selector rules behave when they target an already materialized | |
|
|
14 | * {@link GenericSourceSet}. | |
|
|
15 | * | |
|
|
16 | * <p>This policy is single-valued: | |
|
|
17 | * <ul> | |
|
|
18 | * <li>it must be selected before the first selector rule is registered via | |
|
|
19 | * {@link #variant(String, Action)}, {@link #layer(String, Action)} or | |
|
|
20 | * {@link #unit(String, String, Action)};</li> | |
|
|
21 | * <li>once selected, it cannot be changed later;</li> | |
|
|
22 | * <li>the policy controls both diagnostics and late-application semantics.</li> | |
|
|
23 | * </ul> | |
|
|
24 | */ | |
|
|
25 | void lateConfigurationPolicy(Action<? super LateConfigurationPolicySpec> action); | |
|
|
26 | 26 | |
|
|
27 | whenFinalized(ctx -> ctx.configureLayer(resolveLayer(ctx.getVariants(), layerName), action)); | |
|
|
27 | default void lateConfigurationPolicy(Closure<?> closure) { | |
|
|
28 | lateConfigurationPolicy(Closures.action(closure)); | |
|
|
28 | 29 | } |
|
|
29 | 30 | |
|
|
31 | void namingPolicy(Action<? super NamingPolicySpec> action); | |
|
|
32 | ||
|
|
33 | default void namingPolicy(Closure<?> closure) { | |
|
|
34 | namingPolicy(Closures.action(closure)); | |
|
|
35 | } | |
|
|
36 | ||
|
|
37 | void layer(String layerName, Action<? super GenericSourceSet> action); | |
|
|
38 | ||
|
|
30 | 39 | default void layer(String layerName, Closure<?> closure) { |
|
|
31 | 40 | layer(layerName, Closures.action(closure)); |
|
|
32 | 41 | } |
|
|
33 | 42 | |
|
|
34 |
|
|
|
|
35 | Strings.argumentNotNullOrBlank(variantName, "variantName"); | |
|
|
36 | Objects.requireNonNull(action, "action can't be null"); | |
|
|
37 | ||
|
|
38 | whenFinalized(ctx -> ctx.configureVariant(resolveVariant(ctx.getVariants(), variantName), action)); | |
|
|
39 | } | |
|
|
43 | void variant(String variantName, Action<? super GenericSourceSet> action); | |
|
|
40 | 44 | |
|
|
41 | 45 | default void variant(String variantName, Closure<?> closure) { |
|
|
42 | 46 | variant(variantName, Closures.action(closure)); |
|
|
43 | 47 | } |
|
|
44 | 48 | |
|
|
45 |
|
|
|
|
46 | Strings.argumentNotNullOrBlank(layerName, "layerName"); | |
|
|
47 | Strings.argumentNotNullOrBlank(variantName, "variantName"); | |
|
|
48 | Objects.requireNonNull(action, "action can't be null"); | |
|
|
49 | ||
|
|
50 | whenFinalized(ctx -> ctx.configureUnit(resolveCompileUnit(ctx, variantName, layerName), action)); | |
|
|
51 | } | |
|
|
49 | void unit(String variantName, String layerName, Action<? super GenericSourceSet> action); | |
|
|
52 | 50 | |
|
|
53 | 51 | /** |
|
|
54 | 52 | * Invoked when finalized variants-derived source context becomes available. |
|
|
55 | 53 | * |
|
|
56 | 54 | * Replayable: |
|
|
57 | 55 | * <ul> |
|
|
58 | 56 | * <li>if called before variants finalization, action is queued |
|
|
59 | 57 | * <li>if called after variants finalization, action is invoked immediately |
|
|
60 | 58 | * </ul> |
|
|
61 | 59 | */ |
|
|
62 | 60 | void whenFinalized(Action<? super VariantSourcesContext> action); |
|
|
63 | 61 | |
|
|
64 | 62 | default void whenFinalized(Closure<?> closure) { |
|
|
65 | 63 | whenFinalized(Closures.action(closure)); |
|
|
66 | 64 | } |
|
|
67 | 65 | |
|
|
68 | private static Layer resolveLayer(VariantsView variants, String name) { | |
|
|
69 | return variants.getLayers().stream() | |
|
|
70 | .filter(named(name)) | |
|
|
71 | .findAny() | |
|
|
72 | .orElseThrow(() -> new IllegalArgumentException("Layer '" + name + "' isn't declared")); | |
|
|
66 | ||
|
|
67 | /** | |
|
|
68 | * Imperative selector for the late-configuration mode. | |
|
|
69 | * | |
|
|
70 | * <p>Exactly one mode is expected to be chosen for the extension lifecycle. | |
|
|
71 | */ | |
|
|
72 | interface LateConfigurationPolicySpec { | |
|
|
73 | /** | |
|
|
74 | * Rejects selector registration if it targets any already materialized | |
|
|
75 | * source set. | |
|
|
76 | */ | |
|
|
77 | void failOnLateConfiguration(); | |
|
|
78 | ||
|
|
79 | /** | |
|
|
80 | * Allows late selector registration, but emits a warning when it targets an | |
|
|
81 | * already materialized source set. | |
|
|
82 | * | |
|
|
83 | * <p>For such targets, selector precedence is not re-established | |
|
|
84 | * retroactively. The action is applied as a late imperative step, after the | |
|
|
85 | * state already produced at the materialization moment. | |
|
|
86 | */ | |
|
|
87 | void warnOnLateConfiguration(); | |
|
|
88 | ||
|
|
89 | /** | |
|
|
90 | * Allows late selector registration without a warning when it targets an | |
|
|
91 | * already materialized source set. | |
|
|
92 | * | |
|
|
93 | * <p>For such targets, selector precedence is not re-established | |
|
|
94 | * retroactively. The action is applied as a late imperative step, after the | |
|
|
95 | * state already produced at the materialization moment. | |
|
|
96 | */ | |
|
|
97 | void allowLateConfiguration(); | |
|
|
73 | 98 | } |
|
|
74 | 99 | |
|
|
75 | private static Variant resolveVariant(VariantsView variants, String name) { | |
|
|
76 | return variants.getVariants().stream() | |
|
|
77 | .filter(named(name)) | |
|
|
78 | .findAny() | |
|
|
79 | .orElseThrow(() -> new IllegalArgumentException("Variant '" + name + "' is't declared")); | |
|
|
80 | } | |
|
|
100 | interface NamingPolicySpec { | |
|
|
101 | void failOnNameCollision(); | |
|
|
81 | 102 | |
|
|
82 | private static CompileUnit resolveCompileUnit(VariantSourcesContext ctx, String variantName, String layerName) { | |
|
|
83 | return ctx.getCompileUnits().findUnit( | |
|
|
84 | resolveVariant(ctx.getVariants(), variantName), | |
|
|
85 | resolveLayer(ctx.getVariants(), layerName)) | |
|
|
86 | .orElseThrow(() -> new InvalidUserDataException( | |
|
|
87 | "The CompileUnit isn't declared for variant '" + variantName + "', layer '" + layerName + "'")); | |
|
|
103 | void resolveNameCollision(); | |
|
|
88 | 104 | } |
|
|
89 | ||
|
|
90 | private static Predicate<Named> named(String name) { | |
|
|
91 | return named -> named.getName().equals(name); | |
|
|
92 | } | |
|
|
93 | } No newline at end of file | |
|
|
105 | } | |
| @@ -1,15 +1,114 | |||
|
|
1 | 1 | package org.implab.gradle.variants.sources.internal; |
|
|
2 | 2 | |
|
|
3 | import java.text.MessageFormat; | |
|
|
4 | import java.util.LinkedHashMap; | |
|
|
5 | import java.util.LinkedList; | |
|
|
6 | import java.util.List; | |
|
|
7 | import java.util.Map; | |
|
|
8 | import java.util.function.Consumer; | |
|
|
9 | import java.util.function.Supplier; | |
|
|
10 | import java.util.stream.Collectors; | |
|
|
11 | ||
|
|
12 | import org.eclipse.jdt.annotation.NonNullByDefault; | |
|
|
3 | 13 | import org.gradle.api.Action; |
|
|
14 | import org.gradle.api.Named; | |
|
|
15 | import org.gradle.api.logging.Logger; | |
|
|
16 | import org.gradle.api.logging.Logging; | |
|
|
4 | 17 | import org.implab.gradle.common.sources.GenericSourceSet; |
|
|
5 | 18 | import org.implab.gradle.variants.core.Layer; |
|
|
19 | import org.implab.gradle.variants.core.Variant; | |
|
|
6 | 20 | import org.implab.gradle.variants.sources.CompileUnit; |
|
|
7 | 21 | |
|
|
8 | public interface LayerConfigurationRegistry { | |
|
|
22 | @NonNullByDefault | |
|
|
23 | public class SourceSetConfigurationRegistry { | |
|
|
24 | private static final Logger logger = Logging.getLogger(SourceSetConfigurationRegistry.class); | |
|
|
25 | ||
|
|
26 | private final Map<Layer, ReplayableQueue<GenericSourceSet>> sourcesByLayer = new LinkedHashMap<>(); | |
|
|
27 | private final Map<Variant, ReplayableQueue<GenericSourceSet>> sourcesByVariant = new LinkedHashMap<>(); | |
|
|
28 | private final Map<CompileUnit, ReplayableQueue<GenericSourceSet>> sourcesByUnit = new LinkedHashMap<>(); | |
|
|
29 | ||
|
|
30 | private final Supplier<LateConfigurationMode> lateConfigurationMode; | |
|
|
31 | ||
|
|
32 | public SourceSetConfigurationRegistry(Supplier<LateConfigurationMode> lateConfigurationMode) { | |
|
|
33 | this.lateConfigurationMode = lateConfigurationMode; | |
|
|
34 | } | |
|
|
9 | 35 | |
|
|
10 |
void addLayerAction(Layer layer, Action<? super GenericSourceSet> action) |
|
|
|
36 | public void addLayerAction(Layer layer, Action<? super GenericSourceSet> action) { | |
|
|
37 | addToActions( | |
|
|
38 | sourcesByLayer.computeIfAbsent(layer, key -> new ReplayableQueue<>()), | |
|
|
39 | action, | |
|
|
40 | MessageFormat.format( | |
|
|
41 | "Source sets for [layer={0}] layer already materialized", | |
|
|
42 | layer.getName())); | |
|
|
43 | } | |
|
|
44 | ||
|
|
45 | public void addVariantAction(Variant variant, Action<? super GenericSourceSet> action) { | |
|
|
46 | addToActions( | |
|
|
47 | sourcesByVariant.computeIfAbsent(variant, key -> new ReplayableQueue<>()), | |
|
|
48 | action, | |
|
|
49 | MessageFormat.format( | |
|
|
50 | "Source sets for [variant={0}] variant already materialized", | |
|
|
51 | variant.getName())); | |
|
|
52 | ||
|
|
53 | } | |
|
|
54 | ||
|
|
55 | public void addCompileUnitAction(CompileUnit unit, Action<? super GenericSourceSet> action) { | |
|
|
56 | addToActions( | |
|
|
57 | sourcesByUnit.computeIfAbsent(unit, key -> new ReplayableQueue<>()), | |
|
|
58 | action, | |
|
|
59 | MessageFormat.format( | |
|
|
60 | "Source set for [variant={0}, layer={1}] already materialed", | |
|
|
61 | unit.variant().getName(), | |
|
|
62 | unit.layer().getName())); | |
|
|
63 | } | |
|
|
11 | 64 | |
|
|
12 | void applyLayer(Layer layer, GenericSourceSet sourceSet); | |
|
|
65 | private void addToActions( | |
|
|
66 | ReplayableQueue<GenericSourceSet> actions, | |
|
|
67 | Action<? super GenericSourceSet> action, | |
|
|
68 | String assertMessage) { | |
|
|
69 | assertLazyConfiguration(actions.values(), assertMessage); | |
|
|
70 | actions.forEach(action::execute); | |
|
|
71 | } | |
|
|
72 | ||
|
|
73 | void assertLazyConfiguration(List<GenericSourceSet> sets, String message) { | |
|
|
74 | if (sets.size() == 0) | |
|
|
75 | return; | |
|
|
76 | ||
|
|
77 | var names = sets.stream().map(Named::getName).collect(Collectors.joining(", ")); | |
|
|
78 | ||
|
|
79 | switch (lateConfigurationMode.get()) { | |
|
|
80 | case FAIL: | |
|
|
81 | throw new IllegalStateException(message + " [" + names + "]"); | |
|
|
82 | case WARN: | |
|
|
83 | logger.warn(message + "\n\t" + names); | |
|
|
84 | break; | |
|
|
85 | default: | |
|
|
86 | break; | |
|
|
87 | } | |
|
|
88 | } | |
|
|
13 | 89 | |
|
|
14 |
void apply |
|
|
|
90 | public void applyConfiguration(CompileUnit unit, GenericSourceSet sourceSet) { | |
|
|
91 | sourcesByVariant.computeIfAbsent(unit.variant(), key -> new ReplayableQueue<>()).add(sourceSet); | |
|
|
92 | sourcesByLayer.computeIfAbsent(unit.layer(), key -> new ReplayableQueue<>()).add(sourceSet); | |
|
|
93 | sourcesByUnit.computeIfAbsent(unit, key -> new ReplayableQueue<>()).add(sourceSet); | |
|
|
94 | } | |
|
|
95 | ||
|
|
96 | class ReplayableQueue<T> { | |
|
|
97 | private final List<Consumer<? super T>> consumers = new LinkedList<>(); | |
|
|
98 | private final List<T> values = new LinkedList<>(); | |
|
|
99 | ||
|
|
100 | public void add(T value) { | |
|
|
101 | consumers.forEach(consumer -> consumer.accept(value)); | |
|
|
102 | values.add(value); | |
|
|
103 | } | |
|
|
104 | ||
|
|
105 | List<T> values() { | |
|
|
106 | return List.copyOf(values); | |
|
|
107 | } | |
|
|
108 | ||
|
|
109 | public void forEach(Consumer<? super T> consumer) { | |
|
|
110 | values.forEach(consumer); | |
|
|
111 | consumers.add(consumer); | |
|
|
112 | } | |
|
|
113 | } | |
|
|
15 | 114 | } |
| @@ -1,719 +1,776 | |||
|
|
1 | 1 | # Variants and Variant Sources |
|
|
2 | 2 | |
|
|
3 | 3 | ## Overview |
|
|
4 | 4 | |
|
|
5 | 5 | This document describes a two-layer model for build variants: |
|
|
6 | 6 | |
|
|
7 | 7 | - `variants` defines the **core domain model** |
|
|
8 | 8 | - `variantSources` defines **source materialization semantics** for that model |
|
|
9 | 9 | |
|
|
10 | 10 | The main goal is to keep the core model small, explicit, and stable, while allowing source-related behavior to remain flexible and adapter-friendly. |
|
|
11 | 11 | |
|
|
12 | 12 | The model is intentionally split into: |
|
|
13 | 13 | |
|
|
14 | 14 | 1. a **closed, finalized domain model** |
|
|
15 | 15 | 2. an **open, runtime-oriented source materialization model** |
|
|
16 | 16 | |
|
|
17 | 17 | This separation is important because compilation, source aggregation, publication, and adapter-specific behavior do not belong to the same abstraction layer. |
|
|
18 | 18 | |
|
|
19 | 19 | --- |
|
|
20 | 20 | |
|
|
21 | 21 | ## Core idea |
|
|
22 | 22 | |
|
|
23 | 23 | The `variants` model is based on three independent domains: |
|
|
24 | 24 | |
|
|
25 | 25 | - `Layer` |
|
|
26 | 26 | - `Role` |
|
|
27 | 27 | - `Variant` |
|
|
28 | 28 | |
|
|
29 | 29 | A finalized `VariantsView` contains the normalized relation: |
|
|
30 | 30 | |
|
|
31 | 31 | - `(variant, role, layer)` |
|
|
32 | 32 | |
|
|
33 | 33 | This relation is the source of truth. |
|
|
34 | 34 | |
|
|
35 | 35 | Everything else is derived from it. |
|
|
36 | 36 | |
|
|
37 | 37 | --- |
|
|
38 | 38 | |
|
|
39 | 39 | ## `variants`: the core domain model |
|
|
40 | 40 | |
|
|
41 | 41 | ### Purpose |
|
|
42 | 42 | |
|
|
43 | 43 | `variants` describes: |
|
|
44 | 44 | |
|
|
45 | 45 | - what layers exist |
|
|
46 | 46 | - what roles exist |
|
|
47 | 47 | - what variants exist |
|
|
48 | 48 | - which `(variant, role, layer)` combinations are valid |
|
|
49 | 49 | |
|
|
50 | 50 | It does **not** describe: |
|
|
51 | 51 | |
|
|
52 | 52 | - source directories |
|
|
53 | 53 | - source roots |
|
|
54 | 54 | - source set materialization |
|
|
55 | 55 | - compilation tasks |
|
|
56 | 56 | - publication mechanics |
|
|
57 | 57 | - source set inheritance |
|
|
58 | 58 | - layer merge behavior for a concrete toolchain |
|
|
59 | 59 | |
|
|
60 | 60 | Those concerns are intentionally outside the core model. |
|
|
61 | 61 | |
|
|
62 | 62 | --- |
|
|
63 | 63 | |
|
|
64 | 64 | ## Core DSL example |
|
|
65 | 65 | |
|
|
66 | 66 | ```groovy |
|
|
67 | 67 | variants { |
|
|
68 | 68 | layers { |
|
|
69 | 69 | main() |
|
|
70 | 70 | test() |
|
|
71 | 71 | generated() |
|
|
72 | 72 | rjs() |
|
|
73 | 73 | cjs() |
|
|
74 | 74 | } |
|
|
75 | 75 | |
|
|
76 | 76 | roles { |
|
|
77 | 77 | production() |
|
|
78 | 78 | test() |
|
|
79 | 79 | tool() |
|
|
80 | 80 | } |
|
|
81 | 81 | |
|
|
82 | 82 | variant("browser") { |
|
|
83 | 83 | role("production") { |
|
|
84 | 84 | layers("main", "generated", "rjs") |
|
|
85 | 85 | } |
|
|
86 | 86 | role("test") { |
|
|
87 | 87 | layers("main", "test", "generated", "rjs") |
|
|
88 | 88 | } |
|
|
89 | 89 | } |
|
|
90 | 90 | |
|
|
91 | 91 | variant("nodejs") { |
|
|
92 | 92 | role("production") { |
|
|
93 | 93 | layers("main", "generated", "cjs") |
|
|
94 | 94 | } |
|
|
95 | 95 | role("test") { |
|
|
96 | 96 | layers("main", "test", "generated", "cjs") |
|
|
97 | 97 | } |
|
|
98 | 98 | role("tool") { |
|
|
99 | 99 | layers("main", "generated", "cjs") |
|
|
100 | 100 | } |
|
|
101 | 101 | } |
|
|
102 | 102 | } |
|
|
103 | 103 | ``` |
|
|
104 | 104 | |
|
|
105 | 105 | ### Interpretation |
|
|
106 | 106 | |
|
|
107 | 107 | This example means: |
|
|
108 | 108 | |
|
|
109 | 109 | * `browser` production uses `main`, `generated`, `rjs` |
|
|
110 | 110 | * `browser` test uses `main`, `test`, `generated`, `rjs` |
|
|
111 | 111 | * `nodejs` production uses `main`, `generated`, `cjs` |
|
|
112 | 112 | * `nodejs` test uses `main`, `test`, `generated`, `cjs` |
|
|
113 | 113 | * `nodejs` tool uses `main`, `generated`, `cjs` |
|
|
114 | 114 | |
|
|
115 | 115 | The model is purely declarative. |
|
|
116 | 116 | |
|
|
117 | 117 | --- |
|
|
118 | 118 | |
|
|
119 | 119 | ## Identity and references |
|
|
120 | 120 | |
|
|
121 | 121 | `Layer`, `Role`, and `Variant` are identity objects. |
|
|
122 | 122 | |
|
|
123 | 123 | They exist as declared domain values. |
|
|
124 | 124 | |
|
|
125 | 125 | References between model elements are symbolic: |
|
|
126 | 126 | |
|
|
127 | 127 | * layers are referenced by layer name |
|
|
128 | 128 | * roles are referenced by role name |
|
|
129 | 129 | * variants are referenced by variant name |
|
|
130 | 130 | |
|
|
131 | 131 | This is intentional. |
|
|
132 | 132 | |
|
|
133 | 133 | The core model is declarative, not navigation-oriented. |
|
|
134 | 134 | |
|
|
135 | 135 | It is acceptable for aggregates to hold symbolic references to foreign domain values, as long as identity is clearly defined and validated later. |
|
|
136 | 136 | |
|
|
137 | 137 | --- |
|
|
138 | 138 | |
|
|
139 | 139 | ## Finalization |
|
|
140 | 140 | |
|
|
141 | 141 | The `variants` model is finalized once. |
|
|
142 | 142 | |
|
|
143 | 143 | Finalization is an internal lifecycle transition. It is typically triggered privately, for example from `afterEvaluate`, but that mechanism is not part of the public API contract. |
|
|
144 | 144 | |
|
|
145 | 145 | The public contract is: |
|
|
146 | 146 | |
|
|
147 | 147 | * `variants.whenFinalized(...)` |
|
|
148 | 148 | |
|
|
149 | 149 | This callback is **replayable**: |
|
|
150 | 150 | |
|
|
151 | 151 | * if called before finalization, the action is queued |
|
|
152 | 152 | * if called after finalization, the action is invoked immediately |
|
|
153 | 153 | |
|
|
154 | 154 | The callback receives a finalized, read-only view of the model. |
|
|
155 | 155 | |
|
|
156 | 156 | Example: |
|
|
157 | 157 | |
|
|
158 | 158 | ```java |
|
|
159 | 159 | variants.whenFinalized(view -> { |
|
|
160 | 160 | // use finalized VariantsView here |
|
|
161 | 161 | }); |
|
|
162 | 162 | ``` |
|
|
163 | 163 | |
|
|
164 | 164 | --- |
|
|
165 | 165 | |
|
|
166 | 166 | ## `VariantsView` |
|
|
167 | 167 | |
|
|
168 | 168 | `VariantsView` is the finalized representation of the core model. |
|
|
169 | 169 | |
|
|
170 | 170 | It contains: |
|
|
171 | 171 | |
|
|
172 | 172 | * all declared `Layer` |
|
|
173 | 173 | * all declared `Role` |
|
|
174 | 174 | * all declared `Variant` |
|
|
175 | 175 | * all normalized entries `(variant, role, layer)` |
|
|
176 | 176 | |
|
|
177 | 177 | Conceptually: |
|
|
178 | 178 | |
|
|
179 | 179 | ```java |
|
|
180 | 180 | interface VariantsView { |
|
|
181 | 181 | Set<Layer> getLayers(); |
|
|
182 | 182 | Set<Role> getRoles(); |
|
|
183 | 183 | Set<Variant> getVariants(); |
|
|
184 | 184 | Set<VariantRoleLayer> getEntries(); |
|
|
185 | 185 | } |
|
|
186 | 186 | ``` |
|
|
187 | 187 | |
|
|
188 | 188 | Where: |
|
|
189 | 189 | |
|
|
190 | 190 | ```java |
|
|
191 | 191 | record VariantRoleLayer(Variant variant, Role role, Layer layer) {} |
|
|
192 | 192 | ``` |
|
|
193 | 193 | |
|
|
194 | 194 | This view is: |
|
|
195 | 195 | |
|
|
196 | 196 | * immutable |
|
|
197 | 197 | * normalized |
|
|
198 | 198 | * validated |
|
|
199 | 199 | * independent from DSL internals |
|
|
200 | 200 | |
|
|
201 | 201 | --- |
|
|
202 | 202 | |
|
|
203 | 203 | ## Derived views |
|
|
204 | 204 | |
|
|
205 | 205 | Two important views can be derived from `VariantsView`: |
|
|
206 | 206 | |
|
|
207 | 207 | * `CompileUnitsView` |
|
|
208 | 208 | * `RoleProjectionsView` |
|
|
209 | 209 | |
|
|
210 | 210 | These views are not part of the raw core model itself, but they are naturally derived from it. |
|
|
211 | 211 | |
|
|
212 | 212 | --- |
|
|
213 | 213 | |
|
|
214 | 214 | ## `CompileUnitsView` |
|
|
215 | 215 | |
|
|
216 | 216 | ### Purpose |
|
|
217 | 217 | |
|
|
218 | 218 | A compile unit is defined as: |
|
|
219 | 219 | |
|
|
220 | 220 | * `(variant, layer)` |
|
|
221 | 221 | |
|
|
222 | 222 | This is based on the following rationale: |
|
|
223 | 223 | |
|
|
224 | 224 | * `variant` defines compilation semantics |
|
|
225 | 225 | * `layer` partitions a variant into separate compilation units |
|
|
226 | 226 | * `role` is not a compilation boundary |
|
|
227 | 227 | |
|
|
228 | 228 | This is especially useful for toolchains such as TypeScript, where compilation is often more practical or more correct per layer than for the whole variant at once. |
|
|
229 | 229 | |
|
|
230 | 230 | ### Example |
|
|
231 | 231 | |
|
|
232 | 232 | From: |
|
|
233 | 233 | |
|
|
234 | 234 | * `(browser, production, main)` |
|
|
235 | 235 | * `(browser, production, rjs)` |
|
|
236 | 236 | * `(browser, test, main)` |
|
|
237 | 237 | * `(browser, test, test)` |
|
|
238 | 238 | * `(browser, test, rjs)` |
|
|
239 | 239 | |
|
|
240 | 240 | we derive compile units: |
|
|
241 | 241 | |
|
|
242 | 242 | * `(browser, main)` |
|
|
243 | 243 | * `(browser, rjs)` |
|
|
244 | 244 | * `(browser, test)` |
|
|
245 | 245 | |
|
|
246 | 246 | ### Conceptual API |
|
|
247 | 247 | |
|
|
248 | 248 | ```java |
|
|
249 | 249 | interface CompileUnitsView { |
|
|
250 | 250 | Set<CompileUnit> getUnits(); |
|
|
251 | 251 | Set<CompileUnit> getUnitsForVariant(Variant variant); |
|
|
252 | 252 | boolean contains(Variant variant, Layer layer); |
|
|
253 | 253 | Set<Role> getRoles(CompileUnit unit); |
|
|
254 | 254 | } |
|
|
255 | 255 | |
|
|
256 | 256 | record CompileUnit(Variant variant, Layer layer) {} |
|
|
257 | 257 | ``` |
|
|
258 | 258 | |
|
|
259 | 259 | ### Meaning |
|
|
260 | 260 | |
|
|
261 | 261 | `CompileUnitsView` answers: |
|
|
262 | 262 | |
|
|
263 | 263 | * what can be compiled |
|
|
264 | 264 | * how a variant is partitioned into compile units |
|
|
265 | 265 | * which logical roles include a given compile unit |
|
|
266 | 266 | |
|
|
267 | 267 | --- |
|
|
268 | 268 | |
|
|
269 | 269 | ## `RoleProjectionsView` |
|
|
270 | 270 | |
|
|
271 | 271 | ### Purpose |
|
|
272 | 272 | |
|
|
273 | 273 | A role projection is defined as: |
|
|
274 | 274 | |
|
|
275 | 275 | * `(variant, role)` |
|
|
276 | 276 | |
|
|
277 | 277 | This is based on the following rationale: |
|
|
278 | 278 | |
|
|
279 | 279 | * `role` is not about compilation |
|
|
280 | 280 | * `role` groups compile units by purpose |
|
|
281 | 281 | * roles are more closely related to publication, aggregation, assembly, or result grouping |
|
|
282 | 282 | |
|
|
283 | 283 | ### Example |
|
|
284 | 284 | |
|
|
285 | 285 | For `browser`: |
|
|
286 | 286 | |
|
|
287 | 287 | * `production` includes compile units: |
|
|
288 | 288 | |
|
|
289 | 289 | * `(browser, main)` |
|
|
290 | 290 | * `(browser, rjs)` |
|
|
291 | 291 | |
|
|
292 | 292 | * `test` includes compile units: |
|
|
293 | 293 | |
|
|
294 | 294 | * `(browser, main)` |
|
|
295 | 295 | * `(browser, test)` |
|
|
296 | 296 | * `(browser, rjs)` |
|
|
297 | 297 | |
|
|
298 | 298 | ### Conceptual API |
|
|
299 | 299 | |
|
|
300 | 300 | ```java |
|
|
301 | 301 | interface RoleProjectionsView { |
|
|
302 | 302 | Set<RoleProjection> getProjections(); |
|
|
303 | 303 | Set<RoleProjection> getProjectionsForVariant(Variant variant); |
|
|
304 | 304 | Set<RoleProjection> getProjectionsForRole(Role role); |
|
|
305 | 305 | Set<CompileUnit> getUnits(RoleProjection projection); |
|
|
306 | 306 | } |
|
|
307 | 307 | |
|
|
308 | 308 | record RoleProjection(Variant variant, Role role) {} |
|
|
309 | 309 | ``` |
|
|
310 | 310 | |
|
|
311 | 311 | ### Meaning |
|
|
312 | 312 | |
|
|
313 | 313 | `RoleProjectionsView` answers: |
|
|
314 | 314 | |
|
|
315 | 315 | * how compile units are grouped by purpose |
|
|
316 | 316 | * what belongs to `production`, `test`, `tool`, etc. |
|
|
317 | 317 | * what should be aggregated or published together |
|
|
318 | 318 | |
|
|
319 | 319 | --- |
|
|
320 | 320 | |
|
|
321 | 321 | ## Why `CompileUnitsView` and `RoleProjectionsView` are not part of `VariantsView` |
|
|
322 | 322 | |
|
|
323 | 323 | `VariantsView` is intentionally minimal. |
|
|
324 | 324 | |
|
|
325 | 325 | It expresses the domain relation: |
|
|
326 | 326 | |
|
|
327 | 327 | * `(variant, role, layer)` |
|
|
328 | 328 | |
|
|
329 | 329 | `CompileUnitsView` and `RoleProjectionsView` are **derived interpretations** of that relation. |
|
|
330 | 330 | |
|
|
331 | 331 | They are natural and useful, but they are still interpretations: |
|
|
332 | 332 | |
|
|
333 | 333 | * `CompileUnit = (variant, layer)` |
|
|
334 | 334 | * `RoleProjection = (variant, role)` |
|
|
335 | 335 | |
|
|
336 | 336 | This is why they are better treated as derived views rather than direct core model primitives. |
|
|
337 | 337 | |
|
|
338 | 338 | --- |
|
|
339 | 339 | |
|
|
340 | 340 | ## `variantSources`: source semantics for layers |
|
|
341 | 341 | |
|
|
342 | 342 | ### Purpose |
|
|
343 | 343 | |
|
|
344 | 344 | `variantSources` does **not** define variants. |
|
|
345 | 345 | |
|
|
346 | 346 | It defines how a declared `Layer` contributes sources. |
|
|
347 | 347 | |
|
|
348 | 348 | In other words: |
|
|
349 | 349 | |
|
|
350 | 350 | * `variants` defines **what exists** |
|
|
351 | 351 | * `variantSources` defines **how layers become source inputs** |
|
|
352 | 352 | |
|
|
353 | 353 | This distinction is important. |
|
|
354 | 354 | |
|
|
355 | 355 | `variantSources` does not own the variant model. It interprets it. |
|
|
356 | 356 | |
|
|
357 | 357 | --- |
|
|
358 | 358 | |
|
|
359 | 359 | ## Main idea |
|
|
360 | 360 | |
|
|
361 | 361 | A layer source rule describes the source contribution of a layer. |
|
|
362 | 362 | |
|
|
363 | 363 | This is independent of any concrete variant or role. |
|
|
364 | 364 | |
|
|
365 | 365 | Conceptually: |
|
|
366 | 366 | |
|
|
367 | 367 | * `Layer -> source contribution rule` |
|
|
368 | 368 | |
|
|
369 | 369 | Examples of source contribution semantics: |
|
|
370 | 370 | |
|
|
371 | 371 | * base directory |
|
|
372 | 372 | * source directories |
|
|
373 | 373 | * logical source kinds (`ts`, `js`, `resources`) |
|
|
374 | 374 | * declared outputs (`js`, `dts`, `resources`) |
|
|
375 | 375 | |
|
|
376 | 376 | --- |
|
|
377 | 377 | |
|
|
378 | 378 | ## `variantSources` DSL example |
|
|
379 | 379 | |
|
|
380 | 380 | ```groovy |
|
|
381 | 381 | variantSources { |
|
|
382 | 382 | layerRule("main") { |
|
|
383 | 383 | from("src/main") |
|
|
384 | 384 | |
|
|
385 | 385 | set("ts") { |
|
|
386 | 386 | srcDir("ts") |
|
|
387 | 387 | } |
|
|
388 | 388 | set("js") { |
|
|
389 | 389 | srcDir("js") |
|
|
390 | 390 | } |
|
|
391 | 391 | set("resources") { |
|
|
392 | 392 | srcDir("resources") |
|
|
393 | 393 | } |
|
|
394 | 394 | |
|
|
395 | 395 | outputs("js", "dts", "resources") |
|
|
396 | 396 | } |
|
|
397 | 397 | |
|
|
398 | 398 | layerRule("test") { |
|
|
399 | 399 | from("src/test") |
|
|
400 | 400 | |
|
|
401 | 401 | set("ts") { |
|
|
402 | 402 | srcDir("ts") |
|
|
403 | 403 | } |
|
|
404 | 404 | set("resources") { |
|
|
405 | 405 | srcDir("resources") |
|
|
406 | 406 | } |
|
|
407 | 407 | |
|
|
408 | 408 | outputs("js", "dts", "resources") |
|
|
409 | 409 | } |
|
|
410 | 410 | |
|
|
411 | 411 | layerRule("rjs") { |
|
|
412 | 412 | from("src/rjs") |
|
|
413 | 413 | |
|
|
414 | 414 | set("ts") { |
|
|
415 | 415 | srcDir("ts") |
|
|
416 | 416 | } |
|
|
417 | 417 | |
|
|
418 | 418 | outputs("js", "dts") |
|
|
419 | 419 | } |
|
|
420 | 420 | |
|
|
421 | 421 | layerRule("cjs") { |
|
|
422 | 422 | from("src/cjs") |
|
|
423 | 423 | |
|
|
424 | 424 | set("ts") { |
|
|
425 | 425 | srcDir("ts") |
|
|
426 | 426 | } |
|
|
427 | 427 | |
|
|
428 | 428 | outputs("js", "dts") |
|
|
429 | 429 | } |
|
|
430 | 430 | } |
|
|
431 | 431 | ``` |
|
|
432 | 432 | |
|
|
433 | 433 | ### Interpretation |
|
|
434 | 434 | |
|
|
435 | 435 | This means: |
|
|
436 | 436 | |
|
|
437 | 437 | * `main` contributes `ts`, `js`, and `resources` |
|
|
438 | 438 | * `test` contributes `ts` and `resources` |
|
|
439 | 439 | * `rjs` contributes `ts` |
|
|
440 | 440 | * `cjs` contributes `ts` |
|
|
441 | 441 | |
|
|
442 | 442 | These are layer rules only. |
|
|
443 | 443 | |
|
|
444 | 444 | They do not yet say which variant consumes them. |
|
|
445 | 445 | |
|
|
446 | 446 | --- |
|
|
447 | 447 | |
|
|
448 | 448 | ## Why `variantSources` remains open |
|
|
449 | 449 | |
|
|
450 | 450 | Unlike `variants`, `variantSources` does not need to be closed in the same way. |
|
|
451 | 451 | |
|
|
452 | 452 | Reasons: |
|
|
453 | 453 | |
|
|
454 | 454 | * the DSL is internal to source materialization |
|
|
455 | 455 | * the source of truth for unit existence is already finalized in `VariantsView` |
|
|
456 | 456 | * `GenericSourceSetMaterializer` returns `NamedDomainObjectProvider<GenericSourceSet>` |
|
|
457 | * late configuration of providers is expected and acceptable | |
|
|
458 | 457 | * adapters may need to refine source-related behavior after `variants` is finalized |
|
|
459 | 458 | |
|
|
460 | 459 | Therefore: |
|
|
461 | 460 | |
|
|
462 | 461 | * `variants` is finalized |
|
|
463 | 462 | * `variantSources` may remain open |
|
|
464 | 463 | |
|
|
465 | 464 | This is not a contradiction. |
|
|
466 | 465 | |
|
|
467 | 466 | It reflects the difference between: |
|
|
468 | 467 | |
|
|
469 | 468 | * a closed domain model |
|
|
470 | 469 | * an open infrastructure/materialization model |
|
|
471 | 470 | |
|
|
472 | 471 | --- |
|
|
473 | 472 | |
|
|
473 | ## Late configuration policy | |
|
|
474 | ||
|
|
475 | Openness of `variantSources` does not mean that late configuration is | |
|
|
476 | semantically neutral. | |
|
|
477 | ||
|
|
478 | Selector rules may be added after the finalized context becomes available, but | |
|
|
479 | their behavior against already materialized `GenericSourceSet` objects must be | |
|
|
480 | controlled explicitly. | |
|
|
481 | ||
|
|
482 | Conceptually, `variantSources` exposes a policy choice such as: | |
|
|
483 | ||
|
|
484 | ```groovy | |
|
|
485 | variantSources { | |
|
|
486 | lateConfigurationPolicy { | |
|
|
487 | failOnLateConfiguration() | |
|
|
488 | } | |
|
|
489 | } | |
|
|
490 | ``` | |
|
|
491 | ||
|
|
492 | Available modes are: | |
|
|
493 | ||
|
|
494 | * `failOnLateConfiguration()` | |
|
|
495 | * `warnOnLateConfiguration()` | |
|
|
496 | * `allowLateConfiguration()` | |
|
|
497 | ||
|
|
498 | Meaning: | |
|
|
499 | ||
|
|
500 | * `fail` rejects selector rules that target already materialized source sets | |
|
|
501 | * `warn` allows them but emits a warning | |
|
|
502 | * `allow` allows them silently | |
|
|
503 | ||
|
|
504 | This policy is intentionally modeled as an imperative choice, not as a mutable | |
|
|
505 | property: | |
|
|
506 | ||
|
|
507 | * it must be chosen before the first selector rule is added | |
|
|
508 | * selector rules here mean `variant(...)`, `layer(...)`, and `unit(...)` | |
|
|
509 | * once chosen, it cannot be changed later | |
|
|
510 | * it controls runtime behavior, not just a stored value | |
|
|
511 | ||
|
|
512 | For source sets configured before materialization, selector precedence remains: | |
|
|
513 | ||
|
|
514 | ```text | |
|
|
515 | variant < layer < unit | |
|
|
516 | ``` | |
|
|
517 | ||
|
|
518 | For already materialized source sets in `warn` and `allow` modes: | |
|
|
519 | ||
|
|
520 | * the late action is applied as an imperative follow-up step | |
|
|
521 | * selector precedence is not reconstructed retroactively | |
|
|
522 | * actual observation order is the order in which late actions are registered | |
|
|
523 | ||
|
|
524 | --- | |
|
|
525 | ||
|
|
474 | 526 | ## `VariantSourcesContext` |
|
|
475 | 527 | |
|
|
476 | 528 | `variantSources.whenFinalized(...)` remains useful, but not because `variantSources` itself is frozen. |
|
|
477 | 529 | |
|
|
478 | 530 | Its purpose is to provide access to a finalized context derived from `variants`. |
|
|
479 | 531 | |
|
|
480 | 532 | This context contains: |
|
|
481 | 533 | |
|
|
482 | 534 | * `CompileUnitsView` |
|
|
483 | 535 | * `RoleProjectionsView` |
|
|
484 | 536 | * `GenericSourceSetMaterializer` |
|
|
485 | 537 | |
|
|
486 | 538 | Conceptually: |
|
|
487 | 539 | |
|
|
488 | 540 | ```java |
|
|
489 | 541 | interface VariantSourcesContext { |
|
|
490 | 542 | CompileUnitsView getCompileUnits(); |
|
|
491 | 543 | RoleProjectionsView getRoleProjections(); |
|
|
492 | 544 | GenericSourceSetMaterializer getSourceSets(); |
|
|
493 | 545 | } |
|
|
494 | 546 | ``` |
|
|
495 | 547 | |
|
|
496 | 548 | This callback is also replayable. |
|
|
497 | 549 | |
|
|
498 | 550 | Example: |
|
|
499 | 551 | |
|
|
500 | 552 | ```java |
|
|
501 | 553 | variantSources.whenFinalized(ctx -> { |
|
|
502 | 554 | var units = ctx.getCompileUnits(); |
|
|
503 | 555 | var roles = ctx.getRoleProjections(); |
|
|
504 | 556 | var sourceSets = ctx.getSourceSets(); |
|
|
505 | 557 | }); |
|
|
506 | 558 | ``` |
|
|
507 | 559 | |
|
|
508 | 560 | --- |
|
|
509 | 561 | |
|
|
510 | 562 | ## `GenericSourceSetMaterializer` |
|
|
511 | 563 | |
|
|
512 | 564 | ### Purpose |
|
|
513 | 565 | |
|
|
514 | 566 | `GenericSourceSetMaterializer` is the official source of truth for materialized source sets. |
|
|
515 | 567 | |
|
|
516 | 568 | It is responsible for: |
|
|
517 | 569 | |
|
|
518 | 570 | * lazy creation of `GenericSourceSet` |
|
|
519 | 571 | * applying `layerRule` |
|
|
520 | 572 | * connecting a compile unit to a source set provider |
|
|
521 | 573 | * exposing source sets to adapters |
|
|
522 | 574 | |
|
|
523 | 575 | This is the correct place to apply `layerRule`. |
|
|
524 | 576 | |
|
|
525 | 577 | Adapters should not apply layer rules themselves. |
|
|
526 | 578 | |
|
|
527 | 579 | ### Conceptual API |
|
|
528 | 580 | |
|
|
529 | 581 | ```java |
|
|
530 | 582 | interface GenericSourceSetMaterializer { |
|
|
531 | 583 | NamedDomainObjectProvider<GenericSourceSet> getSourceSet(CompileUnit unit); |
|
|
532 | 584 | } |
|
|
533 | 585 | ``` |
|
|
534 | 586 | |
|
|
535 | 587 | --- |
|
|
536 | 588 | |
|
|
537 | 589 | ## Why `GenericSourceSetMaterializer` should own `layerRule` application |
|
|
538 | 590 | |
|
|
539 | 591 | If adapters applied `layerRule` directly, responsibility would leak across multiple layers: |
|
|
540 | 592 | |
|
|
541 | 593 | * one component would know compile units |
|
|
542 | 594 | * another would know source semantics |
|
|
543 | 595 | * another would know how to configure `GenericSourceSet` |
|
|
544 | 596 | |
|
|
545 | 597 | This would make the model harder to reason about. |
|
|
546 | 598 | |
|
|
547 | 599 | Instead: |
|
|
548 | 600 | |
|
|
549 | 601 | * `layerRule` is DSL/spec-level |
|
|
550 | 602 | * `GenericSourceSetMaterializer` is execution/materialization-level |
|
|
551 | 603 | * adapters are consumption-level |
|
|
552 | 604 | |
|
|
553 | 605 | This gives a much cleaner separation. |
|
|
554 | 606 | |
|
|
555 | 607 | --- |
|
|
556 | 608 | |
|
|
557 | 609 | ## `GenericSourceSet` as materialization target |
|
|
558 | 610 | |
|
|
559 | 611 | `GenericSourceSet` is the materialized source aggregation object. |
|
|
560 | 612 | |
|
|
561 | 613 | It is a good fit because it can represent: |
|
|
562 | 614 | |
|
|
563 | 615 | * multiple logical source sets |
|
|
564 | 616 | * aggregated source directories |
|
|
565 | 617 | * declared outputs |
|
|
566 | 618 | * lazy registration through providers |
|
|
567 | 619 | |
|
|
568 | 620 | The materializer is therefore the owner of: |
|
|
569 | 621 | |
|
|
570 | 622 | * creating `GenericSourceSet` |
|
|
571 | 623 | * populating its source sets |
|
|
572 | 624 | * declaring outputs |
|
|
573 | 625 | * returning a provider for later use |
|
|
574 | 626 | |
|
|
575 | 627 | --- |
|
|
576 | 628 | |
|
|
577 | 629 | ## How an adapter should use the model |
|
|
578 | 630 | |
|
|
579 | 631 | Example: |
|
|
580 | 632 | |
|
|
581 | 633 | ```java |
|
|
582 | 634 | variantSources.whenFinalized(ctx -> { |
|
|
583 | 635 | for (CompileUnit unit : ctx.getCompileUnits().getUnits()) { |
|
|
584 | 636 | var sourceSetProvider = ctx.getSourceSets().getSourceSet(unit); |
|
|
585 | 637 | |
|
|
586 | 638 | var variant = unit.variant(); |
|
|
587 | 639 | var layer = unit.layer(); |
|
|
588 | 640 | |
|
|
589 | 641 | // create compile task for this compile unit |
|
|
590 | 642 | // configure compiler options from variant semantics |
|
|
591 | 643 | // use sourceSetProvider as task input |
|
|
592 | 644 | } |
|
|
593 | 645 | |
|
|
594 | 646 | for (RoleProjection projection : ctx.getRoleProjections().getProjections()) { |
|
|
595 | 647 | var units = ctx.getRoleProjections().getUnits(projection); |
|
|
596 | 648 | |
|
|
597 | 649 | // aggregate outputs of included compile units |
|
|
598 | 650 | // use for publication or assembly |
|
|
599 | 651 | } |
|
|
600 | 652 | }); |
|
|
601 | 653 | ``` |
|
|
602 | 654 | |
|
|
603 | 655 | --- |
|
|
604 | 656 | |
|
|
605 | 657 | ## Why compile unit is `(variant, layer)` and not `(variant, role)` or `(variant, role, layer)` |
|
|
606 | 658 | |
|
|
607 | 659 | ### Not `(variant, role)` |
|
|
608 | 660 | |
|
|
609 | 661 | Because role is not a compilation boundary. |
|
|
610 | 662 | |
|
|
611 | 663 | Role is a logical grouping of results. |
|
|
612 | 664 | |
|
|
613 | 665 | ### Not `(variant, role, layer)` |
|
|
614 | 666 | |
|
|
615 | 667 | Because role does not define the compile unit itself. |
|
|
616 | 668 | |
|
|
617 | 669 | A compile unit is a unit of compilation, not a unit of publication grouping. |
|
|
618 | 670 | |
|
|
619 | 671 | ### Correct interpretation |
|
|
620 | 672 | |
|
|
621 | 673 | * `(variant, layer)` = compile unit |
|
|
622 | 674 | * `(variant, role)` = logical result group |
|
|
623 | 675 | * `(variant, role, layer)` = membership relation between them |
|
|
624 | 676 | |
|
|
625 | 677 | This is the most coherent separation. |
|
|
626 | 678 | |
|
|
627 | 679 | --- |
|
|
628 | 680 | |
|
|
629 | 681 | ## Model boundaries |
|
|
630 | 682 | |
|
|
631 | 683 | ### What belongs to `variants` |
|
|
632 | 684 | |
|
|
633 | 685 | * declared domains: `Layer`, `Role`, `Variant` |
|
|
634 | 686 | * normalized relation `(variant, role, layer)` |
|
|
635 | 687 | * finalization lifecycle |
|
|
636 | 688 | * finalized `VariantsView` |
|
|
637 | 689 | |
|
|
638 | 690 | ### What belongs to derived views |
|
|
639 | 691 | |
|
|
640 | 692 | * compile units: `(variant, layer)` |
|
|
641 | 693 | * role projections: `(variant, role)` |
|
|
642 | 694 | |
|
|
643 | 695 | ### What belongs to `variantSources` |
|
|
644 | 696 | |
|
|
645 | 697 | * source semantics of layers |
|
|
646 | 698 | * source materialization rules |
|
|
647 | 699 | * lazy `GenericSourceSet` provisioning |
|
|
648 | 700 | * source adapter integration |
|
|
649 | 701 | |
|
|
650 | 702 | ### What does not belong to `variants` |
|
|
651 | 703 | |
|
|
652 | 704 | * source directories |
|
|
653 | 705 | * base paths |
|
|
654 | 706 | * output declarations |
|
|
655 | 707 | * source set layout |
|
|
656 | 708 | * task registration |
|
|
657 | 709 | * compiler-specific assumptions |
|
|
658 | 710 | |
|
|
659 | 711 | --- |
|
|
660 | 712 | |
|
|
661 | 713 | ## Design principles |
|
|
662 | 714 | |
|
|
663 | 715 | ### 1. Keep the core model small |
|
|
664 | 716 | |
|
|
665 | 717 | The core model should only contain domain facts. |
|
|
666 | 718 | |
|
|
667 | 719 | ### 2. Separate domain truth from materialization |
|
|
668 | 720 | |
|
|
669 | 721 | The existence of compile units comes from `VariantsView`, not from source rules. |
|
|
670 | 722 | |
|
|
671 | 723 | ### 3. Treat source materialization as infrastructure |
|
|
672 | 724 | |
|
|
673 | 725 | `variantSources` is an interpretation layer, not the source of truth. |
|
|
674 | 726 | |
|
|
675 | 727 | ### 4. Prefer replayable finalized hooks |
|
|
676 | 728 | |
|
|
677 | 729 | Adapters should not depend on raw Gradle lifecycle callbacks such as `afterEvaluate`. |
|
|
678 | 730 | |
|
|
679 | ### 5. Keep heavy runtime objects behind providers | |
|
|
731 | ### 5. Make late behavior explicit | |
|
|
732 | ||
|
|
733 | Late configuration after materialization is a policy decision, not an implicit | |
|
|
734 | guarantee. | |
|
|
735 | ||
|
|
736 | ### 6. Keep heavy runtime objects behind providers | |
|
|
680 | 737 | |
|
|
681 | 738 | Materialized `GenericSourceSet` objects should remain behind a lazy API. |
|
|
682 | 739 | |
|
|
683 | 740 | --- |
|
|
684 | 741 | |
|
|
685 | 742 | ## Summary |
|
|
686 | 743 | |
|
|
687 | 744 | The model is intentionally split into two layers. |
|
|
688 | 745 | |
|
|
689 | 746 | ### `variants` |
|
|
690 | 747 | |
|
|
691 | 748 | A closed, finalized domain model: |
|
|
692 | 749 | |
|
|
693 | 750 | * `Layer` |
|
|
694 | 751 | * `Role` |
|
|
695 | 752 | * `Variant` |
|
|
696 | 753 | * `(variant, role, layer)` |
|
|
697 | 754 | |
|
|
698 | 755 | ### `variantSources` |
|
|
699 | 756 | |
|
|
700 | 757 | An open, source-materialization layer: |
|
|
701 | 758 | |
|
|
702 | 759 | * layer source rules |
|
|
703 | 760 | * compile-unit source set materialization |
|
|
704 | 761 | * adapter-facing `GenericSourceSet` providers |
|
|
705 | 762 | |
|
|
706 | 763 | ### Derived views |
|
|
707 | 764 | |
|
|
708 | 765 | From the finalized variant model: |
|
|
709 | 766 | |
|
|
710 | 767 | * `CompileUnitsView`: `(variant, layer)` |
|
|
711 | 768 | * `RoleProjectionsView`: `(variant, role)` |
|
|
712 | 769 | |
|
|
713 | 770 | ### Operational interpretation |
|
|
714 | 771 | |
|
|
715 | 772 | * `variant` defines compilation semantics |
|
|
716 | 773 | * `layer` partitions compilation |
|
|
717 | 774 | * `role` groups results by purpose |
|
|
718 | 775 | |
|
|
719 | 776 | This keeps the core model stable and minimal, while allowing source handling and adapter integration to remain flexible. |
|
|
1 | NO CONTENT: file was removed |
General Comments 0
You need to be logged in to leave comments.
Login now
