##// END OF EJS Templates
Working on separating variants as standalone plugin
cin -
r38:87d6128f0bc8 default
parent child
Show More
@@ -0,0 +1,41
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.Named;
6 import org.gradle.api.provider.Property;
7
8 import groovy.lang.Closure;
9 import groovy.lang.DelegatesTo;
10
11 /**
12 * Public DSL contract for per-layer source-set policy and callbacks.
13 */
14 public interface LayerBindingSpec extends Named {
15 Property<String> getSourceSetNamePattern();
16
17 default void setSourceSetNamePattern(String pattern) {
18 getSourceSetNamePattern().set(pattern);
19 }
20
21 void configureSourceSet(Action<? super GenericSourceSet> configure);
22
23 default void configureSourceSet(
24 @DelegatesTo(value = GenericSourceSet.class, strategy = Closure.DELEGATE_FIRST) Closure<?> configure) {
25 configureSourceSet(Closures.action(configure));
26 }
27
28 void whenRegistered(Action<? super SourceSetRegistration> action);
29
30 default void whenRegistered(
31 @DelegatesTo(value = SourceSetRegistration.class, strategy = Closure.DELEGATE_FIRST) Closure<?> action) {
32 whenRegistered(Closures.action(action));
33 }
34
35 void whenBound(Action<? super SourceSetUsageBinding> action);
36
37 default void whenBound(
38 @DelegatesTo(value = SourceSetUsageBinding.class, strategy = Closure.DELEGATE_FIRST) Closure<?> action) {
39 whenBound(Closures.action(action));
40 }
41 }
@@ -0,0 +1,53
1 plugins {
2 id "java-library"
3 id "ivy-publish"
4 }
5
6 java {
7 withJavadocJar()
8 withSourcesJar()
9 toolchain {
10 languageVersion = JavaLanguageVersion.of(21)
11 }
12 }
13
14 dependencies {
15 compileOnly libs.jdt.annotations
16
17 api gradleApi(),
18 libs.bundles.jackson
19
20 implementation project(":common")
21
22 testImplementation gradleTestKit()
23 testImplementation "org.junit.jupiter:junit-jupiter-api:5.11.4"
24 testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.11.4"
25 testRuntimeOnly "org.junit.platform:junit-platform-launcher:1.11.4"
26 }
27
28 task printVersion{
29 doLast {
30 println "project: $project.group:$project.name:$project.version"
31 println "jar: ${->jar.archiveFileName.get()}"
32 }
33 }
34
35 test {
36 useJUnitPlatform()
37 }
38
39 publishing {
40 repositories {
41 ivy {
42 url "${System.properties["user.home"]}/ivy-repo"
43 }
44 }
45 publications {
46 ivy(IvyPublication) {
47 from components.java
48 descriptor.description {
49 text = providers.provider({ description })
50 }
51 }
52 }
53 }
@@ -0,0 +1,17
1 package org.implab.gradle.variants;
2
3 import org.gradle.api.Plugin;
4 import org.gradle.api.Project;
5 import org.implab.gradle.variants.model.VariantsExtension;
6
7 public abstract class VariantsPlugin implements Plugin<Project> {
8 @Override
9 public void apply(Project target) {
10 var extension = target.getExtensions().create("variants", VariantsExtension.class);
11
12 target.afterEvaluate(project -> {
13
14 });
15
16 }
17 }
@@ -0,0 +1,9
1 package org.implab.gradle.variants.model;
2
3 import org.gradle.api.Named;
4
5 /**
6 * Identity-only domain object.
7 */
8 public interface Layer extends Named {
9 } No newline at end of file
@@ -0,0 +1,9
1 package org.implab.gradle.variants.model;
2
3 import org.gradle.api.Named;
4
5 /**
6 * Identity-only domain object.
7 */
8 public interface Role extends Named {
9 } No newline at end of file
@@ -0,0 +1,35
1 package org.implab.gradle.variants.model;
2
3 import org.gradle.api.Named;
4 import org.gradle.api.provider.SetProperty;
5
6 /**
7 * Binds a role to a set of layers inside a particular variant.
8 *
9 * The binding name is the role name, e.g. "production", "test", "tool".
10 */
11 public interface RoleBinding extends Named {
12
13 /**
14 * Layer names participating in this (variant, role) selection.
15 *
16 * Core model keeps names here deliberately:
17 * source/materialization semantics live elsewhere.
18 */
19 SetProperty<String> getLayerNames();
20
21 /**
22 * Adds one layer to this binding.
23 */
24 void layer(String name);
25
26 /**
27 * Adds several layers to this binding.
28 */
29 void layers(String... names);
30
31 /**
32 * Adds several layers to this binding.
33 */
34 void layers(Iterable<String> names);
35 } No newline at end of file
@@ -0,0 +1,44
1 package org.implab.gradle.variants.model;
2
3 import org.gradle.api.Action;
4 import org.gradle.api.Named;
5 import org.gradle.api.NamedDomainObjectContainer;
6 import org.implab.gradle.common.core.lang.Closures;
7
8 import groovy.lang.Closure;
9
10 /**
11 * A named variant, e.g. "browser", "electron".
12 *
13 * A variant does not "have a role" directly.
14 * It owns a set of role bindings.
15 */
16 public interface Variant extends Named {
17
18 /**
19 * Role bindings declared inside this variant.
20 *
21 * The binding name is the role name.
22 */
23 NamedDomainObjectContainer<RoleBinding> getRoleBindings();
24
25 /**
26 * Creates or returns an existing role binding and configures it.
27 */
28 default RoleBinding role(String name) {
29 return getRoleBindings().maybeCreate(name);
30 }
31
32 /**
33 * Creates or returns an existing role binding and configures it.
34 */
35 default RoleBinding role(String name, Action<? super RoleBinding> action) {
36 var role = role(name);
37 action.execute(role);
38 return role;
39 }
40
41 default RoleBinding role(String name, Closure<?> closure) {
42 return role(name, Closures.action(closure));
43 }
44 } No newline at end of file
@@ -0,0 +1,59
1 package org.implab.gradle.variants.model;
2
3 import org.gradle.api.Action;
4 import org.gradle.api.NamedDomainObjectContainer;
5 import org.implab.gradle.common.core.lang.Closures;
6
7 import groovy.lang.Closure;
8
9 /**
10 * Root extension:
11 *
12 * variants {
13 * layers { ... }
14 * roles { ... }
15 *
16 * variant("browser") {
17 * role("production") {
18 * layers("main", "generated", "mainRjs")
19 * }
20 * }
21 * }
22 */
23 public interface VariantsExtension {
24
25 /**
26 * Domain of layers.
27 */
28 NamedDomainObjectContainer<Layer> getLayers();
29
30 /**
31 * Domain of roles.
32 */
33 NamedDomainObjectContainer<Role> getRoles();
34
35 /**
36 * Declared variants.
37 */
38 NamedDomainObjectContainer<Variant> getVariantDefinitions();
39
40 /**
41 * Creates or returns an existing variant and configures it.
42 */
43 default Variant variant(String name) {
44 return getVariantDefinitions().maybeCreate(name);
45 }
46
47 /**
48 * Creates or returns an existing variant and configures it.
49 */
50 default Variant variant(String name, Action<? super Variant> action) {
51 var variant = variant(name);
52 action.execute(variant);
53 return variant;
54 }
55
56 default Variant variant(String name, Closure<?> closure) {
57 return variant(name, Closures.action(closure));
58 }
59 } No newline at end of file
@@ -87,11 +87,11 outputs. Это уже "физический" уровень, к которому удобно привязывать задачи,
87 87
88 88 ## DOMAIN MODEL
89 89
90 - `BuildLayer` — глобальный идентификатор слоя.
90 - `BuildLayer` — canonical identity-model объявленного слоя.
91 91 - `BuildVariant` — агрегат ролей, атрибутов, артефактных слотов.
92 - `BuildRole` — роль внутри варианта, содержит ссылки на layer names.
92 - `BuildRole` — роль внутри варианта, содержит ссылки на declared layer names.
93 93 - `GenericSourceSet` — зарегистрированный набор исходников и outputs.
94 - `BuildLayerBinding` — правила registration source set для конкретного layer.
94 - `LayerBindingSpec` — публичный DSL-contract adapter policy/callbacks для слоя.
95 95 - `SourceSetRegistration` — payload события регистрации source set.
96 96 - `SourceSetUsageBinding` — payload события usage-binding.
97 97
@@ -116,7 +116,7 Closure callbacks работают в delegate-first режиме (`@DelegatesTo`). Для
116 116 - `BuildVariant` — API ролей, attributes и artifact slots варианта.
117 117 - `VariantsSourcesPlugin` — применяет `variants` + `sources` и запускает адаптер.
118 118 - `VariantSourcesExtension` — API bind/events registration.
119 - `BuildLayerBinding` — слой-конкретный DSL для имени и конфигурации source set.
119 - `LayerBindingSpec` — слой-конкретный DSL для policy/configuration source set.
120 120 - `SourceSetRegistration` — payload `whenRegistered(...)`.
121 121 - `SourceSetUsageBinding` — payload `whenBound(...)`.
122 122
@@ -20,22 +20,17 import org.gradle.api.model.ObjectFactor
20 20 import groovy.lang.Closure;
21 21
22 22 public abstract class BuildVariantsExtension {
23 private final NamedDomainObjectContainer<BuildLayer> layers;
23 private final ObjectFactory objects;
24 private final LinkedHashMap<String, LayoutLayer> layersByName = new LinkedHashMap<>();
24 25 private final NamedDomainObjectContainer<BuildVariant> variants;
25 26 private final List<Action<? super BuildVariantsExtension>> finalizedActions = new ArrayList<>();
26 27 private boolean finalized;
27 28
28 29 @Inject
29 30 public BuildVariantsExtension(ObjectFactory objects) {
30 layers = objects.domainObjectContainer(BuildLayer.class);
31 this.objects = objects;
31 32 variants = objects.domainObjectContainer(BuildVariant.class);
32 33
33 layers.all(layer -> {
34 if (finalized)
35 throw new InvalidUserDataException(
36 "Variants model is finalized and cannot add layer '" + layer.getName() + "'");
37 });
38
39 34 variants.all(variant -> {
40 35 if (finalized)
41 36 throw new InvalidUserDataException(
@@ -43,44 +38,26 public abstract class BuildVariantsExten
43 38 });
44 39 }
45 40
46 public NamedDomainObjectContainer<BuildLayer> getLayers() {
47 return layers;
41 public Collection<LayoutLayer> getLayers() {
42 return Collections.unmodifiableCollection(layersByName.values());
48 43 }
49 44
50 45 public NamedDomainObjectContainer<BuildVariant> getVariants() {
51 46 return variants;
52 47 }
53 48
54 public void layers(Action<? super NamedDomainObjectContainer<BuildLayer>> action) {
49 public LayoutLayer layer(String name, Action<? super LayoutLayer> configure) {
55 50 ensureMutable("configure layers");
56 action.execute(layers);
57 }
58
59 public void layers(Closure<?> configure) {
60 layers(Closures.action(configure));
61 }
62
63 public void variants(Action<? super NamedDomainObjectContainer<BuildVariant>> action) {
64 ensureMutable("configure variants");
65 action.execute(variants);
66 }
67
68 public void variants(Closure<?> configure) {
69 variants(Closures.action(configure));
70 }
71
72 public BuildLayer layer(String name, Action<? super BuildLayer> configure) {
73 ensureMutable("configure layers");
74 var layer = layers.maybeCreate(name);
51 var layer = layersByName.computeIfAbsent(requireName(name, "Layer name must not be null or blank"), this::newLayer);
75 52 configure.execute(layer);
76 53 return layer;
77 54 }
78 55
79 public BuildLayer layer(String name, Closure<?> configure) {
56 public LayoutLayer layer(String name, Closure<?> configure) {
80 57 return layer(name, Closures.action(configure));
81 58 }
82 59
83 public BuildLayer layer(String name) {
60 public LayoutLayer layer(String name) {
84 61 return layer(name, it -> {
85 62 });
86 63 }
@@ -115,6 +92,16 public abstract class BuildVariantsExten
115 92 return Collections.unmodifiableList(all);
116 93 }
117 94
95 public Optional<LayoutLayer> findLayer(String name) {
96 var normalizedName = normalize(name);
97 return normalizedName == null ? Optional.empty() : Optional.ofNullable(layersByName.get(normalizedName));
98 }
99
100 public LayoutLayer requireLayer(String name) {
101 return findLayer(name)
102 .orElseThrow(() -> new InvalidUserDataException("Layer '" + name + "' isn't defined"));
103 }
104
118 105 public Optional<BuildVariant> find(String name) {
119 106 return Optional.ofNullable(variants.findByName(name));
120 107 }
@@ -160,20 +147,6 public abstract class BuildVariantsExten
160 147 public void validate() {
161 148 var errors = new ArrayList<String>();
162 149
163 var layersByName = new LinkedHashMap<String, BuildLayer>();
164 for (var layer : layers) {
165 var layerName = normalize(layer.getName());
166 if (layerName == null) {
167 errors.add("Layer name must not be blank");
168 continue;
169 }
170
171 var previous = layersByName.putIfAbsent(layerName, layer);
172 if (previous != null) {
173 errors.add("Layer '" + layerName + "' is declared more than once");
174 }
175 }
176
177 150 for (var variant : variants)
178 151 validateVariant(variant, layersByName, errors);
179 152
@@ -186,7 +159,7 public abstract class BuildVariantsExten
186 159 }
187 160 }
188 161
189 private static void validateVariant(BuildVariant variant, Map<String, BuildLayer> layersByName, List<String> errors) {
162 private static void validateVariant(BuildVariant variant, Map<String, LayoutLayer> layersByName, List<String> errors) {
190 163 var variantName = normalize(variant.getName());
191 164 if (variantName == null) {
192 165 errors.add("Variant name must not be blank");
@@ -211,7 +184,7 public abstract class BuildVariantsExten
211 184 }
212 185 }
213 186
214 private static void validateRoleMappings(BuildVariant variant, Map<String, BuildLayer> layersByName,
187 private static void validateRoleMappings(BuildVariant variant, Map<String, LayoutLayer> layersByName,
215 188 List<String> errors) {
216 189 for (var role : variant.getRoles()) {
217 190 var seenLayers = new LinkedHashSet<String>();
@@ -244,6 +217,17 public abstract class BuildVariantsExten
244 217 return trimmed.isEmpty() ? null : trimmed;
245 218 }
246 219
220 private static String requireName(String value, String errorMessage) {
221 var normalized = normalize(value);
222 if (normalized == null)
223 throw new InvalidUserDataException(errorMessage);
224 return normalized;
225 }
226
227 private LayoutLayer newLayer(String name) {
228 return objects.newInstance(LayoutLayer.class, name);
229 }
230
247 231 private void ensureMutable(String operation) {
248 232 if (finalized)
249 233 throw new InvalidUserDataException("Variants model is finalized and cannot " + operation);
@@ -5,24 +5,16 import java.util.LinkedHashSet;
5 5 import java.util.List;
6 6 import java.util.Set;
7 7
8 import javax.inject.Inject;
9
10 import org.implab.gradle.common.core.lang.Closures;
11 8 import org.gradle.api.Action;
12 import org.gradle.api.Named;
13 9 import org.gradle.api.NamedDomainObjectProvider;
10 import org.gradle.api.model.ObjectFactory;
14 11 import org.gradle.api.provider.Property;
15 12
16 import groovy.lang.Closure;
17 import groovy.lang.DelegatesTo;
18
19 /**
20 * Maps a logical layer to per-source-set hooks.
21 */
22 public abstract class BuildLayerBinding implements Named {
23 public static final String DEFAULT_SOURCE_SET_NAME_PATTERN = "{variant}{layerCap}";
13 final class LayerBinding implements LayerBindingSpec {
14 static final String DEFAULT_SOURCE_SET_NAME_PATTERN = "{variant}{layerCap}";
24 15
25 16 private final String name;
17 private final Property<String> sourceSetNamePattern;
26 18
27 19 private final List<Action<? super GenericSourceSet>> sourceSetConfigureActions = new ArrayList<>();
28 20 private final List<Action<? super SourceSetRegistration>> registeredActions = new ArrayList<>();
@@ -32,10 +24,10 public abstract class BuildLayerBinding
32 24 private final List<SourceSetUsageBinding> boundContexts = new ArrayList<>();
33 25 private final Set<String> registeredSourceSetNames = new LinkedHashSet<>();
34 26
35 @Inject
36 public BuildLayerBinding(String name) {
27 LayerBinding(String name, ObjectFactory objects) {
37 28 this.name = name;
38 getSourceSetNamePattern().convention(DEFAULT_SOURCE_SET_NAME_PATTERN);
29 sourceSetNamePattern = objects.property(String.class);
30 sourceSetNamePattern.convention(DEFAULT_SOURCE_SET_NAME_PATTERN);
39 31 }
40 32
41 33 @Override
@@ -43,61 +35,32 public abstract class BuildLayerBinding
43 35 return name;
44 36 }
45 37
46 public abstract Property<String> getSourceSetNamePattern();
38 @Override
39 public Property<String> getSourceSetNamePattern() {
40 return sourceSetNamePattern;
41 }
47 42
48 /**
49 * Action applied to every registered source set for this layer.
50 * Already registered source sets are configured immediately (replay).
51 */
43 @Override
52 44 public void configureSourceSet(Action<? super GenericSourceSet> configure) {
53 45 sourceSetConfigureActions.add(configure);
54 46 for (var sourceSet : registeredSourceSets)
55 47 sourceSet.configure(configure);
56 48 }
57 49
58 public void configureSourceSet(
59 @DelegatesTo(value = GenericSourceSet.class, strategy = Closure.DELEGATE_FIRST) Closure<?> configure) {
60 configureSourceSet(Closures.action(configure));
61 }
62
63 /**
64 * Layer-local callback fired after source-set registration.
65 * Already emitted registrations are delivered immediately (replay).
66 * For simple callbacks you can use delegate-only style
67 * (for example {@code whenRegistered { sourceSetName() }}).
68 * For nested closures prefer explicit parameter
69 * ({@code whenRegistered { ctx -> ... }}).
70 */
50 @Override
71 51 public void whenRegistered(Action<? super SourceSetRegistration> action) {
72 52 registeredActions.add(action);
73 53 for (var context : registeredContexts)
74 54 action.execute(context);
75 55 }
76 56
77 public void whenRegistered(
78 @DelegatesTo(value = SourceSetRegistration.class, strategy = Closure.DELEGATE_FIRST) Closure<?> action) {
79 whenRegistered(Closures.action(action));
80 }
81
82 /**
83 * Layer-local callback fired for every resolved variant/role/layer usage.
84 * Already emitted usage bindings are delivered immediately (replay).
85 * For simple callbacks you can use delegate-only style
86 * (for example {@code whenBound { variantName() }}).
87 * For nested closures prefer explicit parameter
88 * ({@code whenBound { ctx -> ... }}).
89 */
57 @Override
90 58 public void whenBound(Action<? super SourceSetUsageBinding> action) {
91 59 boundActions.add(action);
92 60 for (var context : boundContexts)
93 61 action.execute(context);
94 62 }
95 63
96 public void whenBound(
97 @DelegatesTo(value = SourceSetUsageBinding.class, strategy = Closure.DELEGATE_FIRST) Closure<?> action) {
98 whenBound(Closures.action(action));
99 }
100
101 64 void notifyRegistered(SourceSetRegistration registration) {
102 65 if (registeredSourceSetNames.add(registration.sourceSetName())) {
103 66 var sourceSet = registration.sourceSet();
@@ -5,13 +5,13 import javax.inject.Inject;
5 5 import org.gradle.api.Named;
6 6
7 7 /**
8 * Global layer declaration used by build variants.
8 * Canonical identity model for a declared layout layer.
9 9 */
10 public abstract class BuildLayer implements Named {
10 public abstract class LayoutLayer implements Named {
11 11 private final String name;
12 12
13 13 @Inject
14 public BuildLayer(String name) {
14 public LayoutLayer(String name) {
15 15 this.name = name;
16 16 }
17 17
@@ -12,7 +12,7 import groovy.lang.DelegatesTo;
12 12 *
13 13 * <p>Used as callback payload for
14 14 * {@link VariantSourcesExtension#whenRegistered(org.gradle.api.Action)} and
15 * {@link BuildLayerBinding#whenRegistered(org.gradle.api.Action)}.
15 * {@link LayerBindingSpec#whenRegistered(org.gradle.api.Action)}.
16 16 *
17 17 * @param layerName normalized layer name that owns the registration
18 18 * @param sourceSetName source-set name registered in the container
@@ -12,7 +12,7 import groovy.lang.DelegatesTo;
12 12 *
13 13 * <p>Used as callback payload for
14 14 * {@link VariantSourcesExtension#whenBound(org.gradle.api.Action)} and
15 * {@link BuildLayerBinding#whenBound(org.gradle.api.Action)}.
15 * {@link LayerBindingSpec#whenBound(org.gradle.api.Action)}.
16 16 *
17 17 * @param variantName variant name from the build-variants model
18 18 * @param roleName role name inside the resolved variant
@@ -124,6 +124,10 public class VariantArtifactSlot impleme
124 124 return List.copyOf(bindings);
125 125 }
126 126
127 void acceptBindings(Consumer<? super BindingResolver> consumer) {
128 bindings.forEach(consumer);
129 }
130
127 131 Set<String> referencedRoleNames() {
128 132 return Set.copyOf(referencedRoleNames);
129 133 }
@@ -33,7 +33,8 public abstract class VariantArtifactsPl
33 33 var variantArtifactsResolver = new VariantArtifactsResolver(target.getObjects());
34 34 var artifactAssemblies = new ArtifactAssemblyRegistry(target.getObjects(), target.getTasks());
35 35
36 // Bind variant artifacts resolution to variant sources registration, so that artifact resolution can be performed
36 // Bind variant artifacts resolution to variant sources registration, so that
37 // artifact resolution can be performed
37 38 variantSources.whenBound(variantArtifactsResolver::recordBinding);
38 39
39 40 variants.whenFinalized(model -> {
@@ -86,7 +87,8 public abstract class VariantArtifactsPl
86 87 var assemblies = variantArtifact.getSlots().stream()
87 88 .collect(Collectors.toMap(
88 89 VariantArtifactSlot::getName,
89 slot -> registerAssembly(project, variantArtifactsResolver, artifactAssemblies, variantArtifact, slot),
90 slot -> registerAssembly(project, variantArtifactsResolver, artifactAssemblies, variantArtifact,
91 slot),
90 92 (left, right) -> left,
91 93 LinkedHashMap::new));
92 94
@@ -136,9 +138,10 public abstract class VariantArtifactsPl
136 138 ArtifactAssemblyRegistry artifactAssemblies,
137 139 VariantArtifact variantArtifact,
138 140 VariantArtifactSlot slot) {
141 String assemblyName = variantArtifact.getName() + Strings.capitalize(slot.getName());
139 142 return artifactAssemblies.register(
140 variantArtifact.getName() + Strings.capitalize(slot.getName()),
141 "process" + Strings.capitalize(variantArtifact.getName()) + Strings.capitalize(slot.getName()),
143 assemblyName,
144 "process" + Strings.capitalize(assemblyName),
142 145 project.getLayout().getBuildDirectory()
143 146 .dir("variant-artifacts/" + variantArtifact.getName() + "/" + slot.getName()),
144 147 files -> files.from(variantArtifactsResolver.files(variantArtifact.getName(), slot)));
@@ -1,6 +1,7
1 1 package org.implab.gradle.common.sources;
2 2
3 3 import java.util.ArrayList;
4 import java.util.Collection;
4 5 import java.util.LinkedHashSet;
5 6 import java.util.List;
6 7 import java.util.Set;
@@ -86,13 +87,11 public final class VariantArtifactsResol
86 87 * @return lazily wired file collection for the selected outputs
87 88 */
88 89 public FileCollection files(String variantName, VariantArtifactSlot slot) {
89 var builder = new FileCollectionBuilder();
90 90 var contexts = boundContexts.stream()
91 91 .filter(context -> variantName.equals(context.variantName()))
92 92 .toList();
93
94 slot.bindings().forEach(binding -> binding.resolve(contexts, builder::addOutput));
95
93 var builder = new FileCollectionBuilder(contexts);
94 slot.acceptBindings(builder::visitBinding);
96 95 return builder.build();
97 96 }
98 97
@@ -103,9 +102,11 public final class VariantArtifactsResol
103 102 class FileCollectionBuilder {
104 103 private final ConfigurableFileCollection files;
105 104 private final Set<BindingKey> boundOutputs = new LinkedHashSet<>();
105 private final Collection<SourceSetUsageBinding> contexts;
106 106
107 FileCollectionBuilder() {
107 FileCollectionBuilder(Collection<SourceSetUsageBinding> contexts) {
108 108 this.files = objects.fileCollection();
109 this.contexts = contexts;
109 110 }
110 111
111 112 FileCollection build() {
@@ -116,6 +117,10 public final class VariantArtifactsResol
116 117 if (boundOutputs.add(binding.key()))
117 118 files.from(binding.files());
118 119 }
120
121 void visitBinding(VariantArtifactSlot.BindingResolver resolver) {
122 resolver.resolve(contexts, this::addOutput);
123 }
119 124 }
120 125
121 126 }
@@ -15,7 +15,6 import org.eclipse.jdt.annotation.NonNul
15 15 import org.eclipse.jdt.annotation.Nullable;
16 16 import org.gradle.api.Action;
17 17 import org.gradle.api.InvalidUserDataException;
18 import org.gradle.api.NamedDomainObjectContainer;
19 18 import org.gradle.api.NamedDomainObjectProvider;
20 19 import org.gradle.api.file.ProjectLayout;
21 20 import org.gradle.api.model.ObjectFactory;
@@ -35,8 +34,9 public abstract class VariantSourcesExte
35 34 private static final Logger logger = Logging.getLogger(VariantSourcesExtension.class);
36 35 private static final Pattern SOURCE_SET_NAME_TOKEN = Pattern.compile("\\{([A-Za-z][A-Za-z0-9]*)\\}");
37 36
37 private final ObjectFactory objects;
38 38 private final ProjectLayout layout;
39 private final NamedDomainObjectContainer<BuildLayerBinding> bindings;
39 private final LinkedHashMap<String, LayerBinding> bindingsByName = new LinkedHashMap<>();
40 40 private final List<Action<? super SourceSetRegistration>> registeredActions = new ArrayList<>();
41 41 private final List<Action<? super SourceSetUsageBinding>> boundActions = new ArrayList<>();
42 42 private final List<SourceSetRegistration> registeredContexts = new ArrayList<>();
@@ -47,38 +47,46 public abstract class VariantSourcesExte
47 47
48 48 @Inject
49 49 public VariantSourcesExtension(ObjectFactory objects, ProjectLayout layout) {
50 this.objects = objects;
50 51 this.layout = layout;
51 bindings = objects.domainObjectContainer(BuildLayerBinding.class);
52 52 }
53 53
54 public NamedDomainObjectContainer<BuildLayerBinding> getBindings() {
55 return bindings;
54 public List<LayerBindingSpec> getBindings() {
55 return bindingsByName.values().stream().map(x -> (LayerBindingSpec)x).toList();
56 56 }
57 57
58 public void bindings(Action<? super NamedDomainObjectContainer<BuildLayerBinding>> action) {
59 action.execute(bindings);
58 public LayerBindingSpec bind(String layer) {
59 return bindingsByName.computeIfAbsent(
60 normalize(layer, "Layer name must not be null or blank"),
61 name -> new LayerBinding(name, objects));
60 62 }
61 63
62 public void bindings(
63 @DelegatesTo(value = NamedDomainObjectContainer.class, strategy = Closure.DELEGATE_FIRST) Closure<?> action) {
64 bindings(Closures.action(action));
65 }
66
67 public BuildLayerBinding bind(String layer) {
68 return bindings.maybeCreate(normalize(layer, "Layer name must not be null or blank"));
64 public LayerBindingSpec bind(LayoutLayer layer) {
65 return bind(layer.getName());
69 66 }
70 67
71 68 /**
72 69 * Configures per-layer binding.
73 70 */
74 public BuildLayerBinding bind(String layer, Action<? super BuildLayerBinding> configure) {
71 public LayerBindingSpec bind(String layer, Action<? super LayerBindingSpec> configure) {
75 72 var binding = bind(layer);
76 73 configure.execute(binding);
77 74 return binding;
78 75 }
79 76
80 public BuildLayerBinding bind(String layer,
81 @DelegatesTo(value = BuildLayerBinding.class, strategy = Closure.DELEGATE_FIRST) Closure<?> configure) {
77 public LayerBindingSpec bind(LayoutLayer layer, Action<? super LayerBindingSpec> configure) {
78 var binding = bind(layer);
79 configure.execute(binding);
80 return binding;
81 }
82
83 public LayerBindingSpec bind(String layer,
84 @DelegatesTo(value = LayerBindingSpec.class, strategy = Closure.DELEGATE_FIRST) Closure<?> configure) {
85 return bind(layer, Closures.action(configure));
86 }
87
88 public LayerBindingSpec bind(LayoutLayer layer,
89 @DelegatesTo(value = LayerBindingSpec.class, strategy = Closure.DELEGATE_FIRST) Closure<?> configure) {
82 90 return bind(layer, Closures.action(configure));
83 91 }
84 92
@@ -130,12 +138,12 public abstract class VariantSourcesExte
130 138 whenBound(variantName, Closures.action(action));
131 139 }
132 140
133 void registerSourceSets(BuildVariantsExtension variants, NamedDomainObjectContainer<GenericSourceSet> sources) {
141 void registerSourceSets(BuildVariantsExtension variants, org.gradle.api.NamedDomainObjectContainer<GenericSourceSet> sources) {
134 142 if (sourceSetsRegistered) {
135 143 throw new InvalidUserDataException("variantSources source sets are already registered");
136 144 }
137 145
138 validateBindings(variants);
146 resolveBindings(variants);
139 147
140 148 var usages = layerUsages(variants).toList();
141 149 var registeredBefore = registeredContexts.size();
@@ -145,7 +153,7 public abstract class VariantSourcesExte
145 153 "Starting variant source-set registration (variants={}, layers={}, bindings={}, usages={})",
146 154 variants.getVariants().size(),
147 155 variants.getLayers().size(),
148 bindings.size(),
156 bindingsByName.size(),
149 157 usages.size());
150 158
151 159 usages.forEach(usage -> registerLayerUsage(usage, sources));
@@ -166,24 +174,25 public abstract class VariantSourcesExte
166 174 .map(layerName -> new LayerUsage(
167 175 variant.getName(),
168 176 role.getName(),
169 normalize(layerName, "Layer name in variant '" + variant.getName()
170 + "' and role '" + role.getName() + "' must not be null or blank")))));
177 variants.requireLayer(normalize(layerName, "Layer name in variant '"
178 + variant.getName() + "' and role '" + role.getName()
179 + "' must not be null or blank"))))));
171 180 }
172 181
173 private void registerLayerUsage(LayerUsage usage, NamedDomainObjectContainer<GenericSourceSet> sources) {
174 var resolvedBinding = bind(usage.layerName());
182 private void registerLayerUsage(LayerUsage usage, org.gradle.api.NamedDomainObjectContainer<GenericSourceSet> sources) {
183 var resolvedBinding = binding(usage.layer().getName());
175 184 var sourceSetNamePattern = resolvedBinding.getSourceSetNamePattern();
176 185 sourceSetNamePattern.finalizeValueOnRead();
177 186
178 187 var sourceSetName = sourceSetName(usage, sourceSetNamePattern.get());
179 188
180 ensureSourceSetNameBoundToSingleLayer(sourceSetName, usage.layerName());
189 ensureSourceSetNameBoundToSingleLayer(sourceSetName, usage.layer().getName());
181 190 var isNewSourceSet = !sourceSetsByName.containsKey(sourceSetName);
182 191 var sourceSet = sourceSetsByName.computeIfAbsent(sourceSetName,
183 192 name -> {
184 193 var ssp = sources.register(name);
185 194 ssp.configure(x -> {
186 x.getSourceSetDir().set(layout.getProjectDirectory().dir("src/" + usage.layerName()));
195 x.getSourceSetDir().set(layout.getProjectDirectory().dir("src/" + usage.layer().getName()));
187 196 });
188 197 return ssp;
189 198 });
@@ -191,13 +200,13 public abstract class VariantSourcesExte
191 200 var binding = new SourceSetUsageBinding(
192 201 usage.variantName(),
193 202 usage.roleName(),
194 usage.layerName(),
203 usage.layer().getName(),
195 204 sourceSetName,
196 205 sourceSet);
197 206
198 207 if (isNewSourceSet) {
199 208 var registration = new SourceSetRegistration(
200 usage.layerName(),
209 usage.layer().getName(),
201 210 sourceSetName,
202 211 sourceSet);
203 212 resolvedBinding.notifyRegistered(registration);
@@ -236,14 +245,10 public abstract class VariantSourcesExte
236 245 }
237 246 }
238 247
239 private void validateBindings(BuildVariantsExtension variants) {
240 var knownLayerNames = new java.util.LinkedHashSet<String>();
241 for (var layer : variants.getLayers())
242 knownLayerNames.add(layer.getName());
243
248 private void resolveBindings(BuildVariantsExtension variants) {
244 249 var errors = new ArrayList<String>();
245 for (var binding : bindings) {
246 if (!knownLayerNames.contains(binding.getName())) {
250 for (var binding : bindingsByName.values()) {
251 if (variants.findLayer(binding.getName()).isEmpty()) {
247 252 errors.add("Layer binding '" + binding.getName() + "' references unknown layer");
248 253 }
249 254 }
@@ -256,6 +261,10 public abstract class VariantSourcesExte
256 261 }
257 262 }
258 263
264 private LayerBinding binding(String layerName) {
265 return bindingsByName.computeIfAbsent(layerName, name -> new LayerBinding(name, objects));
266 }
267
259 268 private static String sourceSetName(LayerUsage usage, String pattern) {
260 269 var normalizedPattern = normalize(pattern, "sourceSetNamePattern must not be null or blank");
261 270 var resolved = resolveSourceSetNamePattern(normalizedPattern, usage);
@@ -287,8 +296,8 public abstract class VariantSourcesExte
287 296 case "variantCap" -> Strings.capitalize(sanitizeName(usage.variantName()));
288 297 case "role" -> sanitizeName(usage.roleName());
289 298 case "roleCap" -> Strings.capitalize(sanitizeName(usage.roleName()));
290 case "layer" -> sanitizeName(usage.layerName());
291 case "layerCap" -> Strings.capitalize(sanitizeName(usage.layerName()));
299 case "layer" -> sanitizeName(usage.layer().getName());
300 case "layerCap" -> Strings.capitalize(sanitizeName(usage.layer().getName()));
292 301 default -> throw new InvalidUserDataException(
293 302 "sourceSetNamePattern contains unsupported token '{" + token + "}'");
294 303 };
@@ -303,6 +312,6 public abstract class VariantSourcesExte
303 312 return trimmed;
304 313 }
305 314
306 private record LayerUsage(String variantName, String roleName, String layerName) {
315 private record LayerUsage(String variantName, String roleName, LayoutLayer layer) {
307 316 }
308 317 }
@@ -41,8 +41,8 class VariantsArtifactsPluginFunctionalT
41 41 }
42 42
43 43 variants {
44 layer('mainBase')
45 layer('mainAmd')
44 layers('mainBase', 'mainAmd')
45 roles('main', 'test')
46 46
47 47 variant('browser') {
48 48 role('main') {
@@ -64,6 +64,41 class VariantsPluginFunctionalTest {
64 64 }
65 65
66 66 @Test
67 void supportsLayerRegistryDslAndLookupApi() throws Exception {
68 writeFile(SETTINGS_FILE, ROOT_NAME);
69 writeFile(BUILD_FILE, """
70 plugins {
71 id 'org.implab.gradle-variants'
72 }
73
74 variants {
75 layer('mainBase')
76 layer('mainAmd')
77
78 variant('browser') {
79 role('main') {
80 layers('mainBase', 'mainAmd')
81 }
82 }
83 }
84
85 tasks.register('probe') {
86 doLast {
87 println('layers=' + variants.layers.collect { it.name }.sort().join(','))
88 println('requireLayer=' + variants.requireLayer('mainBase').name)
89 println('findLayer=' + variants.findLayer('missing').isPresent())
90 }
91 }
92 """);
93
94 BuildResult result = runner("probe").build();
95
96 assertTrue(result.getOutput().contains("layers=mainAmd,mainBase"));
97 assertTrue(result.getOutput().contains("requireLayer=mainBase"));
98 assertTrue(result.getOutput().contains("findLayer=false"));
99 }
100
101 @Test
67 102 void failsOnUnknownLayerReference() throws Exception {
68 103 assertBuildFails("""
69 104 plugins {
@@ -133,6 +133,83 class VariantsSourcesPluginFunctionalTes
133 133 }
134 134
135 135 @Test
136 void supportsBindingByBuildLayerIdentity() throws Exception {
137 writeFile(SETTINGS_FILE, ROOT_NAME);
138 writeFile(BUILD_FILE, """
139 plugins {
140 id 'org.implab.gradle-variants-sources'
141 }
142
143 variants {
144 layer('main')
145 variant('browser') {
146 role('main') { layers('main') }
147 }
148 }
149
150 variantSources {
151 bind(variants.requireLayer('main')) {
152 configureSourceSet {
153 declareOutputs('compiled')
154 }
155 }
156 }
157
158 tasks.register('probe') {
159 doLast {
160 def ss = sources.getByName('browserMain')
161 ss.output('compiled')
162 println('bindLayerIdentity=ok')
163 }
164 }
165 """);
166
167 BuildResult result = runner("probe").build();
168 assertTrue(result.getOutput().contains("bindLayerIdentity=ok"));
169 }
170
171 @Test
172 void exposesBindingsSnapshot() throws Exception {
173 writeFile(SETTINGS_FILE, ROOT_NAME);
174 writeFile(BUILD_FILE, """
175 plugins {
176 id 'org.implab.gradle-variants-sources'
177 }
178
179 variants {
180 layer('main')
181 layer('extra')
182 variant('browser') {
183 role('main') { layers('main') }
184 }
185 }
186
187 variantSources {
188 bind('main') {
189 sourceSetNamePattern = '{layer}'
190 configureSourceSet {
191 declareOutputs('compiled')
192 }
193 }
194 bind('extra')
195 }
196
197 tasks.register('probe') {
198 doLast {
199 def ss = sources.getByName('main')
200 ss.output('compiled')
201 println("bindings=" + variantSources.bindings.collect { it.name }.sort().join(','))
202 println('bindingsSnapshot=ok')
203 }
204 }
205 """);
206
207 BuildResult result = runner("probe").build();
208 assertTrue(result.getOutput().contains("bindings=extra,main"));
209 assertTrue(result.getOutput().contains("bindingsSnapshot=ok"));
210 }
211
212 @Test
136 213 void failsOnUnknownLayerBinding() throws Exception {
137 214 writeFile(SETTINGS_FILE, ROOT_NAME);
138 215 writeFile(BUILD_FILE, """
@@ -54,7 +54,7 variantSources {
54 54
55 55 ### binding
56 56
57 `bind('<layer>')` возвращает `BuildLayerBinding` и задает policy для этого
57 `bind('<layer>')` возвращает `LayerBindingSpec` и задает policy для этого
58 58 слоя:
59 59
60 60 - как именовать source set;
@@ -125,14 +125,16 Sugar:
125 125
126 126 ### VariantSourcesExtension
127 127
128 - `bind(BuildLayer)` — получить/создать binding для canonical layer identity.
128 129 - `bind(String)` — получить/создать binding по имени слоя.
129 130 - `bind(String, Action|Closure)` — сконфигурировать binding.
130 - `bindings(Action|Closure)`контейнерная конфигурация bindings.
131 - `bind(BuildLayer, Action|Closure)`сконфигурировать binding по `BuildLayer`.
132 - `getBindings()` — read-only snapshot текущих bindings.
131 133 - `whenRegistered(...)` — глобальные callbacks регистрации source set.
132 134 - `whenBound(...)` — глобальные callbacks usage-binding.
133 135 - `whenBound(String variantName, ...)` — usage-binding callbacks с variant-filter.
134 136
135 ### BuildLayerBinding
137 ### LayerBindingSpec
136 138
137 139 - `sourceSetNamePattern` — naming policy для source set слоя.
138 140 - `configureSourceSet(...)` — слойная конфигурация `GenericSourceSet`.
@@ -143,7 +145,7 Sugar:
143 145
144 146 - `VariantsSourcesPlugin` — точка входа plugin adapter.
145 147 - `VariantSourcesExtension` — глобальный DSL bind/events.
146 - `BuildLayerBinding` — layer-local policy и callbacks.
148 - `LayerBindingSpec`публичный DSL-contract layer-local policy/callbacks.
147 149 - `SourceSetRegistration` — payload регистрации source set.
148 150 - `SourceSetUsageBinding` — payload usage-binding.
149 151
@@ -151,5 +153,7 Sugar:
151 153
152 154 - `sourceSetNamePattern` фиксируется при первом чтении в registration
153 155 (`finalizeValueOnRead`).
156 - runtime state bindings скрыт внутри adapter implementation (`LayerBinding`).
157 - name-based bindings резолвятся к canonical `BuildLayer` через registry `variants`.
154 158 - Closure callbacks используют delegate-first.
155 159 - Для вложенных closure лучше явный параметр (`ctx -> ...`).
@@ -81,8 +81,9 Typed-атрибуты (`Attribute<T> -> Provider<T>`) для передачи параметров в
81 81
82 82 - `layer(...)` — объявление или конфигурация `BuildLayer`.
83 83 - `variant(...)` — объявление или конфигурация `BuildVariant`.
84 - `layers { ... }`, `variants { ... }` — контейнерный DSL.
84 - `layers { layer(...) }`, `variants { ... }` — grouped DSL без публикации container API наружу.
85 85 - `all(...)` — callback для всех вариантов.
86 - `findLayer(name)`, `requireLayer(name)`, `getAllLayers()` — доступ к registry слоев.
86 87 - `getAll()`, `find(name)`, `require(name)` — доступ к вариантам.
87 88 - `validate()` — явный запуск валидации.
88 89 - `finalizeModel()` — валидация + финализация модели.
@@ -99,7 +100,7 Typed-атрибуты (`Attribute<T> -> Provider<T>`) для передачи параметров в
99 100 - `VariantsPlugin` — точка входа плагина.
100 101 - `BuildVariantsExtension` — root extension и lifecycle.
101 102 - `BuildVariant` — агрегатная модель варианта.
102 - `BuildLayer`модель слоя.
103 - `BuildLayer`canonical identity-model слоя.
103 104 - `BuildRole` — модель роли.
104 105 - `BuildArtifactSlot` — модель артефактного слота.
105 106 - `VariantAttributes` — typed wrapper для variant attributes.
@@ -107,4 +108,5 Typed-атрибуты (`Attribute<T> -> Provider<T>`) для передачи параметров в
107 108 ## NOTES
108 109
109 110 - Модель `variants` intentionally agnostic к toolchain.
111 - Layer registry хранится как явная identity-map, а не как публичный `NamedDomainObjectContainer`.
110 112 - Интеграция с задачами выполняется через `variantSources` и адаптеры.
@@ -21,3 +21,4 dependencyResolutionManagement {
21 21 rootProject.name = 'gradle-common'
22 22
23 23 include 'common'
24 include 'variants'
General Comments 0
You need to be logged in to leave comments. Login now