##// END OF EJS Templates
separated SourceSetRegistration, SourceSetUsageBinding
cin -
r31:414a5d71eaa5 default
parent child
Show More
@@ -0,0 +1,33
1 package org.implab.gradle.common.sources;
2
3 import org.implab.gradle.common.core.lang.Closures;
4 import org.gradle.api.Action;
5 import org.gradle.api.NamedDomainObjectProvider;
6
7 import groovy.lang.Closure;
8 import groovy.lang.DelegatesTo;
9
10 /**
11 * Immutable payload for a newly registered {@link GenericSourceSet}.
12 *
13 * <p>Used as callback payload for
14 * {@link VariantSourcesExtension#whenRegistered(org.gradle.api.Action)} and
15 * {@link BuildLayerBinding#whenRegistered(org.gradle.api.Action)}.
16 *
17 * @param layerName normalized layer name that owns the registration
18 * @param sourceSetName source-set name registered in the container
19 * @param sourceSet provider of the registered source set (realized later by Gradle on demand)
20 */
21 public record SourceSetRegistration(
22 String layerName,
23 String sourceSetName,
24 NamedDomainObjectProvider<GenericSourceSet> sourceSet) {
25 public void configureSourceSet(Action<? super GenericSourceSet> action) {
26 sourceSet.configure(action);
27 }
28
29 public void configureSourceSet(
30 @DelegatesTo(value = GenericSourceSet.class, strategy = Closure.DELEGATE_FIRST) Closure<?> action) {
31 configureSourceSet(Closures.action(action));
32 }
33 }
@@ -0,0 +1,37
1 package org.implab.gradle.common.sources;
2
3 import org.implab.gradle.common.core.lang.Closures;
4 import org.gradle.api.Action;
5 import org.gradle.api.NamedDomainObjectProvider;
6
7 import groovy.lang.Closure;
8 import groovy.lang.DelegatesTo;
9
10 /**
11 * Immutable payload for a resolved variant/role/layer usage bound to a source set.
12 *
13 * <p>Used as callback payload for
14 * {@link VariantSourcesExtension#whenBound(org.gradle.api.Action)} and
15 * {@link BuildLayerBinding#whenBound(org.gradle.api.Action)}.
16 *
17 * @param variantName variant name from the build-variants model
18 * @param roleName role name inside the resolved variant
19 * @param layerName normalized layer name used to resolve the source set
20 * @param sourceSetName source-set name registered in the container
21 * @param sourceSet provider of the registered source set (realized later by Gradle on demand)
22 */
23 public record SourceSetUsageBinding(
24 String variantName,
25 String roleName,
26 String layerName,
27 String sourceSetName,
28 NamedDomainObjectProvider<GenericSourceSet> sourceSet) {
29 public void configureSourceSet(Action<? super GenericSourceSet> action) {
30 sourceSet.configure(action);
31 }
32
33 public void configureSourceSet(
34 @DelegatesTo(value = GenericSourceSet.class, strategy = Closure.DELEGATE_FIRST) Closure<?> action) {
35 configureSourceSet(Closures.action(action));
36 }
37 }
@@ -1,132 +1,134
1 # Gradle Common Sources Model
1 # Gradle Common Sources Model
2
2
3 ## NAME
3 ## NAME
4
4
5 `gradle-common/common` — набор плагинов для моделирования вариантов сборки,
5 `gradle-common/common` — набор плагинов для моделирования вариантов сборки,
6 регистрации source sets и интеграции этой модели с toolchain-адаптерами.
6 регистрации source sets и интеграции этой модели с toolchain-адаптерами.
7
7
8 ## SYNOPSIS
8 ## SYNOPSIS
9
9
10 ```groovy
10 ```groovy
11 plugins {
11 plugins {
12 id 'org.implab.gradle-variants-sources'
12 id 'org.implab.gradle-variants-sources'
13 }
13 }
14
14
15 variants {
15 variants {
16 layer('mainBase')
16 layer('mainBase')
17 layer('mainAmd')
17 layer('mainAmd')
18
18
19 variant('browser') {
19 variant('browser') {
20 role('main') { layers('mainBase', 'mainAmd') }
20 role('main') { layers('mainBase', 'mainAmd') }
21 }
21 }
22 }
22 }
23
23
24 variantSources {
24 variantSources {
25 bind('mainBase') {
25 bind('mainBase') {
26 configureSourceSet {
26 configureSourceSet {
27 declareOutputs('compiled')
27 declareOutputs('compiled')
28 }
28 }
29 }
29 }
30
30
31 bind('mainAmd').sourceSetNamePattern = '{variant}{layerCap}'
31 bind('mainAmd').sourceSetNamePattern = '{variant}{layerCap}'
32
32
33 whenRegistered { sourceSetName() }
33 whenRegistered { sourceSetName() }
34
34
35 whenBound { ctx ->
35 whenBound { ctx ->
36 ctx.configureSourceSet {
36 ctx.configureSourceSet {
37 declareOutputs('typings')
37 declareOutputs('typings')
38 }
38 }
39 }
39 }
40 }
40 }
41 ```
41 ```
42
42
43 ## DESCRIPTION
43 ## DESCRIPTION
44
44
45 Модуль состоит из трех логических частей:
45 Модуль состоит из трех логических частей:
46
46
47 - `variants` — декларативная доменная модель сборки;
47 - `variants` — декларативная доменная модель сборки;
48 - `sources` — модель физически регистрируемых source sets;
48 - `sources` — модель физически регистрируемых source sets;
49 - `variantSources` — адаптер, который связывает первые две модели.
49 - `variantSources` — адаптер, который связывает первые две модели.
50
50
51 Ниже раскрытие каждой части.
51 Ниже раскрытие каждой части.
52
52
53 ### variants
53 ### variants
54
54
55 `variants` задает структуру пространства сборки: какие есть слои, какие роли
55 `variants` задает структуру пространства сборки: какие есть слои, какие роли
56 используют эти слои в каждом варианте, какие есть атрибуты и artifact slots.
56 используют эти слои в каждом варианте, какие есть атрибуты и artifact slots.
57 Модель не создает задачи и не привязана к TS/JS.
57 Модель не создает задачи и не привязана к TS/JS.
58
58
59 Практический смысл:
59 Практический смысл:
60
60
61 - формализовать архитектуру сборки;
61 - формализовать архитектуру сборки;
62 - дать адаптерам единый источник правды.
62 - дать адаптерам единый источник правды.
63
63
64 ### sources
64 ### sources
65
65
66 `sources` описывает независимые source sets (`GenericSourceSet`) с именованными
66 `sources` описывает независимые source sets (`GenericSourceSet`) с именованными
67 outputs. Это уже "физический" уровень, к которому удобно привязывать задачи,
67 outputs. Это уже "физический" уровень, к которому удобно привязывать задачи,
68 артефакты и task inputs/outputs.
68 артефакты и task inputs/outputs.
69
69
70 Практический смысл:
70 Практический смысл:
71
71
72 - создать единый контракт по входам/выходам;
72 - создать единый контракт по входам/выходам;
73 - регистрировать результаты задач как outputs source set;
73 - регистрировать результаты задач как outputs source set;
74 - минимизировать ручные `dependsOn` за счет модели outputs.
74 - минимизировать ручные `dependsOn` за счет модели outputs.
75
75
76 ### variantSources
76 ### variantSources
77
77
78 `variantSources` регистрирует source sets на основе `variants`, применяет
78 `variantSources` регистрирует source sets на основе `variants`, применяет
79 конфигурацию layer-bindings и отдает события (`whenRegistered`, `whenBound`) для
79 конфигурацию layer-bindings и отдает события (`whenRegistered`, `whenBound`) для
80 адаптеров других плагинов.
80 адаптеров других плагинов.
81
81
82 Практический смысл:
82 Практический смысл:
83
83
84 - переводить логическую модель `variants` в executable-модель `sources`;
84 - переводить логическую модель `variants` в executable-модель `sources`;
85 - навешивать политики toolchain на зарегистрированные source sets;
85 - навешивать политики toolchain на зарегистрированные source sets;
86 - синхронизировать плагины через replayable callback-контракт.
86 - синхронизировать плагины через replayable callback-контракт.
87
87
88 ## DOMAIN MODEL
88 ## DOMAIN MODEL
89
89
90 - `BuildLayer` — глобальный идентификатор слоя.
90 - `BuildLayer` — глобальный идентификатор слоя.
91 - `BuildVariant` — агрегат ролей, атрибутов, артефактных слотов.
91 - `BuildVariant` — агрегат ролей, атрибутов, артефактных слотов.
92 - `BuildRole` — роль внутри варианта, содержит ссылки на layer names.
92 - `BuildRole` — роль внутри варианта, содержит ссылки на layer names.
93 - `GenericSourceSet` — зарегистрированный набор исходников и outputs.
93 - `GenericSourceSet` — зарегистрированный набор исходников и outputs.
94 - `BuildLayerBinding` — правила registration source set для конкретного layer.
94 - `BuildLayerBinding` — правила registration source set для конкретного layer.
95 - `SourceSetContext` — контекст callback-событий registration.
95 - `SourceSetRegistration` — payload события регистрации source set.
96 - `SourceSetUsageBinding` — payload события usage-binding.
96
97
97 ## EVENT CONTRACT
98 ## EVENT CONTRACT
98
99
99 - `whenRegistered`:
100 - `whenRegistered`:
100 - событие нового уникального source set name;
101 - событие нового уникального source set name;
101 - replayable.
102 - replayable.
102 - `whenBound`:
103 - `whenBound`:
103 - событие каждой usage-связки `variant/role/layer`;
104 - событие каждой usage-связки `variant/role/layer`;
104 - replayable.
105 - replayable.
105
106
106 Closure callbacks работают в delegate-first режиме (`@DelegatesTo`). Для
107 Closure callbacks работают в delegate-first режиме (`@DelegatesTo`). Для
107 вложенных closure рекомендуется явный параметр (`ctx -> ...`).
108 вложенных closure рекомендуется явный параметр (`ctx -> ...`).
108
109
109 ## KEY CLASSES
110 ## KEY CLASSES
110
111
111 - `SourcesPlugin` — регистрирует extension `sources`.
112 - `SourcesPlugin` — регистрирует extension `sources`.
112 - `GenericSourceSet` — модель источников/outputs для конкретного имени.
113 - `GenericSourceSet` — модель источников/outputs для конкретного имени.
113 - `VariantsPlugin` — регистрирует extension `variants` и lifecycle finalize.
114 - `VariantsPlugin` — регистрирует extension `variants` и lifecycle finalize.
114 - `BuildVariantsExtension` — корневой API модели вариантов.
115 - `BuildVariantsExtension` — корневой API модели вариантов.
115 - `BuildVariant` — API ролей, attributes и artifact slots варианта.
116 - `BuildVariant` — API ролей, attributes и artifact slots варианта.
116 - `VariantsSourcesPlugin` — применяет `variants` + `sources` и запускает адаптер.
117 - `VariantsSourcesPlugin` — применяет `variants` + `sources` и запускает адаптер.
117 - `VariantSourcesExtension` — API bind/events registration.
118 - `VariantSourcesExtension` — API bind/events registration.
118 - `BuildLayerBinding` — слой-конкретный DSL для имени и конфигурации source set.
119 - `BuildLayerBinding` — слой-конкретный DSL для имени и конфигурации source set.
119 - `SourceSetContext` — payload событий и sugar `configureSourceSet(...)`.
120 - `SourceSetRegistration` — payload `whenRegistered(...)`.
121 - `SourceSetUsageBinding` — payload `whenBound(...)`.
120
122
121 ## NOTES
123 ## NOTES
122
124
123 - Marker ids:
125 - Marker ids:
124 - `org.implab.gradle-variants`
126 - `org.implab.gradle-variants`
125 - `org.implab.gradle-variants-sources`
127 - `org.implab.gradle-variants-sources`
126 - `SourcesPlugin` пока class-only (без marker id).
128 - `SourcesPlugin` пока class-only (без marker id).
127
129
128 ## SEE ALSO
130 ## SEE ALSO
129
131
130 - `sources-plugin.md`
132 - `sources-plugin.md`
131 - `variants-plugin.md`
133 - `variants-plugin.md`
132 - `variant-sources-plugin.md`
134 - `variant-sources-plugin.md`
@@ -1,120 +1,120
1 package org.implab.gradle.common.sources;
1 package org.implab.gradle.common.sources;
2
2
3 import java.util.ArrayList;
3 import java.util.ArrayList;
4 import java.util.LinkedHashSet;
4 import java.util.LinkedHashSet;
5 import java.util.List;
5 import java.util.List;
6 import java.util.Set;
6 import java.util.Set;
7
7
8 import javax.inject.Inject;
8 import javax.inject.Inject;
9
9
10 import org.implab.gradle.common.core.lang.Closures;
10 import org.implab.gradle.common.core.lang.Closures;
11 import org.gradle.api.Action;
11 import org.gradle.api.Action;
12 import org.gradle.api.Named;
12 import org.gradle.api.Named;
13 import org.gradle.api.NamedDomainObjectProvider;
13 import org.gradle.api.NamedDomainObjectProvider;
14 import org.gradle.api.provider.Property;
14 import org.gradle.api.provider.Property;
15
15
16 import groovy.lang.Closure;
16 import groovy.lang.Closure;
17 import groovy.lang.DelegatesTo;
17 import groovy.lang.DelegatesTo;
18
18
19 /**
19 /**
20 * Maps a logical layer to per-source-set hooks.
20 * Maps a logical layer to per-source-set hooks.
21 */
21 */
22 public abstract class BuildLayerBinding implements Named {
22 public abstract class BuildLayerBinding implements Named {
23 public static final String DEFAULT_SOURCE_SET_NAME_PATTERN = "{variant}{layerCap}";
23 public static final String DEFAULT_SOURCE_SET_NAME_PATTERN = "{variant}{layerCap}";
24
24
25 private final String name;
25 private final String name;
26
26
27 private final List<Action<? super GenericSourceSet>> sourceSetConfigureActions = new ArrayList<>();
27 private final List<Action<? super GenericSourceSet>> sourceSetConfigureActions = new ArrayList<>();
28 private final List<Action<? super SourceSetContext>> registeredActions = new ArrayList<>();
28 private final List<Action<? super SourceSetRegistration>> registeredActions = new ArrayList<>();
29 private final List<Action<? super SourceSetContext>> boundActions = new ArrayList<>();
29 private final List<Action<? super SourceSetUsageBinding>> boundActions = new ArrayList<>();
30 private final List<NamedDomainObjectProvider<GenericSourceSet>> registeredSourceSets = new ArrayList<>();
30 private final List<NamedDomainObjectProvider<GenericSourceSet>> registeredSourceSets = new ArrayList<>();
31 private final List<SourceSetContext> registeredContexts = new ArrayList<>();
31 private final List<SourceSetRegistration> registeredContexts = new ArrayList<>();
32 private final List<SourceSetContext> boundContexts = new ArrayList<>();
32 private final List<SourceSetUsageBinding> boundContexts = new ArrayList<>();
33 private final Set<String> registeredSourceSetNames = new LinkedHashSet<>();
33 private final Set<String> registeredSourceSetNames = new LinkedHashSet<>();
34
34
35 @Inject
35 @Inject
36 public BuildLayerBinding(String name) {
36 public BuildLayerBinding(String name) {
37 this.name = name;
37 this.name = name;
38 getSourceSetNamePattern().convention(DEFAULT_SOURCE_SET_NAME_PATTERN);
38 getSourceSetNamePattern().convention(DEFAULT_SOURCE_SET_NAME_PATTERN);
39 }
39 }
40
40
41 @Override
41 @Override
42 public String getName() {
42 public String getName() {
43 return name;
43 return name;
44 }
44 }
45
45
46 public abstract Property<String> getSourceSetNamePattern();
46 public abstract Property<String> getSourceSetNamePattern();
47
47
48 /**
48 /**
49 * Action applied to every registered source set for this layer.
49 * Action applied to every registered source set for this layer.
50 * Already registered source sets are configured immediately (replay).
50 * Already registered source sets are configured immediately (replay).
51 */
51 */
52 public void configureSourceSet(Action<? super GenericSourceSet> configure) {
52 public void configureSourceSet(Action<? super GenericSourceSet> configure) {
53 sourceSetConfigureActions.add(configure);
53 sourceSetConfigureActions.add(configure);
54 for (var sourceSet : registeredSourceSets)
54 for (var sourceSet : registeredSourceSets)
55 sourceSet.configure(configure);
55 sourceSet.configure(configure);
56 }
56 }
57
57
58 public void configureSourceSet(
58 public void configureSourceSet(
59 @DelegatesTo(value = GenericSourceSet.class, strategy = Closure.DELEGATE_FIRST) Closure<?> configure) {
59 @DelegatesTo(value = GenericSourceSet.class, strategy = Closure.DELEGATE_FIRST) Closure<?> configure) {
60 configureSourceSet(Closures.action(configure));
60 configureSourceSet(Closures.action(configure));
61 }
61 }
62
62
63 /**
63 /**
64 * Layer-local callback fired after source-set registration.
64 * Layer-local callback fired after source-set registration.
65 * Already emitted contexts are delivered immediately (replay).
65 * Already emitted registrations are delivered immediately (replay).
66 * For simple callbacks you can use delegate-only style
66 * For simple callbacks you can use delegate-only style
67 * (for example {@code whenRegistered { sourceSetName() }}).
67 * (for example {@code whenRegistered { sourceSetName() }}).
68 * For nested closures prefer explicit parameter
68 * For nested closures prefer explicit parameter
69 * ({@code whenRegistered { ctx -> ... }}).
69 * ({@code whenRegistered { ctx -> ... }}).
70 */
70 */
71 public void whenRegistered(Action<? super SourceSetContext> action) {
71 public void whenRegistered(Action<? super SourceSetRegistration> action) {
72 registeredActions.add(action);
72 registeredActions.add(action);
73 for (var context : registeredContexts)
73 for (var context : registeredContexts)
74 action.execute(context);
74 action.execute(context);
75 }
75 }
76
76
77 public void whenRegistered(
77 public void whenRegistered(
78 @DelegatesTo(value = SourceSetContext.class, strategy = Closure.DELEGATE_FIRST) Closure<?> action) {
78 @DelegatesTo(value = SourceSetRegistration.class, strategy = Closure.DELEGATE_FIRST) Closure<?> action) {
79 whenRegistered(Closures.action(action));
79 whenRegistered(Closures.action(action));
80 }
80 }
81
81
82 /**
82 /**
83 * Layer-local callback fired for every resolved variant/role/layer usage.
83 * Layer-local callback fired for every resolved variant/role/layer usage.
84 * Already emitted contexts are delivered immediately (replay).
84 * Already emitted usage bindings are delivered immediately (replay).
85 * For simple callbacks you can use delegate-only style
85 * For simple callbacks you can use delegate-only style
86 * (for example {@code whenBound { variantName() }}).
86 * (for example {@code whenBound { variantName() }}).
87 * For nested closures prefer explicit parameter
87 * For nested closures prefer explicit parameter
88 * ({@code whenBound { ctx -> ... }}).
88 * ({@code whenBound { ctx -> ... }}).
89 */
89 */
90 public void whenBound(Action<? super SourceSetContext> action) {
90 public void whenBound(Action<? super SourceSetUsageBinding> action) {
91 boundActions.add(action);
91 boundActions.add(action);
92 for (var context : boundContexts)
92 for (var context : boundContexts)
93 action.execute(context);
93 action.execute(context);
94 }
94 }
95
95
96 public void whenBound(
96 public void whenBound(
97 @DelegatesTo(value = SourceSetContext.class, strategy = Closure.DELEGATE_FIRST) Closure<?> action) {
97 @DelegatesTo(value = SourceSetUsageBinding.class, strategy = Closure.DELEGATE_FIRST) Closure<?> action) {
98 whenBound(Closures.action(action));
98 whenBound(Closures.action(action));
99 }
99 }
100
100
101 void notifyRegistered(SourceSetContext context) {
101 void notifyRegistered(SourceSetRegistration registration) {
102 if (registeredSourceSetNames.add(context.sourceSetName())) {
102 if (registeredSourceSetNames.add(registration.sourceSetName())) {
103 var sourceSet = context.sourceSet();
103 var sourceSet = registration.sourceSet();
104 registeredSourceSets.add(sourceSet);
104 registeredSourceSets.add(sourceSet);
105
105
106 for (var action : sourceSetConfigureActions)
106 for (var action : sourceSetConfigureActions)
107 sourceSet.configure(action);
107 sourceSet.configure(action);
108 }
108 }
109
109
110 registeredContexts.add(context);
110 registeredContexts.add(registration);
111 for (var action : registeredActions)
111 for (var action : registeredActions)
112 action.execute(context);
112 action.execute(registration);
113 }
113 }
114
114
115 void notifyBound(SourceSetContext context) {
115 void notifyBound(SourceSetUsageBinding binding) {
116 boundContexts.add(context);
116 boundContexts.add(binding);
117 for (var action : boundActions)
117 for (var action : boundActions)
118 action.execute(context);
118 action.execute(binding);
119 }
119 }
120 }
120 }
@@ -1,303 +1,297
1 package org.implab.gradle.common.sources;
1 package org.implab.gradle.common.sources;
2
2
3 import java.util.ArrayList;
3 import java.util.ArrayList;
4 import java.util.LinkedHashMap;
4 import java.util.LinkedHashMap;
5 import java.util.List;
5 import java.util.List;
6 import java.util.regex.Matcher;
6 import java.util.regex.Matcher;
7 import java.util.regex.Pattern;
7 import java.util.regex.Pattern;
8 import java.util.stream.Stream;
8 import java.util.stream.Stream;
9
9
10 import javax.inject.Inject;
10 import javax.inject.Inject;
11
11
12 import org.implab.gradle.common.core.lang.Closures;
12 import org.implab.gradle.common.core.lang.Closures;
13 import org.implab.gradle.common.core.lang.Strings;
13 import org.implab.gradle.common.core.lang.Strings;
14 import org.eclipse.jdt.annotation.NonNullByDefault;
14 import org.eclipse.jdt.annotation.NonNullByDefault;
15 import org.eclipse.jdt.annotation.Nullable;
15 import org.eclipse.jdt.annotation.Nullable;
16 import org.gradle.api.Action;
16 import org.gradle.api.Action;
17 import org.gradle.api.InvalidUserDataException;
17 import org.gradle.api.InvalidUserDataException;
18 import org.gradle.api.NamedDomainObjectContainer;
18 import org.gradle.api.NamedDomainObjectContainer;
19 import org.gradle.api.NamedDomainObjectProvider;
19 import org.gradle.api.NamedDomainObjectProvider;
20 import org.gradle.api.model.ObjectFactory;
20 import org.gradle.api.model.ObjectFactory;
21 import org.gradle.api.logging.Logger;
21 import org.gradle.api.logging.Logger;
22 import org.gradle.api.logging.Logging;
22 import org.gradle.api.logging.Logging;
23
23
24 import groovy.lang.Closure;
24 import groovy.lang.Closure;
25 import groovy.lang.DelegatesTo;
25 import groovy.lang.DelegatesTo;
26
26
27 import static org.implab.gradle.common.core.lang.Strings.sanitizeName;
27 import static org.implab.gradle.common.core.lang.Strings.sanitizeName;
28
28
29 /**
29 /**
30 * Adapter extension that registers source sets for variant/layer pairs.
30 * Adapter extension that registers source sets for variant/layer pairs.
31 */
31 */
32 @NonNullByDefault
32 @NonNullByDefault
33 public abstract class VariantSourcesExtension {
33 public abstract class VariantSourcesExtension {
34 private static final Logger logger = Logging.getLogger(VariantSourcesExtension.class);
34 private static final Logger logger = Logging.getLogger(VariantSourcesExtension.class);
35 private static final Pattern SOURCE_SET_NAME_TOKEN = Pattern.compile("\\{([A-Za-z][A-Za-z0-9]*)\\}");
35 private static final Pattern SOURCE_SET_NAME_TOKEN = Pattern.compile("\\{([A-Za-z][A-Za-z0-9]*)\\}");
36
36
37 private final NamedDomainObjectContainer<BuildLayerBinding> bindings;
37 private final NamedDomainObjectContainer<BuildLayerBinding> bindings;
38 private final List<Action<? super SourceSetContext>> registeredActions = new ArrayList<>();
38 private final List<Action<? super SourceSetRegistration>> registeredActions = new ArrayList<>();
39 private final List<Action<? super SourceSetContext>> boundActions = new ArrayList<>();
39 private final List<Action<? super SourceSetUsageBinding>> boundActions = new ArrayList<>();
40 private final List<SourceSetContext> registeredContexts = new ArrayList<>();
40 private final List<SourceSetRegistration> registeredContexts = new ArrayList<>();
41 private final List<SourceSetContext> boundContexts = new ArrayList<>();
41 private final List<SourceSetUsageBinding> boundContexts = new ArrayList<>();
42 private final LinkedHashMap<String, NamedDomainObjectProvider<GenericSourceSet>> sourceSetsByName = new LinkedHashMap<>();
42 private final LinkedHashMap<String, NamedDomainObjectProvider<GenericSourceSet>> sourceSetsByName = new LinkedHashMap<>();
43 private final LinkedHashMap<String, String> sourceSetLayersByName = new LinkedHashMap<>();
43 private final LinkedHashMap<String, String> sourceSetLayersByName = new LinkedHashMap<>();
44 private boolean sourceSetsRegistered;
44 private boolean sourceSetsRegistered;
45
45
46 @Inject
46 @Inject
47 public VariantSourcesExtension(ObjectFactory objects) {
47 public VariantSourcesExtension(ObjectFactory objects) {
48 bindings = objects.domainObjectContainer(BuildLayerBinding.class);
48 bindings = objects.domainObjectContainer(BuildLayerBinding.class);
49 }
49 }
50
50
51 public NamedDomainObjectContainer<BuildLayerBinding> getBindings() {
51 public NamedDomainObjectContainer<BuildLayerBinding> getBindings() {
52 return bindings;
52 return bindings;
53 }
53 }
54
54
55 public void bindings(Action<? super NamedDomainObjectContainer<BuildLayerBinding>> action) {
55 public void bindings(Action<? super NamedDomainObjectContainer<BuildLayerBinding>> action) {
56 action.execute(bindings);
56 action.execute(bindings);
57 }
57 }
58
58
59 public void bindings(
59 public void bindings(
60 @DelegatesTo(value = NamedDomainObjectContainer.class, strategy = Closure.DELEGATE_FIRST) Closure<?> action) {
60 @DelegatesTo(value = NamedDomainObjectContainer.class, strategy = Closure.DELEGATE_FIRST) Closure<?> action) {
61 bindings(Closures.action(action));
61 bindings(Closures.action(action));
62 }
62 }
63
63
64 public BuildLayerBinding bind(String layer) {
64 public BuildLayerBinding bind(String layer) {
65 return bindings.maybeCreate(normalize(layer, "Layer name must not be null or blank"));
65 return bindings.maybeCreate(normalize(layer, "Layer name must not be null or blank"));
66 }
66 }
67
67
68 /**
68 /**
69 * Configures per-layer binding.
69 * Configures per-layer binding.
70 */
70 */
71 public BuildLayerBinding bind(String layer, Action<? super BuildLayerBinding> configure) {
71 public BuildLayerBinding bind(String layer, Action<? super BuildLayerBinding> configure) {
72 var binding = bind(layer);
72 var binding = bind(layer);
73 configure.execute(binding);
73 configure.execute(binding);
74 return binding;
74 return binding;
75 }
75 }
76
76
77 public BuildLayerBinding bind(String layer,
77 public BuildLayerBinding bind(String layer,
78 @DelegatesTo(value = BuildLayerBinding.class, strategy = Closure.DELEGATE_FIRST) Closure<?> configure) {
78 @DelegatesTo(value = BuildLayerBinding.class, strategy = Closure.DELEGATE_FIRST) Closure<?> configure) {
79 return bind(layer, Closures.action(configure));
79 return bind(layer, Closures.action(configure));
80 }
80 }
81
81
82 /**
82 /**
83 * Global callback fired for each registered source-set context.
83 * Global callback fired for each registered source set.
84 * Already emitted contexts are delivered immediately (replay).
84 * Already emitted registrations are delivered immediately (replay).
85 * For simple callbacks you can use delegate-only style
85 * For simple callbacks you can use delegate-only style
86 * (for example {@code whenRegistered { sourceSetName() }}).
86 * (for example {@code whenRegistered { sourceSetName() }}).
87 * For nested closures prefer explicit parameter
87 * For nested closures prefer explicit parameter
88 * ({@code whenRegistered { ctx -> ... }}).
88 * ({@code whenRegistered { ctx -> ... }}).
89 */
89 */
90 public void whenRegistered(Action<? super SourceSetContext> action) {
90 public void whenRegistered(Action<? super SourceSetRegistration> action) {
91 registeredActions.add(action);
91 registeredActions.add(action);
92 for (var context : registeredContexts)
92 for (var context : registeredContexts)
93 action.execute(context);
93 action.execute(context);
94 }
94 }
95
95
96 public void whenRegistered(
96 public void whenRegistered(
97 @DelegatesTo(value = SourceSetContext.class, strategy = Closure.DELEGATE_FIRST) Closure<?> action) {
97 @DelegatesTo(value = SourceSetRegistration.class, strategy = Closure.DELEGATE_FIRST) Closure<?> action) {
98 whenRegistered(Closures.action(action));
98 whenRegistered(Closures.action(action));
99 }
99 }
100
100
101 public void whenRegistered(String variantName, Action<? super SourceSetContext> action) {
102 var normalizedVariantName = normalize(variantName, "variantName must not be null or blank");
103 whenRegistered(filterByVariant(normalizedVariantName, action));
104 }
105
106 public void whenRegistered(String variantName,
107 @DelegatesTo(value = SourceSetContext.class, strategy = Closure.DELEGATE_FIRST) Closure<?> action) {
108 whenRegistered(variantName, Closures.action(action));
109 }
110
111 /**
101 /**
112 * Global callback fired for every resolved variant/role/layer usage.
102 * Global callback fired for every resolved variant/role/layer usage.
113 * Already emitted contexts are delivered immediately (replay).
103 * Already emitted usage bindings are delivered immediately (replay).
114 * For simple callbacks you can use delegate-only style
104 * For simple callbacks you can use delegate-only style
115 * (for example {@code whenBound { variantName() }}).
105 * (for example {@code whenBound { variantName() }}).
116 * For nested closures prefer explicit parameter
106 * For nested closures prefer explicit parameter
117 * ({@code whenBound { ctx -> ... }}).
107 * ({@code whenBound { ctx -> ... }}).
118 */
108 */
119 public void whenBound(Action<? super SourceSetContext> action) {
109 public void whenBound(Action<? super SourceSetUsageBinding> action) {
120 boundActions.add(action);
110 boundActions.add(action);
121 for (var context : boundContexts)
111 for (var context : boundContexts)
122 action.execute(context);
112 action.execute(context);
123 }
113 }
124
114
125 public void whenBound(
115 public void whenBound(
126 @DelegatesTo(value = SourceSetContext.class, strategy = Closure.DELEGATE_FIRST) Closure<?> action) {
116 @DelegatesTo(value = SourceSetUsageBinding.class, strategy = Closure.DELEGATE_FIRST) Closure<?> action) {
127 whenBound(Closures.action(action));
117 whenBound(Closures.action(action));
128 }
118 }
129
119
130 public void whenBound(String variantName, Action<? super SourceSetContext> action) {
120 public void whenBound(String variantName, Action<? super SourceSetUsageBinding> action) {
131 var normalizedVariantName = normalize(variantName, "variantName must not be null or blank");
121 var normalizedVariantName = normalize(variantName, "variantName must not be null or blank");
132 whenBound(filterByVariant(normalizedVariantName, action));
122 whenBound(filterByVariant(normalizedVariantName, action));
133 }
123 }
134
124
135 public void whenBound(String variantName,
125 public void whenBound(String variantName,
136 @DelegatesTo(value = SourceSetContext.class, strategy = Closure.DELEGATE_FIRST) Closure<?> action) {
126 @DelegatesTo(value = SourceSetUsageBinding.class, strategy = Closure.DELEGATE_FIRST) Closure<?> action) {
137 whenBound(variantName, Closures.action(action));
127 whenBound(variantName, Closures.action(action));
138 }
128 }
139
129
140 void registerSourceSets(BuildVariantsExtension variants, NamedDomainObjectContainer<GenericSourceSet> sources) {
130 void registerSourceSets(BuildVariantsExtension variants, NamedDomainObjectContainer<GenericSourceSet> sources) {
141 if (sourceSetsRegistered) {
131 if (sourceSetsRegistered) {
142 throw new InvalidUserDataException("variantSources source sets are already registered");
132 throw new InvalidUserDataException("variantSources source sets are already registered");
143 }
133 }
144
134
145 validateBindings(variants);
135 validateBindings(variants);
146
136
147 var usages = layerUsages(variants).toList();
137 var usages = layerUsages(variants).toList();
148 var registeredBefore = registeredContexts.size();
138 var registeredBefore = registeredContexts.size();
149 var boundBefore = boundContexts.size();
139 var boundBefore = boundContexts.size();
150
140
151 logger.debug(
141 logger.debug(
152 "Starting variant source-set registration (variants={}, layers={}, bindings={}, usages={})",
142 "Starting variant source-set registration (variants={}, layers={}, bindings={}, usages={})",
153 variants.getVariants().size(),
143 variants.getVariants().size(),
154 variants.getLayers().size(),
144 variants.getLayers().size(),
155 bindings.size(),
145 bindings.size(),
156 usages.size());
146 usages.size());
157
147
158 usages.forEach(usage -> registerLayerUsage(usage, sources));
148 usages.forEach(usage -> registerLayerUsage(usage, sources));
159
149
160 logger.debug(
150 logger.debug(
161 "Completed variant source-set registration (newSourceSets={}, newBounds={}, totalSourceSets={})",
151 "Completed variant source-set registration (newSourceSets={}, newBounds={}, totalSourceSets={})",
162 registeredContexts.size() - registeredBefore,
152 registeredContexts.size() - registeredBefore,
163 boundContexts.size() - boundBefore,
153 boundContexts.size() - boundBefore,
164 sourceSetsByName.size());
154 sourceSetsByName.size());
165
155
166 sourceSetsRegistered = true;
156 sourceSetsRegistered = true;
167 }
157 }
168
158
169 private Stream<LayerUsage> layerUsages(BuildVariantsExtension variants) {
159 private Stream<LayerUsage> layerUsages(BuildVariantsExtension variants) {
170 return variants.getVariants().stream()
160 return variants.getVariants().stream()
171 .flatMap(variant -> variant.getRoles().stream()
161 .flatMap(variant -> variant.getRoles().stream()
172 .flatMap(role -> role.getLayers().getOrElse(List.of()).stream()
162 .flatMap(role -> role.getLayers().getOrElse(List.of()).stream()
173 .map(layerName -> new LayerUsage(
163 .map(layerName -> new LayerUsage(
174 variant.getName(),
164 variant.getName(),
175 role.getName(),
165 role.getName(),
176 normalize(layerName, "Layer name in variant '" + variant.getName() + "' and role '" + role.getName() + "' must not be null or blank")))));
166 normalize(layerName, "Layer name in variant '" + variant.getName() + "' and role '" + role.getName() + "' must not be null or blank")))));
177 }
167 }
178
168
179 private void registerLayerUsage(LayerUsage usage, NamedDomainObjectContainer<GenericSourceSet> sources) {
169 private void registerLayerUsage(LayerUsage usage, NamedDomainObjectContainer<GenericSourceSet> sources) {
180 var resolvedBinding = bind(usage.layerName());
170 var resolvedBinding = bind(usage.layerName());
181 var sourceSetNamePattern = resolvedBinding.getSourceSetNamePattern();
171 var sourceSetNamePattern = resolvedBinding.getSourceSetNamePattern();
182 sourceSetNamePattern.finalizeValueOnRead();
172 sourceSetNamePattern.finalizeValueOnRead();
183
173
184 var sourceSetName = sourceSetName(usage, sourceSetNamePattern.get());
174 var sourceSetName = sourceSetName(usage, sourceSetNamePattern.get());
185
175
186 ensureSourceSetNameBoundToSingleLayer(sourceSetName, usage.layerName());
176 ensureSourceSetNameBoundToSingleLayer(sourceSetName, usage.layerName());
187 var isNewSourceSet = !sourceSetsByName.containsKey(sourceSetName);
177 var isNewSourceSet = !sourceSetsByName.containsKey(sourceSetName);
188 var sourceSet = sourceSetsByName.computeIfAbsent(sourceSetName,
178 var sourceSet = sourceSetsByName.computeIfAbsent(sourceSetName,
189 name -> sources.register(name));
179 name -> sources.register(name));
190
180
191 var context = new SourceSetContext(
181 var binding = new SourceSetUsageBinding(
192 usage.variantName(),
182 usage.variantName(),
193 usage.roleName(),
183 usage.roleName(),
194 usage.layerName(),
184 usage.layerName(),
195 sourceSetName,
185 sourceSetName,
196 sourceSet);
186 sourceSet);
197
187
198 if (isNewSourceSet) {
188 if (isNewSourceSet) {
199 resolvedBinding.notifyRegistered(context);
189 var registration = new SourceSetRegistration(
200 notifyRegistered(context);
190 usage.layerName(),
191 sourceSetName,
192 sourceSet);
193 resolvedBinding.notifyRegistered(registration);
194 notifyRegistered(registration);
201 }
195 }
202
196
203 resolvedBinding.notifyBound(context);
197 resolvedBinding.notifyBound(binding);
204 notifyBound(context);
198 notifyBound(binding);
205 }
199 }
206
200
207 private void notifyRegistered(SourceSetContext context) {
201 private void notifyRegistered(SourceSetRegistration registration) {
208 registeredContexts.add(context);
202 registeredContexts.add(registration);
209 for (var action : registeredActions)
203 for (var action : registeredActions)
210 action.execute(context);
204 action.execute(registration);
211 }
205 }
212
206
213 private void notifyBound(SourceSetContext context) {
207 private void notifyBound(SourceSetUsageBinding binding) {
214 boundContexts.add(context);
208 boundContexts.add(binding);
215 for (var action : boundActions)
209 for (var action : boundActions)
216 action.execute(context);
210 action.execute(binding);
217 }
211 }
218
212
219 private static Action<? super SourceSetContext> filterByVariant(String variantName,
213 private static Action<? super SourceSetUsageBinding> filterByVariant(String variantName,
220 Action<? super SourceSetContext> action) {
214 Action<? super SourceSetUsageBinding> action) {
221 return context -> {
215 return binding -> {
222 if (variantName.equals(context.variantName()))
216 if (variantName.equals(binding.variantName()))
223 action.execute(context);
217 action.execute(binding);
224 };
218 };
225 }
219 }
226
220
227 private void ensureSourceSetNameBoundToSingleLayer(String sourceSetName, String layerName) {
221 private void ensureSourceSetNameBoundToSingleLayer(String sourceSetName, String layerName) {
228 var existingLayer = sourceSetLayersByName.putIfAbsent(sourceSetName, layerName);
222 var existingLayer = sourceSetLayersByName.putIfAbsent(sourceSetName, layerName);
229 if (existingLayer != null && !existingLayer.equals(layerName)) {
223 if (existingLayer != null && !existingLayer.equals(layerName)) {
230 throw new InvalidUserDataException("Source set '" + sourceSetName + "' is resolved from multiple layers: '"
224 throw new InvalidUserDataException("Source set '" + sourceSetName + "' is resolved from multiple layers: '"
231 + existingLayer + "' and '" + layerName + "'");
225 + existingLayer + "' and '" + layerName + "'");
232 }
226 }
233 }
227 }
234
228
235 private void validateBindings(BuildVariantsExtension variants) {
229 private void validateBindings(BuildVariantsExtension variants) {
236 var knownLayerNames = new java.util.LinkedHashSet<String>();
230 var knownLayerNames = new java.util.LinkedHashSet<String>();
237 for (var layer : variants.getLayers())
231 for (var layer : variants.getLayers())
238 knownLayerNames.add(layer.getName());
232 knownLayerNames.add(layer.getName());
239
233
240 var errors = new ArrayList<String>();
234 var errors = new ArrayList<String>();
241 for (var binding : bindings) {
235 for (var binding : bindings) {
242 if (!knownLayerNames.contains(binding.getName())) {
236 if (!knownLayerNames.contains(binding.getName())) {
243 errors.add("Layer binding '" + binding.getName() + "' references unknown layer");
237 errors.add("Layer binding '" + binding.getName() + "' references unknown layer");
244 }
238 }
245 }
239 }
246
240
247 if (!errors.isEmpty()) {
241 if (!errors.isEmpty()) {
248 var message = new StringBuilder("Invalid variantSources model:");
242 var message = new StringBuilder("Invalid variantSources model:");
249 for (var error : errors)
243 for (var error : errors)
250 message.append("\n - ").append(error);
244 message.append("\n - ").append(error);
251 throw new InvalidUserDataException(message.toString());
245 throw new InvalidUserDataException(message.toString());
252 }
246 }
253 }
247 }
254
248
255 private static String sourceSetName(LayerUsage usage, String pattern) {
249 private static String sourceSetName(LayerUsage usage, String pattern) {
256 var normalizedPattern = normalize(pattern, "sourceSetNamePattern must not be null or blank");
250 var normalizedPattern = normalize(pattern, "sourceSetNamePattern must not be null or blank");
257 var resolved = resolveSourceSetNamePattern(normalizedPattern, usage);
251 var resolved = resolveSourceSetNamePattern(normalizedPattern, usage);
258 var result = sanitizeName(resolved);
252 var result = sanitizeName(resolved);
259
253
260 if (result.isEmpty())
254 if (result.isEmpty())
261 throw new InvalidUserDataException("sourceSetNamePattern '" + pattern + "' resolved to empty source set name");
255 throw new InvalidUserDataException("sourceSetNamePattern '" + pattern + "' resolved to empty source set name");
262
256
263 return result;
257 return result;
264 }
258 }
265
259
266 private static String resolveSourceSetNamePattern(String pattern, LayerUsage usage) {
260 private static String resolveSourceSetNamePattern(String pattern, LayerUsage usage) {
267 var matcher = SOURCE_SET_NAME_TOKEN.matcher(pattern);
261 var matcher = SOURCE_SET_NAME_TOKEN.matcher(pattern);
268 var output = new StringBuffer();
262 var output = new StringBuffer();
269
263
270 while (matcher.find()) {
264 while (matcher.find()) {
271 var token = matcher.group(1);
265 var token = matcher.group(1);
272 matcher.appendReplacement(output, Matcher.quoteReplacement(tokenValue(token, usage)));
266 matcher.appendReplacement(output, Matcher.quoteReplacement(tokenValue(token, usage)));
273 }
267 }
274 matcher.appendTail(output);
268 matcher.appendTail(output);
275
269
276 return output.toString();
270 return output.toString();
277 }
271 }
278
272
279 private static String tokenValue(String token, LayerUsage usage) {
273 private static String tokenValue(String token, LayerUsage usage) {
280 return switch (token) {
274 return switch (token) {
281 case "variant" -> sanitizeName(usage.variantName());
275 case "variant" -> sanitizeName(usage.variantName());
282 case "variantCap" -> Strings.capitalize(sanitizeName(usage.variantName()));
276 case "variantCap" -> Strings.capitalize(sanitizeName(usage.variantName()));
283 case "role" -> sanitizeName(usage.roleName());
277 case "role" -> sanitizeName(usage.roleName());
284 case "roleCap" -> Strings.capitalize(sanitizeName(usage.roleName()));
278 case "roleCap" -> Strings.capitalize(sanitizeName(usage.roleName()));
285 case "layer" -> sanitizeName(usage.layerName());
279 case "layer" -> sanitizeName(usage.layerName());
286 case "layerCap" -> Strings.capitalize(sanitizeName(usage.layerName()));
280 case "layerCap" -> Strings.capitalize(sanitizeName(usage.layerName()));
287 default -> throw new InvalidUserDataException(
281 default -> throw new InvalidUserDataException(
288 "sourceSetNamePattern contains unsupported token '{" + token + "}'");
282 "sourceSetNamePattern contains unsupported token '{" + token + "}'");
289 };
283 };
290 }
284 }
291
285
292 private static String normalize(@Nullable String value, String errorMessage) {
286 private static String normalize(@Nullable String value, String errorMessage) {
293 if (value == null)
287 if (value == null)
294 throw new InvalidUserDataException(errorMessage);
288 throw new InvalidUserDataException(errorMessage);
295 var trimmed = value.trim();
289 var trimmed = value.trim();
296 if (trimmed.isEmpty())
290 if (trimmed.isEmpty())
297 throw new InvalidUserDataException(errorMessage);
291 throw new InvalidUserDataException(errorMessage);
298 return trimmed;
292 return trimmed;
299 }
293 }
300
294
301 private record LayerUsage(String variantName, String roleName, String layerName) {
295 private record LayerUsage(String variantName, String roleName, String layerName) {
302 }
296 }
303 }
297 }
@@ -1,366 +1,367
1 package org.implab.gradle.common.sources;
1 package org.implab.gradle.common.sources;
2
2
3 import static org.junit.jupiter.api.Assertions.assertNotNull;
3 import static org.junit.jupiter.api.Assertions.assertNotNull;
4 import static org.junit.jupiter.api.Assertions.assertThrows;
4 import static org.junit.jupiter.api.Assertions.assertThrows;
5 import static org.junit.jupiter.api.Assertions.assertTrue;
5 import static org.junit.jupiter.api.Assertions.assertTrue;
6
6
7 import java.io.File;
7 import java.io.File;
8 import java.io.IOException;
8 import java.io.IOException;
9 import java.nio.file.Files;
9 import java.nio.file.Files;
10 import java.nio.file.Path;
10 import java.nio.file.Path;
11 import java.util.List;
11 import java.util.List;
12
12
13 import org.gradle.testkit.runner.BuildResult;
13 import org.gradle.testkit.runner.BuildResult;
14 import org.gradle.testkit.runner.GradleRunner;
14 import org.gradle.testkit.runner.GradleRunner;
15 import org.gradle.testkit.runner.TaskOutcome;
15 import org.gradle.testkit.runner.TaskOutcome;
16 import org.gradle.testkit.runner.UnexpectedBuildFailure;
16 import org.gradle.testkit.runner.UnexpectedBuildFailure;
17 import org.junit.jupiter.api.Test;
17 import org.junit.jupiter.api.Test;
18 import org.junit.jupiter.api.io.TempDir;
18 import org.junit.jupiter.api.io.TempDir;
19
19
20 class VariantsSourcesPluginFunctionalTest {
20 class VariantsSourcesPluginFunctionalTest {
21 private static final String SETTINGS_FILE = "settings.gradle";
21 private static final String SETTINGS_FILE = "settings.gradle";
22 private static final String BUILD_FILE = "build.gradle";
22 private static final String BUILD_FILE = "build.gradle";
23 private static final String ROOT_NAME = "rootProject.name = 'variants-sources-fixture'\n";
23 private static final String ROOT_NAME = "rootProject.name = 'variants-sources-fixture'\n";
24
24
25 @TempDir
25 @TempDir
26 Path testProjectDir;
26 Path testProjectDir;
27
27
28 @Test
28 @Test
29 void registersVariantSourceSetsAndFiresCallbacks() throws Exception {
29 void registersVariantSourceSetsAndFiresCallbacks() throws Exception {
30 writeFile(SETTINGS_FILE, ROOT_NAME);
30 writeFile(SETTINGS_FILE, ROOT_NAME);
31 writeFile(BUILD_FILE, """
31 writeFile(BUILD_FILE, """
32 plugins {
32 plugins {
33 id 'org.implab.gradle-variants-sources'
33 id 'org.implab.gradle-variants-sources'
34 }
34 }
35
35
36 variants {
36 variants {
37 layer('mainBase')
37 layer('mainBase')
38 layer('mainAmd')
38 layer('mainAmd')
39
39
40 variant('browser') {
40 variant('browser') {
41 role('main') { layers('mainBase', 'mainAmd') }
41 role('main') { layers('mainBase', 'mainAmd') }
42 }
42 }
43
43
44 variant('node') {
44 variant('node') {
45 role('main') { layers('mainBase') }
45 role('main') { layers('mainBase') }
46 }
46 }
47 }
47 }
48
48
49 def events = []
49 def events = []
50 def localEvents = []
50 def localEvents = []
51
51
52 variantSources {
52 variantSources {
53 bind('mainBase') {
53 bind('mainBase') {
54 configureSourceSet {
54 configureSourceSet {
55 declareOutputs('compiled')
55 declareOutputs('compiled')
56 }
56 }
57 }
57 }
58 bind('mainAmd') {
58 bind('mainAmd') {
59 configureSourceSet {
59 configureSourceSet {
60 declareOutputs('compiled')
60 declareOutputs('compiled')
61 }
61 }
62 }
62 }
63 bind('mainAmd').whenRegistered { ctx ->
63 bind('mainAmd').whenRegistered { ctx ->
64 localEvents << "${ctx.variantName()}:${ctx.roleName()}:${ctx.layerName()}:${ctx.sourceSetName()}"
64 localEvents << "${ctx.layerName()}:${ctx.sourceSetName()}"
65 }
65 }
66 whenRegistered { ctx ->
66 whenRegistered { ctx ->
67 events << "${ctx.variantName()}:${ctx.roleName()}:${ctx.layerName()}:${ctx.sourceSetName()}"
67 events << "${ctx.layerName()}:${ctx.sourceSetName()}"
68 }
68 }
69 }
69 }
70
70
71 tasks.register('probe') {
71 tasks.register('probe') {
72 doLast {
72 doLast {
73 println("sources=" + sources.collect { it.name }.sort().join(','))
73 println("sources=" + sources.collect { it.name }.sort().join(','))
74 println("events=" + events.sort().join('|'))
74 println("events=" + events.sort().join('|'))
75 println("local=" + localEvents.sort().join('|'))
75 println("local=" + localEvents.sort().join('|'))
76
76
77 def base = sources.getByName('browserMainBase')
77 def base = sources.getByName('browserMainBase')
78 def amd = sources.getByName('browserMainAmd')
78 def amd = sources.getByName('browserMainAmd')
79 def nodeBase = sources.getByName('nodeMainBase')
79 def nodeBase = sources.getByName('nodeMainBase')
80
80
81 base.output('compiled')
81 base.output('compiled')
82 amd.output('compiled')
82 amd.output('compiled')
83 nodeBase.output('compiled')
83 nodeBase.output('compiled')
84
84
85 println('outputs=ok')
85 println('outputs=ok')
86 }
86 }
87 }
87 }
88 """);
88 """);
89
89
90 BuildResult result = runner("probe").build();
90 BuildResult result = runner("probe").build();
91
91
92 assertTrue(result.getOutput().contains("sources=browserMainAmd,browserMainBase,nodeMainBase"));
92 assertTrue(result.getOutput().contains("sources=browserMainAmd,browserMainBase,nodeMainBase"));
93 assertTrue(result.getOutput().contains(
93 assertTrue(result.getOutput().contains("events=mainAmd:browserMainAmd|mainBase:browserMainBase|mainBase:nodeMainBase"));
94 "events=browser:main:mainAmd:browserMainAmd|browser:main:mainBase:browserMainBase|node:main:mainBase:nodeMainBase"));
94 assertTrue(result.getOutput().contains("local=mainAmd:browserMainAmd"));
95 assertTrue(result.getOutput().contains("local=browser:main:mainAmd:browserMainAmd"));
96 assertTrue(result.getOutput().contains("outputs=ok"));
95 assertTrue(result.getOutput().contains("outputs=ok"));
97 assertTrue(result.task(":probe").getOutcome() == TaskOutcome.SUCCESS);
96 assertTrue(result.task(":probe").getOutcome() == TaskOutcome.SUCCESS);
98 }
97 }
99
98
100 @Test
99 @Test
101 void supportsTrailingClosureOnBind() throws Exception {
100 void supportsTrailingClosureOnBind() throws Exception {
102 writeFile(SETTINGS_FILE, ROOT_NAME);
101 writeFile(SETTINGS_FILE, ROOT_NAME);
103 writeFile(BUILD_FILE, """
102 writeFile(BUILD_FILE, """
104 plugins {
103 plugins {
105 id 'org.implab.gradle-variants-sources'
104 id 'org.implab.gradle-variants-sources'
106 }
105 }
107
106
108 variants {
107 variants {
109 layer('main')
108 layer('main')
110 variant('browser') {
109 variant('browser') {
111 role('main') { layers('main') }
110 role('main') { layers('main') }
112 }
111 }
113 }
112 }
114
113
115 variantSources {
114 variantSources {
116 bind('main') {
115 bind('main') {
117 configureSourceSet {
116 configureSourceSet {
118 declareOutputs('compiled')
117 declareOutputs('compiled')
119 }
118 }
120 }
119 }
121 }
120 }
122
121
123 tasks.register('probe') {
122 tasks.register('probe') {
124 doLast {
123 doLast {
125 def ss = sources.getByName('browserMain')
124 def ss = sources.getByName('browserMain')
126 ss.output('compiled')
125 ss.output('compiled')
127 println('bindClosure=ok')
126 println('bindClosure=ok')
128 }
127 }
129 }
128 }
130 """);
129 """);
131
130
132 BuildResult result = runner("probe").build();
131 BuildResult result = runner("probe").build();
133 assertTrue(result.getOutput().contains("bindClosure=ok"));
132 assertTrue(result.getOutput().contains("bindClosure=ok"));
134 }
133 }
135
134
136 @Test
135 @Test
137 void failsOnUnknownLayerBinding() throws Exception {
136 void failsOnUnknownLayerBinding() throws Exception {
138 writeFile(SETTINGS_FILE, ROOT_NAME);
137 writeFile(SETTINGS_FILE, ROOT_NAME);
139 writeFile(BUILD_FILE, """
138 writeFile(BUILD_FILE, """
140 plugins {
139 plugins {
141 id 'org.implab.gradle-variants-sources'
140 id 'org.implab.gradle-variants-sources'
142 }
141 }
143
142
144 variants {
143 variants {
145 layer('main')
144 layer('main')
146 variant('browser') {
145 variant('browser') {
147 role('main') { layers('main') }
146 role('main') { layers('main') }
148 }
147 }
149 }
148 }
150
149
151 variantSources {
150 variantSources {
152 bind('missing')
151 bind('missing')
153 }
152 }
154 """);
153 """);
155
154
156 var ex = assertThrows(UnexpectedBuildFailure.class, () -> runner("help").build());
155 var ex = assertThrows(UnexpectedBuildFailure.class, () -> runner("help").build());
157 assertTrue(ex.getBuildResult().getOutput().contains("Layer binding 'missing' references unknown layer"));
156 assertTrue(ex.getBuildResult().getOutput().contains("Layer binding 'missing' references unknown layer"));
158 }
157 }
159
158
160 @Test
159 @Test
161 void exposesProviderInSourceSetRegisteredContext() throws Exception {
160 void exposesProviderInSourceSetRegistration() throws Exception {
162 writeFile(SETTINGS_FILE, ROOT_NAME);
161 writeFile(SETTINGS_FILE, ROOT_NAME);
163 writeFile(BUILD_FILE, """
162 writeFile(BUILD_FILE, """
164 plugins {
163 plugins {
165 id 'org.implab.gradle-variants-sources'
164 id 'org.implab.gradle-variants-sources'
166 }
165 }
167
166
168 variants {
167 variants {
169 layer('main')
168 layer('main')
170 variant('browser') {
169 variant('browser') {
171 role('main') { layers('main') }
170 role('main') { layers('main') }
172 }
171 }
173 }
172 }
174
173
175 variantSources {
174 variantSources {
176 whenRegistered {
175 whenRegistered {
177 configureSourceSet {
176 configureSourceSet {
178 declareOutputs('generated')
177 declareOutputs('generated')
179 }
178 }
180 }
179 }
181 }
180 }
182
181
183 tasks.register('probe') {
182 tasks.register('probe') {
184 doLast {
183 doLast {
185 def ss = sources.getByName('browserMain')
184 def ss = sources.getByName('browserMain')
186 ss.output('generated')
185 ss.output('generated')
187 println('contextProvider=ok')
186 println('contextProvider=ok')
188 }
187 }
189 }
188 }
190 """);
189 """);
191
190
192 BuildResult result = runner("probe").build();
191 BuildResult result = runner("probe").build();
193 assertTrue(result.getOutput().contains("contextProvider=ok"));
192 assertTrue(result.getOutput().contains("contextProvider=ok"));
194 }
193 }
195
194
196 @Test
195 @Test
197 void replaysLateBindingsAndCallbacksAfterRegistration() throws Exception {
196 void replaysLateBindingsAndCallbacksAfterRegistration() throws Exception {
198 writeFile(SETTINGS_FILE, ROOT_NAME);
197 writeFile(SETTINGS_FILE, ROOT_NAME);
199 writeFile(BUILD_FILE, """
198 writeFile(BUILD_FILE, """
200 plugins {
199 plugins {
201 id 'org.implab.gradle-variants-sources'
200 id 'org.implab.gradle-variants-sources'
202 }
201 }
203
202
204 variants {
203 variants {
205 layer('main')
204 layer('main')
206 variant('browser') {
205 variant('browser') {
207 role('main') { layers('main') }
206 role('main') { layers('main') }
208 }
207 }
209 }
208 }
210
209
211 def events = []
210 def events = []
212
211
213 afterEvaluate {
212 afterEvaluate {
214 variantSources {
213 variantSources {
215 bind('main') {
214 bind('main') {
216 configureSourceSet {
215 configureSourceSet {
217 declareOutputs('late')
216 declareOutputs('late')
218 }
217 }
219 }
218 }
220
219
221 bind('main').whenRegistered { ctx ->
220 bind('main').whenRegistered { ctx ->
222 events << "layer:${ctx.sourceSetName()}"
221 events << "layer:${ctx.sourceSetName()}"
223 }
222 }
224
223
225 whenRegistered { ctx ->
224 whenRegistered { ctx ->
226 events << "global:${ctx.sourceSetName()}"
225 events << "global:${ctx.sourceSetName()}"
227 }
226 }
228 }
227 }
229 }
228 }
230
229
231 tasks.register('probe') {
230 tasks.register('probe') {
232 doLast {
231 doLast {
233 def ss = sources.getByName('browserMain')
232 def ss = sources.getByName('browserMain')
234 ss.output('late')
233 ss.output('late')
235 println("events=" + events.sort().join('|'))
234 println("events=" + events.sort().join('|'))
236 println('lateReplay=ok')
235 println('lateReplay=ok')
237 }
236 }
238 }
237 }
239 """);
238 """);
240
239
241 BuildResult result = runner("probe").build();
240 BuildResult result = runner("probe").build();
242 assertTrue(result.getOutput().contains("events=global:browserMain|layer:browserMain"));
241 assertTrue(result.getOutput().contains("events=global:browserMain|layer:browserMain"));
243 assertTrue(result.getOutput().contains("lateReplay=ok"));
242 assertTrue(result.getOutput().contains("lateReplay=ok"));
244 }
243 }
245
244
246 @Test
245 @Test
247 void supportsSourceSetNamePatternAndSharedRegistration() throws Exception {
246 void supportsSourceSetNamePatternAndSharedRegistration() throws Exception {
248 writeFile(SETTINGS_FILE, ROOT_NAME);
247 writeFile(SETTINGS_FILE, ROOT_NAME);
249 writeFile(BUILD_FILE, """
248 writeFile(BUILD_FILE, """
250 plugins {
249 plugins {
251 id 'org.implab.gradle-variants-sources'
250 id 'org.implab.gradle-variants-sources'
252 }
251 }
253
252
254 variants {
253 variants {
255 layer('main')
254 layer('main')
256
255
257 variant('browser') {
256 variant('browser') {
258 role('main') { layers('main') }
257 role('main') { layers('main') }
259 }
258 }
260
259
261 variant('node') {
260 variant('node') {
262 role('main') { layers('main') }
261 role('main') { layers('main') }
263 }
262 }
264 }
263 }
265
264
266 def registeredEvents = []
265 def registeredEvents = []
267 def browserRegisteredEvents = []
268 def boundEvents = []
266 def boundEvents = []
269 def browserBoundEvents = []
267 def browserBoundEvents = []
268 def localRegisteredEvents = []
270 def localBoundEvents = []
269 def localBoundEvents = []
271
270
272 variantSources {
271 variantSources {
273 bind('main').sourceSetNamePattern = '{layer}'
272 bind('main').sourceSetNamePattern = '{layer}'
274
273
275 bind('main') {
274 bind('main') {
276 configureSourceSet {
275 configureSourceSet {
277 declareOutputs('compiled')
276 declareOutputs('compiled')
278 }
277 }
279 }
278 }
280
279
281 bind('main') {
280 bind('main') {
281 whenRegistered {
282 localRegisteredEvents << "${layerName()}:${sourceSetName()}"
283 }
284 }
285
286 bind('main') {
282 whenBound {
287 whenBound {
283 localBoundEvents << "${variantName()}:${roleName()}:${layerName()}:${sourceSetName()}"
288 localBoundEvents << "${variantName()}:${roleName()}:${layerName()}:${sourceSetName()}"
284 }
289 }
285 }
290 }
286
291
287 whenRegistered { ctx ->
292 whenRegistered { ctx ->
288 registeredEvents << "${ctx.variantName()}:${ctx.roleName()}:${ctx.layerName()}:${ctx.sourceSetName()}"
293 registeredEvents << "${ctx.layerName()}:${ctx.sourceSetName()}"
289 }
290
291 whenRegistered('browser') { ctx ->
292 browserRegisteredEvents << "${ctx.variantName()}:${ctx.roleName()}:${ctx.layerName()}:${ctx.sourceSetName()}"
293 }
294 }
294
295
295 whenBound { ctx ->
296 whenBound { ctx ->
296 boundEvents << "${ctx.variantName()}:${ctx.roleName()}:${ctx.layerName()}:${ctx.sourceSetName()}"
297 boundEvents << "${ctx.variantName()}:${ctx.roleName()}:${ctx.layerName()}:${ctx.sourceSetName()}"
297 }
298 }
298
299
299 whenBound('browser') { ctx ->
300 whenBound('browser') { ctx ->
300 browserBoundEvents << "${ctx.variantName()}:${ctx.roleName()}:${ctx.layerName()}:${ctx.sourceSetName()}"
301 browserBoundEvents << "${ctx.variantName()}:${ctx.roleName()}:${ctx.layerName()}:${ctx.sourceSetName()}"
301 }
302 }
302 }
303 }
303
304
304 tasks.register('probe') {
305 tasks.register('probe') {
305 doLast {
306 doLast {
306 println("sources=" + sources.collect { it.name }.sort().join(','))
307 println("sources=" + sources.collect { it.name }.sort().join(','))
307
308
308 def main = sources.getByName('main')
309 def main = sources.getByName('main')
309 main.output('compiled')
310 main.output('compiled')
310
311
311 println("registered=" + registeredEvents.sort().join('|'))
312 println("registered=" + registeredEvents.sort().join('|'))
312 println("browserRegistered=" + browserRegisteredEvents.sort().join('|'))
313 println("localRegistered=" + localRegisteredEvents.sort().join('|'))
313 println("bound=" + boundEvents.sort().join('|'))
314 println("bound=" + boundEvents.sort().join('|'))
314 println("browserBound=" + browserBoundEvents.sort().join('|'))
315 println("browserBound=" + browserBoundEvents.sort().join('|'))
315 println("localBound=" + localBoundEvents.sort().join('|'))
316 println("localBound=" + localBoundEvents.sort().join('|'))
316 println('sharedPattern=ok')
317 println('sharedPattern=ok')
317 }
318 }
318 }
319 }
319 """);
320 """);
320
321
321 BuildResult result = runner("probe").build();
322 BuildResult result = runner("probe").build();
322 assertTrue(result.getOutput().contains("sources=main"));
323 assertTrue(result.getOutput().contains("sources=main"));
323 assertTrue(result.getOutput().contains("registered=browser:main:main:main"));
324 assertTrue(result.getOutput().contains("registered=main:main"));
324 assertTrue(result.getOutput().contains("browserRegistered=browser:main:main:main"));
325 assertTrue(result.getOutput().contains("localRegistered=main:main"));
325 assertTrue(result.getOutput().contains("bound=browser:main:main:main|node:main:main:main"));
326 assertTrue(result.getOutput().contains("bound=browser:main:main:main|node:main:main:main"));
326 assertTrue(result.getOutput().contains("browserBound=browser:main:main:main"));
327 assertTrue(result.getOutput().contains("browserBound=browser:main:main:main"));
327 assertTrue(result.getOutput().contains("localBound=browser:main:main:main|node:main:main:main"));
328 assertTrue(result.getOutput().contains("localBound=browser:main:main:main|node:main:main:main"));
328 assertTrue(result.getOutput().contains("sharedPattern=ok"));
329 assertTrue(result.getOutput().contains("sharedPattern=ok"));
329 }
330 }
330
331
331 private GradleRunner runner(String... arguments) {
332 private GradleRunner runner(String... arguments) {
332 return GradleRunner.create()
333 return GradleRunner.create()
333 .withProjectDir(testProjectDir.toFile())
334 .withProjectDir(testProjectDir.toFile())
334 .withPluginClasspath(pluginClasspath())
335 .withPluginClasspath(pluginClasspath())
335 .withArguments(arguments)
336 .withArguments(arguments)
336 .forwardOutput();
337 .forwardOutput();
337 }
338 }
338
339
339 private static List<File> pluginClasspath() {
340 private static List<File> pluginClasspath() {
340 try {
341 try {
341 var classesDir = Path.of(VariantsSourcesPlugin.class
342 var classesDir = Path.of(VariantsSourcesPlugin.class
342 .getProtectionDomain()
343 .getProtectionDomain()
343 .getCodeSource()
344 .getCodeSource()
344 .getLocation()
345 .getLocation()
345 .toURI());
346 .toURI());
346
347
347 var markerResource = VariantsSourcesPlugin.class.getClassLoader()
348 var markerResource = VariantsSourcesPlugin.class.getClassLoader()
348 .getResource("META-INF/gradle-plugins/org.implab.gradle-variants-sources.properties");
349 .getResource("META-INF/gradle-plugins/org.implab.gradle-variants-sources.properties");
349
350
350 assertNotNull(markerResource, "Plugin marker resource is missing from test classpath");
351 assertNotNull(markerResource, "Plugin marker resource is missing from test classpath");
351
352
352 var markerPath = Path.of(markerResource.toURI());
353 var markerPath = Path.of(markerResource.toURI());
353 var resourcesDir = markerPath.getParent().getParent().getParent();
354 var resourcesDir = markerPath.getParent().getParent().getParent();
354
355
355 return List.of(classesDir.toFile(), resourcesDir.toFile());
356 return List.of(classesDir.toFile(), resourcesDir.toFile());
356 } catch (Exception e) {
357 } catch (Exception e) {
357 throw new RuntimeException("Unable to build plugin classpath for test", e);
358 throw new RuntimeException("Unable to build plugin classpath for test", e);
358 }
359 }
359 }
360 }
360
361
361 private void writeFile(String relativePath, String content) throws IOException {
362 private void writeFile(String relativePath, String content) throws IOException {
362 Path path = testProjectDir.resolve(relativePath);
363 Path path = testProjectDir.resolve(relativePath);
363 Files.createDirectories(path.getParent());
364 Files.createDirectories(path.getParent());
364 Files.writeString(path, content);
365 Files.writeString(path, content);
365 }
366 }
366 }
367 }
@@ -1,145 +1,155
1 # Variant Sources Plugin
1 # Variant Sources Plugin
2
2
3 ## NAME
3 ## NAME
4
4
5 `VariantsSourcesPlugin` и extension `variantSources`.
5 `VariantsSourcesPlugin` и extension `variantSources`.
6
6
7 ## SYNOPSIS
7 ## SYNOPSIS
8
8
9 ```groovy
9 ```groovy
10 plugins {
10 plugins {
11 id 'org.implab.gradle-variants-sources'
11 id 'org.implab.gradle-variants-sources'
12 }
12 }
13
13
14 variants {
14 variants {
15 layer('main')
15 layer('main')
16
16
17 variant('browser') {
17 variant('browser') {
18 role('main') { layers('main') }
18 role('main') { layers('main') }
19 }
19 }
20
20
21 variant('node') {
21 variant('node') {
22 role('main') { layers('main') }
22 role('main') { layers('main') }
23 }
23 }
24 }
24 }
25
25
26 variantSources {
26 variantSources {
27 bind('main').sourceSetNamePattern = '{layer}'
27 bind('main').sourceSetNamePattern = '{layer}'
28
28
29 bind('main') {
29 bind('main') {
30 configureSourceSet {
30 configureSourceSet {
31 declareOutputs('compiled')
31 declareOutputs('compiled')
32 }
32 }
33 }
33 }
34
34
35 whenRegistered { sourceSetName() }
35 whenRegistered { sourceSetName() }
36 whenBound('browser') { roleName() }
36 whenBound('browser') { roleName() }
37 }
37 }
38 ```
38 ```
39
39
40 ## DESCRIPTION
40 ## DESCRIPTION
41
41
42 `VariantsSourcesPlugin` применяет `VariantsPlugin` и `SourcesPlugin`, затем
42 `VariantsSourcesPlugin` применяет `VariantsPlugin` и `SourcesPlugin`, затем
43 регистрирует source sets из модели `variants`.
43 регистрирует source sets из модели `variants`.
44
44
45 Точка запуска registration:
45 Точка запуска registration:
46
46
47 - `variants.whenFinalized(model -> registerSourceSets(...))`
47 - `variants.whenFinalized(model -> registerSourceSets(...))`
48
48
49 ### registration
49 ### registration
50
50
51 Для каждой usage-связки `variant/role/layer` вычисляется имя source set,
51 Для каждой usage-связки `variant/role/layer` вычисляется имя source set,
52 регистрируется `GenericSourceSet` (если он еще не существует), затем
52 регистрируется `GenericSourceSet` (если он еще не существует), затем
53 вызываются callbacks.
53 вызываются callbacks.
54
54
55 ### binding
55 ### binding
56
56
57 `bind('<layer>')` возвращает `BuildLayerBinding` и задает policy для этого
57 `bind('<layer>')` возвращает `BuildLayerBinding` и задает policy для этого
58 слоя:
58 слоя:
59
59
60 - как именовать source set;
60 - как именовать source set;
61 - как конфигурировать source set;
61 - как конфигурировать source set;
62 - какие callbacks вызвать на registration/binding.
62 - какие callbacks вызвать на registration/binding.
63
63
64 ### sourceSetNamePattern
64 ### sourceSetNamePattern
65
65
66 `sourceSetNamePattern` определяет naming policy зарегистрированного source set.
66 `sourceSetNamePattern` определяет naming policy зарегистрированного source set.
67
67
68 Default:
68 Default:
69
69
70 - `{variant}{layerCap}`
70 - `{variant}{layerCap}`
71
71
72 Tokens:
72 Tokens:
73
73
74 - `{variant}`, `{variantCap}`
74 - `{variant}`, `{variantCap}`
75 - `{role}`, `{roleCap}`
75 - `{role}`, `{roleCap}`
76 - `{layer}`, `{layerCap}`
76 - `{layer}`, `{layerCap}`
77
77
78 Имя санитизируется (`[^A-Za-z0-9_.-] -> _`).
78 Имя санитизируется (`[^A-Za-z0-9_.-] -> _`).
79
79
80 Ограничение:
80 Ограничение:
81
81
82 - один `sourceSetName` не может быть порожден разными слоями.
82 - один `sourceSetName` не может быть порожден разными слоями.
83
83
84 ## EVENTS
84 ## EVENTS
85
85
86 ### whenRegistered
86 ### whenRegistered
87
87
88 - callback на новый уникальный source set;
88 - callback на новый уникальный source set;
89 - replayable;
89 - replayable;
90 - при shared source set срабатывает один раз.
90 - при shared source set срабатывает один раз.
91
91
92 ### whenBound
92 ### whenBound
93
93
94 - callback на каждую usage-связку `variant/role/layer`;
94 - callback на каждую usage-связку `variant/role/layer`;
95 - replayable;
95 - replayable;
96 - подходит для per-usage логики.
96 - подходит для per-usage логики.
97
97
98 ### variant filter
98 ### variant filter
99
99
100 Глобальные callbacks поддерживают фильтр по варианту:
100 Фильтр по варианту поддерживает только usage-binding:
101
101
102 - `whenRegistered(String variantName, ...)`
103 - `whenBound(String variantName, ...)`
102 - `whenBound(String variantName, ...)`
104
103
105 ## SOURCE SET CONTEXT
104 ## PAYLOAD TYPES
105
106 `SourceSetRegistration` содержит:
106
107
107 `SourceSetContext` содержит:
108 - `layerName`, `sourceSetName`;
109 - `sourceSet` (`NamedDomainObjectProvider<GenericSourceSet>`).
110
111 Sugar:
112
113 - `configureSourceSet(Action|Closure)`.
114
115 `SourceSetUsageBinding` содержит:
108
116
109 - `variantName`, `roleName`, `layerName`, `sourceSetName`;
117 - `variantName`, `roleName`, `layerName`, `sourceSetName`;
110 - `sourceSet` (`NamedDomainObjectProvider<GenericSourceSet>`).
118 - `sourceSet` (`NamedDomainObjectProvider<GenericSourceSet>`).
111
119
112 Sugar:
120 Sugar:
113
121
114 - `configureSourceSet(Action|Closure)`.
122 - `configureSourceSet(Action|Closure)`.
115
123
116 ## API
124 ## API
117
125
118 ### VariantSourcesExtension
126 ### VariantSourcesExtension
119
127
120 - `bind(String)` — получить/создать binding по имени слоя.
128 - `bind(String)` — получить/создать binding по имени слоя.
121 - `bind(String, Action|Closure)` — сконфигурировать binding.
129 - `bind(String, Action|Closure)` — сконфигурировать binding.
122 - `bindings(Action|Closure)` — контейнерная конфигурация bindings.
130 - `bindings(Action|Closure)` — контейнерная конфигурация bindings.
123 - `whenRegistered(...)` — глобальные callbacks регистрации source set.
131 - `whenRegistered(...)` — глобальные callbacks регистрации source set.
124 - `whenBound(...)` — глобальные callbacks usage-binding.
132 - `whenBound(...)` — глобальные callbacks usage-binding.
133 - `whenBound(String variantName, ...)` — usage-binding callbacks с variant-filter.
125
134
126 ### BuildLayerBinding
135 ### BuildLayerBinding
127
136
128 - `sourceSetNamePattern` — naming policy для source set слоя.
137 - `sourceSetNamePattern` — naming policy для source set слоя.
129 - `configureSourceSet(...)` — слойная конфигурация `GenericSourceSet`.
138 - `configureSourceSet(...)` — слойная конфигурация `GenericSourceSet`.
130 - `whenRegistered(...)` — callbacks регистрации в рамках слоя.
139 - `whenRegistered(...)` — callbacks регистрации в рамках слоя.
131 - `whenBound(...)` — callbacks usage-binding в рамках слоя.
140 - `whenBound(...)` — callbacks usage-binding в рамках слоя.
132
141
133 ## KEY CLASSES
142 ## KEY CLASSES
134
143
135 - `VariantsSourcesPlugin` — точка входа plugin adapter.
144 - `VariantsSourcesPlugin` — точка входа plugin adapter.
136 - `VariantSourcesExtension` — глобальный DSL bind/events.
145 - `VariantSourcesExtension` — глобальный DSL bind/events.
137 - `BuildLayerBinding` — layer-local policy и callbacks.
146 - `BuildLayerBinding` — layer-local policy и callbacks.
138 - `SourceSetContext` — payload callbacks и sugar-конфигурирование.
147 - `SourceSetRegistration` — payload регистрации source set.
148 - `SourceSetUsageBinding` — payload usage-binding.
139
149
140 ## NOTES
150 ## NOTES
141
151
142 - `sourceSetNamePattern` фиксируется при первом чтении в registration
152 - `sourceSetNamePattern` фиксируется при первом чтении в registration
143 (`finalizeValueOnRead`).
153 (`finalizeValueOnRead`).
144 - Closure callbacks используют delegate-first.
154 - Closure callbacks используют delegate-first.
145 - Для вложенных closure лучше явный параметр (`ctx -> ...`).
155 - Для вложенных closure лучше явный параметр (`ctx -> ...`).
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now