##// END OF EJS Templates
variants: expose compile unit source set specs...
cin -
r58:a4138749793f default
parent child
Show More
@@ -0,0 +1,69
1 package org.implab.gradle.variants.sources;
2
3 import org.gradle.api.Action;
4 import org.implab.gradle.common.core.lang.Closures;
5 import org.implab.gradle.variants.core.Layer;
6 import org.implab.gradle.variants.core.Variant;
7
8 import groovy.lang.Closure;
9
10 /**
11 * Source-set materialization context for one compile unit.
12 *
13 * <p>This type keeps compile-unit identity next to the materialized
14 * {@link GenericSourceSet} without turning the selector DSL into a second
15 * source-set API. Use {@link #getCompileUnit()} for selection metadata and
16 * {@link #sourceSet(Action)} when the underlying source set body must be
17 * configured.
18 */
19 public interface CompileUnitSourceSetSpec {
20 /**
21 * Returns the compile unit represented by this source set.
22 *
23 * @return compile-unit identity
24 */
25 CompileUnit getCompileUnit();
26
27 /**
28 * Returns the variant part of the compile-unit identity.
29 *
30 * @return variant identity
31 */
32 default Variant getVariant() {
33 return getCompileUnit().variant();
34 }
35
36 /**
37 * Returns the layer part of the compile-unit identity.
38 *
39 * @return layer identity
40 */
41 default Layer getLayer() {
42 return getCompileUnit().layer();
43 }
44
45 /**
46 * Returns the materialized source set body.
47 *
48 * @return source set body
49 */
50 GenericSourceSet getSourceSet();
51
52 /**
53 * Configures the materialized source set body.
54 *
55 * @param action source set configuration action
56 */
57 default void sourceSet(Action<? super GenericSourceSet> action) {
58 action.execute(getSourceSet());
59 }
60
61 /**
62 * Configures the materialized source set body.
63 *
64 * @param closure source set configuration closure
65 */
66 default void sourceSet(Closure<?> closure) {
67 sourceSet(Closures.action(closure));
68 }
69 }
@@ -0,0 +1,25
1 package org.implab.gradle.variants.sources.internal;
2
3 import org.implab.gradle.variants.sources.CompileUnit;
4 import org.implab.gradle.variants.sources.CompileUnitSourceSetSpec;
5 import org.implab.gradle.variants.sources.GenericSourceSet;
6
7 class DefaultCompileUnitSourceSetSpec implements CompileUnitSourceSetSpec {
8 private final CompileUnit compileUnit;
9 private final GenericSourceSet sourceSet;
10
11 DefaultCompileUnitSourceSetSpec(CompileUnit compileUnit, GenericSourceSet sourceSet) {
12 this.compileUnit = compileUnit;
13 this.sourceSet = sourceSet;
14 }
15
16 @Override
17 public CompileUnit getCompileUnit() {
18 return compileUnit;
19 }
20
21 @Override
22 public GenericSourceSet getSourceSet() {
23 return sourceSet;
24 }
25 }
@@ -1,157 +1,166
1 1 package org.implab.gradle.variants;
2 2
3 3 import java.util.Objects;
4 4 import java.util.function.Predicate;
5 5
6 6 import org.eclipse.jdt.annotation.NonNullByDefault;
7 7 import org.gradle.api.Action;
8 8 import org.gradle.api.InvalidUserDataException;
9 9 import org.gradle.api.Named;
10 10 import org.gradle.api.Plugin;
11 11 import org.gradle.api.Project;
12 12 import org.implab.gradle.common.core.lang.Deferred;
13 13 import org.implab.gradle.common.core.lang.Strings;
14 14 import org.implab.gradle.variants.core.Layer;
15 15 import org.implab.gradle.variants.core.Variant;
16 16 import org.implab.gradle.variants.core.VariantsExtension;
17 17 import org.implab.gradle.variants.core.VariantsView;
18 18 import org.implab.gradle.variants.sources.CompileUnit;
19 import org.implab.gradle.variants.sources.CompileUnitSourceSetSpec;
19 20 import org.implab.gradle.variants.sources.CompileUnitsView;
20 import org.implab.gradle.variants.sources.GenericSourceSet;
21 21 import org.implab.gradle.variants.sources.RoleProjectionsView;
22 22 import org.implab.gradle.variants.sources.VariantSourcesContext;
23 23 import org.implab.gradle.variants.sources.VariantSourcesExtension;
24 24 import org.implab.gradle.variants.sources.internal.CompileUnitNamer;
25 25 import org.implab.gradle.variants.sources.internal.DefaultCompileUnitNamingPolicy;
26 26 import org.implab.gradle.variants.sources.internal.DefaultLateConfigurationPolicySpec;
27 27 import org.implab.gradle.variants.sources.internal.DefaultVariantSourcesContext;
28 28 import org.implab.gradle.variants.sources.internal.SourceSetConfigurationRegistry;
29 29 import org.implab.gradle.variants.sources.internal.SourceSetRegistry;
30 30
31 31 @NonNullByDefault
32 32 public abstract class VariantSourcesPlugin implements Plugin<Project> {
33 33 public static final String VARIANT_SOURCES_EXTENSION = "variantSources";
34 34
35 35 @Override
36 36 public void apply(Project target) {
37 37 var extensions = target.getExtensions();
38 38
39 39 // Apply the main VariantsPlugin to ensure the core variant model is available.
40 40 target.getPlugins().apply(VariantsPlugin.class);
41 41 // Access the VariantsExtension to configure variant sources.
42 42 var variantsExtension = extensions.getByType(VariantsExtension.class);
43 43 var objectFactory = target.getObjects();
44 44
45 45 var deferred = new Deferred<VariantSourcesContext>();
46 46
47 47 var lateConfigurationPolicy = new DefaultLateConfigurationPolicySpec();
48 48 var namingPolicy = new DefaultCompileUnitNamingPolicy();
49 49
50 50 variantsExtension.whenFinalized(variants -> {
51 51 // create variant views
52 52 var compileUnits = CompileUnitsView.of(variants);
53 53 var roleProjections = RoleProjectionsView.of(variants);
54 54
55 55 // create registries
56 56 var sourceSetRegistry = new SourceSetRegistry(objectFactory);
57 57 lateConfigurationPolicy.finalizePolicy();
58 58 var sourceSetConfiguration = new SourceSetConfigurationRegistry(lateConfigurationPolicy::mode);
59 59
60 60 // build compile unit namer
61 61 var compileUnitNamer = CompileUnitNamer.builder()
62 62 .addUnits(compileUnits.getUnits())
63 63 .nameCollisionPolicy(namingPolicy.policy())
64 64 .build();
65 65
66 66 // create the context
67 67 var context = new DefaultVariantSourcesContext(
68 68 variants,
69 69 compileUnits,
70 70 roleProjections,
71 71 compileUnitNamer,
72 72 sourceSetRegistry,
73 73 sourceSetConfiguration
74 74 );
75 75 deferred.resolve(context);
76 76 });
77 77
78 78 var variantSourcesExtension = new VariantSourcesExtension() {
79 79 @Override
80 80 public void whenAvailable(Action<? super VariantSourcesContext> action) {
81 81 deferred.whenResolved(action::execute);
82 82 }
83 83
84 84 @Override
85 85 public void lateConfigurationPolicy(Action<? super LateConfigurationPolicySpec> action) {
86 86 action.execute(lateConfigurationPolicy);
87 87 }
88 88
89 89 @Override
90 90 public void namingPolicy(Action<? super NamingPolicySpec> action) {
91 91 action.execute(namingPolicy);
92 92 }
93 93
94 94 @Override
95 public void variant(String variantName, Action<? super GenericSourceSet> action) {
95 public void variant(String variantName, Action<? super CompileUnitSourceSetSpec> action) {
96 96 Strings.argumentNotNullOrBlank(variantName, "variantName");
97 97 Objects.requireNonNull(action, "action can't be null");
98 98
99 99 lateConfigurationPolicy.finalizePolicy();
100 100
101 101 whenAvailable(ctx -> ctx.configureVariant(resolveVariant(ctx.getVariants(), variantName), action));
102 102 }
103 103
104 104 @Override
105 public void layer(String layerName, Action<? super GenericSourceSet> action) {
105 public void layer(String layerName, Action<? super CompileUnitSourceSetSpec> action) {
106 106 // protect external DSL
107 107 Strings.argumentNotNullOrBlank(layerName, "layerName");
108 108 Objects.requireNonNull(action, "action can't be null");
109 109
110 110 lateConfigurationPolicy.finalizePolicy();
111 111
112 112 whenAvailable(ctx -> ctx.configureLayer(resolveLayer(ctx.getVariants(), layerName), action));
113 113 }
114 114
115 115 @Override
116 public void unit(String variantName, String layerName, Action<? super GenericSourceSet> action) {
116 public void unit(String variantName, String layerName, Action<? super CompileUnitSourceSetSpec> action) {
117 117 Strings.argumentNotNullOrBlank(layerName, "layerName");
118 118 Strings.argumentNotNullOrBlank(variantName, "variantName");
119 119 Objects.requireNonNull(action, "action can't be null");
120 120
121 121 lateConfigurationPolicy.finalizePolicy();
122 122
123 123 whenAvailable(ctx -> ctx.configureUnit(resolveCompileUnit(ctx, variantName, layerName), action));
124 124 }
125
126 @Override
127 public void configureEach(Action<? super CompileUnitSourceSetSpec> action) {
128 Objects.requireNonNull(action, "action can't be null");
129
130 lateConfigurationPolicy.finalizePolicy();
131
132 whenAvailable(ctx -> ctx.configureEach(action));
133 }
125 134 };
126 135
127 136 extensions.add(VariantSourcesExtension.class, VARIANT_SOURCES_EXTENSION, variantSourcesExtension);
128 137
129 138 }
130 139
131 140 private static Layer resolveLayer(VariantsView variants, String name) {
132 141 return variants.getLayers().stream()
133 142 .filter(named(name))
134 143 .findAny()
135 144 .orElseThrow(() -> new InvalidUserDataException("Layer '" + name + "' isn't declared"));
136 145 }
137 146
138 147 private static Variant resolveVariant(VariantsView variants, String name) {
139 148 return variants.getVariants().stream()
140 149 .filter(named(name))
141 150 .findAny()
142 151 .orElseThrow(() -> new InvalidUserDataException("Variant '" + name + "' isn't declared"));
143 152 }
144 153
145 154 private static CompileUnit resolveCompileUnit(VariantSourcesContext ctx, String variantName, String layerName) {
146 155 return ctx.getCompileUnits().findUnit(
147 156 resolveVariant(ctx.getVariants(), variantName),
148 157 resolveLayer(ctx.getVariants(), layerName))
149 158 .orElseThrow(() -> new InvalidUserDataException(
150 159 "The CompileUnit isn't declared for variant '" + variantName + "', layer '" + layerName + "'"));
151 160 }
152 161
153 162 private static Predicate<Named> named(String name) {
154 163 return named -> named.getName().equals(name);
155 164 }
156 165
157 166 }
@@ -1,206 +1,191
1 1 package org.implab.gradle.variants.sources;
2 2
3 3 import java.io.File;
4 4 import java.nio.file.Paths;
5 5 import java.util.HashSet;
6 6 import java.util.LinkedHashMap;
7 7 import java.util.List;
8 8 import java.util.Map;
9 9 import java.util.Objects;
10 10 import java.util.Set;
11 11 import java.util.concurrent.Callable;
12 12 import java.util.function.Function;
13 13 import java.util.stream.Collectors;
14 14 import java.util.stream.Stream;
15 15
16 16 import javax.inject.Inject;
17 17
18 18 import org.gradle.api.InvalidUserDataException;
19 19 import org.gradle.api.Named;
20 20 import org.gradle.api.NamedDomainObjectContainer;
21 21 import org.gradle.api.Task;
22 22 import org.gradle.api.file.ConfigurableFileCollection;
23 23 import org.gradle.api.file.DirectoryProperty;
24 24 import org.gradle.api.file.FileCollection;
25 25 import org.gradle.api.file.ProjectLayout;
26 26 import org.gradle.api.file.SourceDirectorySet;
27 27 import org.gradle.api.model.ObjectFactory;
28 28 import org.gradle.api.tasks.TaskProvider;
29 import org.gradle.util.Configurable;
30 import org.implab.gradle.common.core.lang.Closures;
31
32 import groovy.lang.Closure;
33 29
34 30 /**
35 31 * A configurable source set abstraction with named outputs.
36 32 *
37 33 * <p>
38 34 * Each instance aggregates multiple {@link SourceDirectorySet source sets}
39 35 * under a shared name and exposes typed outputs that must be declared up front.
40 36 * Default locations are {@code src/<name>} for sources and
41 37 * {@code build/<name>} for outputs, both of which can be customized via the
42 38 * exposed {@link DirectoryProperty} setters.
43 39 * </p>
44 40 *
45 41 * <p>
46 42 * Outputs are grouped by names to make task wiring explicit. An output must be
47 43 * declared with {@link #declareOutputs(String, String...)} before files can be
48 44 * registered against it. Attempting to register or retrieve an undeclared
49 45 * output results in
50 46 * {@link InvalidUserDataException}.
51 47 * </p>
52 48 */
53 public abstract class GenericSourceSet
54 implements Named, Configurable<GenericSourceSet> {
49 public abstract class GenericSourceSet implements Named {
55 50 private final String name;
56 51
57 52 private final NamedDomainObjectContainer<SourceDirectorySet> sourceDirectorySets;
58 53
59 54 private final Map<String, ConfigurableFileCollection> outputs;
60 55
61 56 private final FileCollection allOutputs;
62 57
63 58 private final FileCollection allSourceDirectories;
64 59
65 60 private final ObjectFactory objects;
66 61
67 62 private final Set<String> declaredOutputs = new HashSet<>();
68 63
69 64 @Inject
70 65 public GenericSourceSet(String name, ObjectFactory objects, ProjectLayout layout) {
71 66 this.name = name;
72 67 this.objects = objects;
73 68
74 69 sourceDirectorySets = objects.domainObjectContainer(
75 70 SourceDirectorySet.class,
76 71 this::createSourceDirectorySet);
77 72
78 73 outputs = new LinkedHashMap<>();
79 74
80 75 allSourceDirectories = objects.fileCollection().from(sourceDirectoriesProvider());
81 76
82 77 allOutputs = objects.fileCollection().from(outputsProvider());
83 78
84 79 getSourceSetDir().convention(layout
85 80 .getProjectDirectory()
86 81 .dir(Paths.get("src", name).toString()));
87 82
88 83 getOutputsDir().convention(layout
89 84 .getBuildDirectory()
90 85 .dir(name));
91 86 }
92 87
93 88 @Override
94 89 public String getName() {
95 90 return name;
96 91 }
97 92
98 93 /**
99 94 * Base directory for this source set. Defaults to {@code src/<name>} under
100 95 * the project directory.
101 96 */
102 97 public abstract DirectoryProperty getSourceSetDir();
103 98
104 99 /**
105 100 * Base directory for outputs of this source set. Defaults to
106 101 * {@code build/<name>}.
107 102 */
108 103 public abstract DirectoryProperty getOutputsDir();
109 104
110 105 /**
111 106 * The container of {@link SourceDirectorySet} instances that belong to this
112 107 * logical source set.
113 108 */
114 109 public NamedDomainObjectContainer<SourceDirectorySet> getSets() {
115 110 return sourceDirectorySets;
116 111 }
117 112
118 113 /**
119 114 * All registered outputs grouped across output names.
120 115 */
121 116 public FileCollection getAllOutputs() {
122 117 return allOutputs;
123 118 }
124 119
125 120 /**
126 121 * All source directories from every contained {@link SourceDirectorySet}.
127 122 */
128 123 public FileCollection getAllSourceDirectories() {
129 124 return allSourceDirectories;
130 125 }
131 126
132 127 /**
133 128 * Returns the file collection for the specified output name, creating it
134 129 * if necessary.
135 130 *
136 131 * @throws InvalidUserDataException if the output was not declared
137 132 */
138 133 public FileCollection output(String name) {
139 134 return configurableOutput(name);
140 135 }
141 136
142 137 private ConfigurableFileCollection configurableOutput(String name) {
143 138 requireDeclaredOutput(name);
144 139 return outputs.computeIfAbsent(name, key -> objects.fileCollection());
145 140 }
146 141
147 142 /**
148 143 * Declares allowed output names. Outputs must be declared before registering
149 144 * files under them.
150 145 */
151 146 public void declareOutputs(String name, String... extra) {
152 147 Stream.concat(Stream.of(name), Stream.of(extra))
153 148 .map(Objects::requireNonNull)
154 149 .forEach(declaredOutputs::add);
155 150 }
156 151
157 152 /**
158 153 * Registers files produced elsewhere under the given output.
159 154 */
160 155 public void registerOutput(String name, Object... files) {
161 156 configurableOutput(name).from(files);
162 157 }
163 158
164 159 /**
165 160 * Registers output files produced by a task, using a mapper to extract the
166 161 * output from the task. The task will be added as a build dependency of this
167 162 * output.
168 163 */
169 164 public <T extends Task> void registerOutput(String name, TaskProvider<T> task,
170 165 Function<? super T, ?> mapper) {
171 166 configurableOutput(name).from(task.map(mapper::apply))
172 167 .builtBy(task);
173 168 }
174 169
175 /**
176 * Applies a Groovy closure to this source set, enabling DSL-style
177 * configuration.
178 */
179 @Override
180 public GenericSourceSet configure(Closure configure) {
181 Closures.apply(configure, this);
182 return this;
183 }
184
185 170 private SourceDirectorySet createSourceDirectorySet(String name) {
186 171 return objects.sourceDirectorySet(name, name);
187 172 }
188 173
189 174 private void requireDeclaredOutput(String outputName) {
190 175 if (!declaredOutputs.contains(outputName)) {
191 176 throw new InvalidUserDataException(
192 177 "Output '" + outputName + "' is not declared for source set '" + name + "'");
193 178 }
194 179 }
195 180
196 181 private Callable<List<? extends FileCollection>> outputsProvider() {
197 182 return () -> outputs.values().stream().toList();
198 183 }
199 184
200 185 private Callable<Set<File>> sourceDirectoriesProvider() {
201 186 return () -> sourceDirectorySets.stream()
202 187 .flatMap(x -> x.getSrcDirs().stream())
203 188 .collect(Collectors.toSet());
204 189 }
205 190
206 191 }
@@ -1,81 +1,93
1 1 package org.implab.gradle.variants.sources;
2 2
3 3 import org.gradle.api.Action;
4 4 import org.implab.gradle.variants.core.Layer;
5 5 import org.implab.gradle.variants.core.Variant;
6 6 import org.implab.gradle.variants.core.VariantsView;
7 7
8 8 /**
9 9 * Registry of symbolic source set names produced by sources projection.
10 10 *
11 11 * <p>Identity in this registry is the {@link GenericSourceSet} name assigned
12 12 * by the finalized
13 13 * {@link VariantSourcesExtension#namingPolicy(org.gradle.api.Action)}.
14 14 */
15 15 public interface VariantSourcesContext {
16 16
17 17 /**
18 18 * Finalized core model.
19 19 */
20 20 VariantsView getVariants();
21 21
22 22 /**
23 23 * Derived compile-side view.
24 24 */
25 25 CompileUnitsView getCompileUnits();
26 26
27 27 /**
28 28 * Derived role-side view.
29 29 */
30 30 RoleProjectionsView getRoleProjections();
31 31
32 32 /**
33 33 * Lazy source set provider service.
34 34 */
35 35 SourceSetMaterializer getSourceSets();
36 36
37 37 /**
38 * Configures all GenericSourceSets produced from the given layer.
38 * Configures all source sets produced from the given layer.
39 39 *
40 40 * The action is applied:
41 41 * - to already materialized source sets of this layer
42 42 * - to all future source sets of this layer
43 43 *
44 44 * <p>For future source sets, selector precedence and registration order are
45 45 * preserved by the materializer.
46 46 *
47 47 * <p>For already materialized source sets, behavior is governed by
48 48 * {@link VariantSourcesExtension#lateConfigurationPolicy(org.gradle.api.Action)}.
49 49 * In warn/allow modes the action is applied as a late imperative step and does
50 50 * not retroactively restore selector precedence.
51 51 *
52 52 * @throws org.gradle.api.InvalidUserDataException if the layer is not part of
53 53 * the finalized variant model
54 54 */
55 void configureLayer(Layer layer, Action<? super GenericSourceSet> action);
55 void configureLayer(Layer layer, Action<? super CompileUnitSourceSetSpec> action);
56 56
57 57 /**
58 * Configures all GenericSourceSets produced from the given variant.
58 * Configures all source sets produced from the given variant.
59 59 *
60 60 * <p>Late application semantics for already materialized source sets are
61 61 * governed by
62 62 * {@link VariantSourcesExtension#lateConfigurationPolicy(org.gradle.api.Action)}.
63 63 *
64 64 * @throws org.gradle.api.InvalidUserDataException if the variant is not part
65 65 * of the finalized variant model
66 66 */
67 void configureVariant(Variant variant, Action<? super GenericSourceSet> action);
67 void configureVariant(Variant variant, Action<? super CompileUnitSourceSetSpec> action);
68 68
69 69 /**
70 * Configures the GenericSourceSet produced from the given compile unit.
70 * Configures the source set produced from the given compile unit.
71 71 *
72 72 * <p>Late application semantics for already materialized source sets are
73 73 * governed by
74 74 * {@link VariantSourcesExtension#lateConfigurationPolicy(org.gradle.api.Action)}.
75 75 *
76 76 * @throws org.gradle.api.InvalidUserDataException if the compile unit is not
77 77 * part of the finalized variant model
78 78 */
79 void configureUnit(CompileUnit unit, Action<? super GenericSourceSet> action);
79 void configureUnit(CompileUnit unit, Action<? super CompileUnitSourceSetSpec> action);
80
81 /**
82 * Configures every source set materialized from the finalized compile-unit
83 * model.
84 *
85 * <p>This selector is applied before variant-, layer-, and unit-specific
86 * selectors.
87 *
88 * @throws org.gradle.api.InvalidUserDataException if late configuration is
89 * rejected by the selected policy
90 */
91 void configureEach(Action<? super CompileUnitSourceSetSpec> action);
80 92
81 93 }
@@ -1,170 +1,184
1 1 package org.implab.gradle.variants.sources;
2 2
3 3 import org.eclipse.jdt.annotation.NonNullByDefault;
4 4 import org.gradle.api.Action;
5 5 import org.implab.gradle.common.core.lang.Closures;
6 6 import groovy.lang.Closure;
7 7
8 8 @NonNullByDefault
9 9 public interface VariantSourcesExtension {
10 10
11 11 /**
12 12 * Selects how selector rules behave when they target an already materialized
13 * {@link GenericSourceSet}.
13 * source set.
14 14 *
15 15 * <p>This policy is single-valued:
16 16 * <ul>
17 17 * <li>it must be selected before the first selector rule is registered via
18 18 * {@link #variant(String, Action)}, {@link #layer(String, Action)} or
19 19 * {@link #unit(String, String, Action)};</li>
20 20 * <li>it must be selected before the source context becomes observable via
21 21 * {@link #whenAvailable(Action)};</li>
22 22 * <li>once selected or once source context creation begins, it cannot
23 23 * be changed later;</li>
24 24 * <li>the policy controls both diagnostics and late-application semantics.</li>
25 25 * </ul>
26 26 *
27 27 * <p>If not selected explicitly, the default is
28 28 * {@link LateConfigurationPolicySpec#failOnLateConfiguration()}.
29 29 */
30 30 void lateConfigurationPolicy(Action<? super LateConfigurationPolicySpec> action);
31 31
32 32 default void lateConfigurationPolicy(Closure<?> closure) {
33 33 lateConfigurationPolicy(Closures.action(closure));
34 34 }
35 35
36 36 /**
37 37 * Selects how compile-unit name collisions are handled when the source
38 38 * context is created from the finalized variant model.
39 39 *
40 40 * <p>This policy is single-valued:
41 41 * <ul>
42 42 * <li>it must be selected before the
43 43 * {@link VariantSourcesContext} becomes observable through
44 44 * {@link #whenAvailable(Action)};</li>
45 45 * <li>once the context is being created, the policy is fixed and cannot be
46 46 * changed later;</li>
47 47 * <li>the policy governs validation of compile-unit names produced by the
48 48 * source-set materializer.</li>
49 49 * </ul>
50 50 *
51 51 * <p>If not selected explicitly, the default is
52 52 * {@link NamingPolicySpec#failOnNameCollision()}.
53 53 */
54 54 void namingPolicy(Action<? super NamingPolicySpec> action);
55 55
56 56 default void namingPolicy(Closure<?> closure) {
57 57 namingPolicy(Closures.action(closure));
58 58 }
59 59
60 60 /**
61 61 * Registers a selector rule for all compile units of the given layer.
62 62 *
63 63 * <p>Registering the first selector rule fixes the selected
64 64 * {@link #lateConfigurationPolicy(Action)} for the remaining extension
65 65 * lifecycle.
66 66 */
67 void layer(String layerName, Action<? super GenericSourceSet> action);
67 void layer(String layerName, Action<? super CompileUnitSourceSetSpec> action);
68 68
69 69 default void layer(String layerName, Closure<?> closure) {
70 70 layer(layerName, Closures.action(closure));
71 71 }
72 72
73 73 /**
74 74 * Registers a selector rule for all compile units of the given variant.
75 75 *
76 76 * <p>Registering the first selector rule fixes the selected
77 77 * {@link #lateConfigurationPolicy(Action)} for the remaining extension
78 78 * lifecycle.
79 79 */
80 void variant(String variantName, Action<? super GenericSourceSet> action);
80 void variant(String variantName, Action<? super CompileUnitSourceSetSpec> action);
81 81
82 82 default void variant(String variantName, Closure<?> closure) {
83 83 variant(variantName, Closures.action(closure));
84 84 }
85 85
86 86 /**
87 87 * Registers a selector rule for one exact compile unit.
88 88 *
89 89 * <p>Registering the first selector rule fixes the selected
90 90 * {@link #lateConfigurationPolicy(Action)} for the remaining extension
91 91 * lifecycle.
92 92 */
93 void unit(String variantName, String layerName, Action<? super GenericSourceSet> action);
93 void unit(String variantName, String layerName, Action<? super CompileUnitSourceSetSpec> action);
94 94
95 95 default void unit(String variantName, String layerName, Closure<?> closure) {
96 96 unit(variantName, layerName, Closures.action(closure));
97 97 }
98 98
99 99 /**
100 * Registers a selector rule for every materialized compile-unit source set.
101 *
102 * <p>Registering the first selector rule fixes the selected
103 * {@link #lateConfigurationPolicy(Action)} for the remaining extension
104 * lifecycle. This selector is applied before variant-, layer-, and unit-specific
105 * selectors.
106 */
107 void configureEach(Action<? super CompileUnitSourceSetSpec> action);
108
109 default void configureEach(Closure<?> closure) {
110 configureEach(Closures.action(closure));
111 }
112
113 /**
100 114 * Invoked when the variants-derived source context becomes available.
101 115 *
102 116 * Replayable:
103 117 * <ul>
104 118 * <li>if called before variants finalization, action is queued
105 119 * <li>if called after variants finalization, action is invoked immediately
106 120 * </ul>
107 121 *
108 122 * <p>By the time this callback becomes observable, compile-unit naming
109 123 * policy has already been fixed and symbolic source-set names for finalized
110 124 * compile units are determined.
111 125 */
112 126 void whenAvailable(Action<? super VariantSourcesContext> action);
113 127
114 128 default void whenAvailable(Closure<?> closure) {
115 129 whenAvailable(Closures.action(closure));
116 130 }
117 131
118 132
119 133 /**
120 134 * Imperative selector for the late-configuration mode.
121 135 *
122 136 * <p>Exactly one mode is expected to be chosen for the extension lifecycle.
123 137 */
124 138 interface LateConfigurationPolicySpec {
125 139 /**
126 140 * Rejects selector registration if it targets any already materialized
127 141 * source set.
128 142 */
129 143 void failOnLateConfiguration();
130 144
131 145 /**
132 146 * Allows late selector registration, but emits a warning when it targets an
133 147 * already materialized source set.
134 148 *
135 149 * <p>For such targets, selector precedence is not re-established
136 150 * retroactively. The action is applied as a late imperative step, after the
137 151 * state already produced at the materialization moment.
138 152 */
139 153 void warnOnLateConfiguration();
140 154
141 155 /**
142 156 * Allows late selector registration without a warning when it targets an
143 157 * already materialized source set.
144 158 *
145 159 * <p>For such targets, selector precedence is not re-established
146 160 * retroactively. The action is applied as a late imperative step, after the
147 161 * state already produced at the materialization moment.
148 162 */
149 163 void allowLateConfiguration();
150 164 }
151 165
152 166 interface NamingPolicySpec {
153 167 /**
154 168 * Rejects finalized compile-unit models that project the same source-set
155 169 * name for different compile units.
156 170 */
157 171 void failOnNameCollision();
158 172
159 173 /**
160 174 * Resolves name collisions deterministically for the finalized
161 175 * compile-unit model.
162 176 *
163 177 * <p>Conflicting compile units are ordered canonically by
164 178 * {@code (variant.name, layer.name)}. The first unit keeps the base
165 179 * projected name, and each next unit receives a numeric suffix
166 180 * ({@code 2}, {@code 3}, ...).
167 181 */
168 182 void resolveNameCollision();
169 183 }
170 184 }
@@ -1,107 +1,114
1 1 package org.implab.gradle.variants.sources.internal;
2 2
3 3 import java.util.HashMap;
4 4 import java.util.Map;
5 5 import java.util.Objects;
6 6 import org.gradle.api.Action;
7 7 import org.gradle.api.NamedDomainObjectProvider;
8 8 import org.implab.gradle.variants.core.Layer;
9 9 import org.implab.gradle.variants.core.Variant;
10 10 import org.implab.gradle.variants.core.VariantsView;
11 11 import org.implab.gradle.variants.sources.CompileUnit;
12 import org.implab.gradle.variants.sources.CompileUnitSourceSetSpec;
12 13 import org.implab.gradle.variants.sources.CompileUnitsView;
13 14 import org.implab.gradle.variants.sources.GenericSourceSet;
14 15 import org.implab.gradle.variants.sources.RoleProjectionsView;
15 16 import org.implab.gradle.variants.sources.SourceSetMaterializer;
16 17 import org.implab.gradle.variants.sources.VariantSourcesContext;
17 18
18 19 public class DefaultVariantSourcesContext implements VariantSourcesContext {
19 20 private final VariantsView variantsView;
20 21 private final CompileUnitsView compileUnitsView;
21 22 private final RoleProjectionsView roleProjectionsView;
22 23 private final SourceSetMaterializer sourceSetMaterializer;
23 24 private final SourceSetRegistry sourceSetRegistry;
24 25 private final CompileUnitNamer compileUnitNamer;
25 26 private final SourceSetConfigurationRegistry sourceSetConfigurationRegistry;
26 27
27 28 public DefaultVariantSourcesContext(
28 29 VariantsView variantsView,
29 30 CompileUnitsView compileUnitsView,
30 31 RoleProjectionsView roleProjectionsView,
31 32 CompileUnitNamer compileUnitNamer,
32 33 SourceSetRegistry sourceSetRegistry,
33 34 SourceSetConfigurationRegistry sourceSetConfigurationRegistry) {
34 35 this.variantsView = variantsView;
35 36 this.compileUnitNamer = compileUnitNamer;
36 37 this.compileUnitsView = compileUnitsView;
37 38 this.roleProjectionsView = roleProjectionsView;
38 39 this.sourceSetRegistry = sourceSetRegistry;
39 40 this.sourceSetConfigurationRegistry = sourceSetConfigurationRegistry;
40 41
41 42 sourceSetMaterializer = new LocalSourceSetMaterializer();
42 43 }
43 44
44 45 @Override
45 46 public VariantsView getVariants() {
46 47 return variantsView;
47 48 }
48 49
49 50 @Override
50 51 public CompileUnitsView getCompileUnits() {
51 52 return compileUnitsView;
52 53 }
53 54
54 55 @Override
55 56 public RoleProjectionsView getRoleProjections() {
56 57 return roleProjectionsView;
57 58 }
58 59
59 60 @Override
60 61 public SourceSetMaterializer getSourceSets() {
61 62 return sourceSetMaterializer;
62 63 }
63 64
64 65 @Override
65 public void configureLayer(Layer layer, Action<? super GenericSourceSet> action) {
66 public void configureLayer(Layer layer, Action<? super CompileUnitSourceSetSpec> action) {
66 67 variantsView.assertLayer(layer);
67 68 Objects.requireNonNull(action, "action can't be null");
68 69 sourceSetConfigurationRegistry.addLayerAction(layer, action);
69 70 }
70 71
71 72 @Override
72 public void configureVariant(Variant variant, Action<? super GenericSourceSet> action) {
73 public void configureVariant(Variant variant, Action<? super CompileUnitSourceSetSpec> action) {
73 74 variantsView.assertVariant(variant);
74 75 Objects.requireNonNull(action, "action can't be null");
75 76 sourceSetConfigurationRegistry.addVariantAction(variant, action);
76 77 }
77 78
78 79 @Override
79 public void configureUnit(CompileUnit unit, Action<? super GenericSourceSet> action) {
80 public void configureUnit(CompileUnit unit, Action<? super CompileUnitSourceSetSpec> action) {
80 81 assertCompileUnit(unit);
81 82 Objects.requireNonNull(action, "action can't be null");
82 83 sourceSetConfigurationRegistry.addCompileUnitAction(unit, action);
83 84 }
84 85
86 @Override
87 public void configureEach(Action<? super CompileUnitSourceSetSpec> action) {
88 Objects.requireNonNull(action, "action can't be null");
89 sourceSetConfigurationRegistry.addAction(action);
90 }
91
85 92 private void assertCompileUnit(CompileUnit unit) {
86 93 Objects.requireNonNull(unit, "unit can't be null");
87 94 variantsView.assertVariant(unit.variant());
88 95 variantsView.assertLayer(unit.layer());
89 96 compileUnitsView.requireUnit(unit.variant(), unit.layer());
90 97 }
91 98
92 99 class LocalSourceSetMaterializer implements SourceSetMaterializer {
93 100 private final Map<CompileUnit, NamedDomainObjectProvider<GenericSourceSet>> registeredSources = new HashMap<>();
94 101
95 102 @Override
96 103 public NamedDomainObjectProvider<GenericSourceSet> getSourceSet(CompileUnit unit) {
97 104 assertCompileUnit(unit);
98 105 return registeredSources.computeIfAbsent(unit, k -> {
99 106 var sourcesName = compileUnitNamer.resolveName(unit);
100 107 sourceSetRegistry.whenMaterialized(sourcesName,
101 108 sourceSet -> sourceSetConfigurationRegistry.applyConfiguration(unit, sourceSet));
102 109 return sourceSetRegistry.sourceSets().register(sourcesName);
103 110 });
104 111 }
105 112
106 113 }
107 114 }
@@ -1,95 +1,109
1 1 package org.implab.gradle.variants.sources.internal;
2 2
3 3 import java.text.MessageFormat;
4 4 import java.util.LinkedHashMap;
5 5 import java.util.List;
6 6 import java.util.Map;
7 7 import java.util.function.Supplier;
8 8 import java.util.stream.Collectors;
9 9
10 10 import org.eclipse.jdt.annotation.NonNullByDefault;
11 11 import org.gradle.api.Action;
12 12 import org.gradle.api.InvalidUserDataException;
13 13 import org.gradle.api.Named;
14 14 import org.gradle.api.logging.Logger;
15 15 import org.gradle.api.logging.Logging;
16 16 import org.implab.gradle.common.core.lang.ReplayableQueue;
17 17 import org.implab.gradle.variants.core.Layer;
18 18 import org.implab.gradle.variants.core.Variant;
19 19 import org.implab.gradle.variants.sources.CompileUnit;
20 import org.implab.gradle.variants.sources.CompileUnitSourceSetSpec;
20 21 import org.implab.gradle.variants.sources.GenericSourceSet;
21 22
22 23 @NonNullByDefault
23 24 public class SourceSetConfigurationRegistry {
24 25 private static final Logger logger = Logging.getLogger(SourceSetConfigurationRegistry.class);
25 26
26 private final Map<Layer, ReplayableQueue<GenericSourceSet>> sourcesByLayer = new LinkedHashMap<>();
27 private final Map<Variant, ReplayableQueue<GenericSourceSet>> sourcesByVariant = new LinkedHashMap<>();
28 private final Map<CompileUnit, ReplayableQueue<GenericSourceSet>> sourcesByUnit = new LinkedHashMap<>();
27 private final ReplayableQueue<CompileUnitSourceSetSpec> sources = new ReplayableQueue<>();
28 private final Map<Layer, ReplayableQueue<CompileUnitSourceSetSpec>> sourcesByLayer = new LinkedHashMap<>();
29 private final Map<Variant, ReplayableQueue<CompileUnitSourceSetSpec>> sourcesByVariant = new LinkedHashMap<>();
30 private final Map<CompileUnit, ReplayableQueue<CompileUnitSourceSetSpec>> sourcesByUnit = new LinkedHashMap<>();
29 31
30 32 private final Supplier<LateConfigurationMode> lateConfigurationMode;
31 33
32 34 public SourceSetConfigurationRegistry(Supplier<LateConfigurationMode> lateConfigurationMode) {
33 35 this.lateConfigurationMode = lateConfigurationMode;
34 36 }
35 37
36 public void addLayerAction(Layer layer, Action<? super GenericSourceSet> action) {
38 public void addAction(Action<? super CompileUnitSourceSetSpec> action) {
39 addToActions(
40 sources,
41 action,
42 "Source sets already materialized");
43 }
44
45 public void addLayerAction(Layer layer, Action<? super CompileUnitSourceSetSpec> action) {
37 46 addToActions(
38 47 sourcesByLayer.computeIfAbsent(layer, key -> new ReplayableQueue<>()),
39 48 action,
40 49 MessageFormat.format(
41 50 "Source sets for [layer={0}] layer already materialized",
42 51 layer.getName()));
43 52 }
44 53
45 public void addVariantAction(Variant variant, Action<? super GenericSourceSet> action) {
54 public void addVariantAction(Variant variant, Action<? super CompileUnitSourceSetSpec> action) {
46 55 addToActions(
47 56 sourcesByVariant.computeIfAbsent(variant, key -> new ReplayableQueue<>()),
48 57 action,
49 58 MessageFormat.format(
50 59 "Source sets for [variant={0}] variant already materialized",
51 60 variant.getName()));
52 61
53 62 }
54 63
55 public void addCompileUnitAction(CompileUnit unit, Action<? super GenericSourceSet> action) {
64 public void addCompileUnitAction(CompileUnit unit, Action<? super CompileUnitSourceSetSpec> action) {
56 65 addToActions(
57 66 sourcesByUnit.computeIfAbsent(unit, key -> new ReplayableQueue<>()),
58 67 action,
59 68 MessageFormat.format(
60 69 "Source set for [variant={0}, layer={1}] already materialized",
61 70 unit.variant().getName(),
62 71 unit.layer().getName()));
63 72 }
64 73
65 74 private void addToActions(
66 ReplayableQueue<GenericSourceSet> actions,
67 Action<? super GenericSourceSet> action,
75 ReplayableQueue<CompileUnitSourceSetSpec> actions,
76 Action<? super CompileUnitSourceSetSpec> action,
68 77 String assertMessage) {
69 78 assertLazyConfiguration(actions.values(), assertMessage);
70 79 actions.forEach(action::execute);
71 80 }
72 81
73 void assertLazyConfiguration(List<GenericSourceSet> sets, String message) {
82 void assertLazyConfiguration(List<CompileUnitSourceSetSpec> sets, String message) {
74 83 if (sets.size() == 0)
75 84 return;
76 85
77 var names = sets.stream().map(Named::getName).collect(Collectors.joining(", "));
86 var names = sets.stream()
87 .map(CompileUnitSourceSetSpec::getSourceSet)
88 .map(Named::getName)
89 .collect(Collectors.joining(", "));
78 90
79 91 switch (lateConfigurationMode.get()) {
80 92 case FAIL:
81 93 throw new InvalidUserDataException(message + " [" + names + "]");
82 94 case WARN:
83 95 logger.warn(message + "\n\t" + names);
84 96 break;
85 97 default:
86 98 break;
87 99 }
88 100 }
89 101
90 102 public void applyConfiguration(CompileUnit unit, GenericSourceSet sourceSet) {
91 sourcesByVariant.computeIfAbsent(unit.variant(), key -> new ReplayableQueue<>()).add(sourceSet);
92 sourcesByLayer.computeIfAbsent(unit.layer(), key -> new ReplayableQueue<>()).add(sourceSet);
93 sourcesByUnit.computeIfAbsent(unit, key -> new ReplayableQueue<>()).add(sourceSet);
103 var spec = new DefaultCompileUnitSourceSetSpec(unit, sourceSet);
104 sources.add(spec);
105 sourcesByVariant.computeIfAbsent(unit.variant(), key -> new ReplayableQueue<>()).add(spec);
106 sourcesByLayer.computeIfAbsent(unit.layer(), key -> new ReplayableQueue<>()).add(spec);
107 sourcesByUnit.computeIfAbsent(unit, key -> new ReplayableQueue<>()).add(spec);
94 108 }
95 109 }
@@ -1,691 +1,703
1 1 package org.implab.gradle.variants;
2 2
3 3 import static org.junit.jupiter.api.Assertions.assertTrue;
4 4
5 5 import org.gradle.testkit.runner.BuildResult;
6 6 import org.gradle.testkit.runner.TaskOutcome;
7 7 import org.junit.jupiter.api.Test;
8 8
9 9 class VariantArtifactsPluginFunctionalTest extends AbstractFunctionalTest {
10 10
11 11 @Test
12 12 void gradleReferenceLazyOutgoingConfigurationAllowsSecondaryArtifactSelection() throws Exception {
13 13 writeFile("settings.gradle", """
14 14 rootProject.name = 'gradle-reference-outgoing-resolution'
15 15 include 'producer', 'consumer'
16 16 """);
17 17 writeFile("producer/inputs/typesPackage", "types\n");
18 18 writeFile("producer/inputs/js", "js\n");
19 19 writeBuildFile("""
20 20 import org.gradle.api.attributes.Attribute
21 21
22 22 def variantAttr = Attribute.of('test.variant', String)
23 23 def slotAttr = Attribute.of('test.slot', String)
24 24
25 25 project(':producer') {
26 26 def browserElements = configurations.consumable('browserElements')
27 27
28 28 println('reference: registered browserElements provider')
29 29
30 30 browserElements.configure { configuration ->
31 31 println('reference: configuring browserElements')
32 32
33 33 configuration.attributes.attribute(variantAttr, 'browser')
34 34 configuration.outgoing.attributes.attribute(slotAttr, 'typesPackage')
35 35 configuration.outgoing.artifact(layout.projectDirectory.file('inputs/typesPackage'))
36 36
37 37 configuration.outgoing.variants.create('js') { secondary ->
38 38 println('reference: creating js outgoing variant')
39 39
40 40 secondary.attributes.attribute(slotAttr, 'js')
41 41 secondary.artifact(layout.projectDirectory.file('inputs/js'))
42 42 }
43 43 }
44 44 }
45 45
46 46 project(':consumer') {
47 47 configurations {
48 48 compileView {
49 49 canBeResolved = true
50 50 canBeConsumed = false
51 51 canBeDeclared = true
52 52 attributes {
53 53 attribute(variantAttr, 'browser')
54 54 attribute(slotAttr, 'typesPackage')
55 55 }
56 56 }
57 57 }
58 58
59 59 dependencies {
60 60 compileView project(':producer')
61 61 }
62 62
63 63 tasks.register('probe') {
64 64 doLast {
65 65 println('reference: resolving primary files')
66 66
67 67 def compileFiles = configurations.compileView.files.collect { it.name }.sort().join(',')
68 68
69 69 println('reference: resolving secondary files')
70 70
71 71 def jsFiles = configurations.compileView.incoming.artifactView {
72 72 attributes {
73 73 attribute(slotAttr, 'js')
74 74 }
75 75 }.files.files.collect { it.name }.sort().join(',')
76 76
77 77 println('compileFiles=' + compileFiles)
78 78 println('jsFiles=' + jsFiles)
79 79 }
80 80 }
81 81 }
82 82 """);
83 83
84 84 BuildResult result = runner(":consumer:probe").build();
85 85 var output = result.getOutput();
86 86 var registered = output.indexOf("reference: registered browserElements provider");
87 87 var resolvingPrimary = output.indexOf("reference: resolving primary files");
88 88 var configuring = output.indexOf("reference: configuring browserElements");
89 89 var creatingSecondary = output.indexOf("reference: creating js outgoing variant");
90 90 var resolvingSecondary = output.indexOf("reference: resolving secondary files");
91 91
92 92 assertTrue(registered >= 0);
93 93 assertTrue(resolvingPrimary >= 0);
94 94 assertTrue(configuring >= 0);
95 95 assertTrue(creatingSecondary >= 0);
96 96 assertTrue(resolvingSecondary >= 0);
97 97 assertTrue(registered < resolvingPrimary);
98 98 assertTrue(resolvingPrimary < configuring);
99 99 assertTrue(configuring < creatingSecondary);
100 100 assertTrue(creatingSecondary < resolvingSecondary);
101 101 assertTrue(output.contains("compileFiles=typesPackage"));
102 102 assertTrue(output.contains("jsFiles=js"));
103 103 }
104 104
105 105 @Test
106 106 void gradleReferenceRegisteredSecondaryArtifactVariantIsNotRealizedBeforeResolution() throws Exception {
107 107 // Gradle issue: https://github.com/gradle/gradle/issues/27441
108 108 // Registered outgoing artifact variants are not realized before dependency resolution.
109 109 writeFile("settings.gradle", """
110 110 rootProject.name = 'gradle-reference-registered-secondary-variant'
111 111 include 'producer', 'consumer'
112 112 """);
113 113 writeFile("producer/inputs/typesPackage", "types\n");
114 114 writeFile("producer/inputs/js", "js\n");
115 115 writeFile("build.gradle", """
116 116 import org.gradle.api.attributes.Attribute
117 117
118 118 def variantAttr = Attribute.of('test.variant', String)
119 119 def slotAttr = Attribute.of('test.slot', String)
120 120
121 121 project(':producer') {
122 122 def browserElements = configurations.consumable('browserElements')
123 123
124 124 browserElements.configure { configuration ->
125 125 configuration.attributes.attribute(variantAttr, 'browser')
126 126 configuration.outgoing.attributes.attribute(slotAttr, 'typesPackage')
127 127 configuration.outgoing.artifact(layout.projectDirectory.file('inputs/typesPackage'))
128 128
129 129 configuration.outgoing.variants.register('js') { secondary ->
130 130 secondary.attributes.attribute(slotAttr, 'js')
131 131 secondary.artifact(layout.projectDirectory.file('inputs/js'))
132 132 }
133 133 }
134 134 }
135 135
136 136 project(':consumer') {
137 137 configurations {
138 138 compileView {
139 139 canBeResolved = true
140 140 canBeConsumed = false
141 141 canBeDeclared = true
142 142 attributes {
143 143 attribute(variantAttr, 'browser')
144 144 attribute(slotAttr, 'typesPackage')
145 145 }
146 146 }
147 147 }
148 148
149 149 dependencies {
150 150 compileView project(':producer')
151 151 }
152 152
153 153 tasks.register('probe') {
154 154 doLast {
155 155 def compileFiles = configurations.compileView.files.collect { it.name }.sort().join(',')
156 156 def jsFiles = configurations.compileView.incoming.artifactView {
157 157 attributes {
158 158 attribute(slotAttr, 'js')
159 159 }
160 160 }.files.files.collect { it.name }.sort().join(',')
161 161
162 162 println('compileFiles=' + compileFiles)
163 163 println('jsFiles=' + jsFiles)
164 164 }
165 165 }
166 166 }
167 167 """);
168 168
169 169 assertBuildFails("Cannot create variant 'js' after dependency configuration ':producer:browserElements' has been resolved",
170 170 ":consumer:probe");
171 171 }
172 172
173 173 @Test
174 174 void materializesPrimaryAndSecondarySlotsAndInvokesOutgoingHooks() throws Exception {
175 175 writeSettings("variant-artifacts-slots");
176 176 writeFile("inputs/base.js", "console.log('base')\n");
177 177 writeFile("inputs/amd.js", "console.log('amd')\n");
178 178 writeFile("inputs/mainJs.txt", "mainJs marker\n");
179 179 writeFile("inputs/amdJs.txt", "amdJs marker\n");
180 180 writeBuildFile("""
181 181 import org.gradle.api.attributes.Attribute
182 182
183 183 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
184 184
185 185 def variantAttr = Attribute.of('test.variant', String)
186 186 def slotAttr = Attribute.of('test.slot', String)
187 187
188 188 variants.layers.create('mainBase')
189 189 variants.layers.create('mainAmd')
190 190 variants.roles.create('main')
191 191 variants.roles.create('test')
192 192 variants.variant('browser') {
193 193 role('main') {
194 194 layers('mainBase', 'mainAmd')
195 195 }
196 196 }
197 197
198 198 variantSources {
199 199 layer('mainBase') {
200 declareOutputs('js')
201 registerOutput('js', layout.projectDirectory.file('inputs/base.js'))
200 sourceSet {
201 declareOutputs('js')
202 registerOutput('js', layout.projectDirectory.file('inputs/base.js'))
203 }
202 204 }
203 205 layer('mainAmd') {
204 declareOutputs('js')
205 registerOutput('js', layout.projectDirectory.file('inputs/amd.js'))
206 sourceSet {
207 declareOutputs('js')
208 registerOutput('js', layout.projectDirectory.file('inputs/amd.js'))
209 }
206 210 }
207 211 }
208 212
209 213 variantArtifacts {
210 214 variant('browser') {
211 215 primarySlot('mainJs') {
212 216 fromRole('main') {
213 217 output('js')
214 218 }
215 219 from(layout.projectDirectory.file('inputs/mainJs.txt'))
216 220 }
217 221 slot('amdJs') {
218 222 fromLayer('mainAmd') {
219 223 output('js')
220 224 }
221 225 from(layout.projectDirectory.file('inputs/amdJs.txt'))
222 226 }
223 227 }
224 228
225 229 whenOutgoingConfiguration { publication ->
226 230 publication.configuration {
227 231 attributes.attribute(variantAttr, publication.variant.name)
228 232 }
229 233 }
230 234
231 235 whenOutgoingSlot { publication ->
232 236 def slotName = publication.artifactSlot.slot.name
233 237 publication.artifactAttributes {
234 238 attribute(slotAttr, slotName)
235 239 }
236 240 }
237 241 }
238 242
239 243 tasks.register('probe') {
240 244 dependsOn 'assembleVariantArtifactSlot_v7_browser_s6_mainJs'
241 245 dependsOn 'assembleVariantArtifactSlot_v7_browser_s5_amdJs'
242 246
243 247 doLast {
244 248 def mainDir = layout.buildDirectory.dir('variant-assemblies/browser/mainJs').get().asFile
245 249 def amdDir = layout.buildDirectory.dir('variant-assemblies/browser/amdJs').get().asFile
246 250
247 251 assert new File(mainDir, 'base.js').exists()
248 252 assert new File(mainDir, 'amd.js').exists()
249 253 assert new File(mainDir, 'mainJs.txt').exists()
250 254
251 255 assert !new File(amdDir, 'base.js').exists()
252 256 assert new File(amdDir, 'amd.js').exists()
253 257 assert new File(amdDir, 'amdJs.txt').exists()
254 258
255 259 def elements = configurations.getByName('browserElements')
256 260 def amdVariant = elements.outgoing.variants.getByName('amdJs')
257 261
258 262 println('variantAttr=' + elements.attributes.getAttribute(variantAttr))
259 263 println('primarySlotAttr=' + elements.outgoing.attributes.getAttribute(slotAttr))
260 264 println('amdSlotAttr=' + amdVariant.attributes.getAttribute(slotAttr))
261 265 println('configurations=' + configurations.matching { it.name == 'browserElements' }.collect { it.name }.join(','))
262 266 println('secondaryVariants=' + elements.outgoing.variants.collect { it.name }.sort().join(','))
263 267 }
264 268 }
265 269 """);
266 270
267 271 BuildResult result = runner("probe").build();
268 272
269 273 assertTrue(result.getOutput().contains("variantAttr=browser"));
270 274 assertTrue(result.getOutput().contains("primarySlotAttr=mainJs"));
271 275 assertTrue(result.getOutput().contains("amdSlotAttr=amdJs"));
272 276 assertTrue(result.getOutput().contains("configurations=browserElements"));
273 277 assertTrue(result.getOutput().contains("secondaryVariants=amdJs"));
274 278 assertTrue(result.task(":probe").getOutcome() == TaskOutcome.SUCCESS);
275 279 }
276 280
277 281 @Test
278 282 void outgoingSlotHookFollowsMaterializedGradleArtifactVariants() throws Exception {
279 283 writeSettings("variant-artifacts-materialized-gradle-variant");
280 284 writeFile("inputs/typesPackage", "types\n");
281 285 writeFile("inputs/js", "js\n");
282 286 writeBuildFile("""
283 287 import org.gradle.api.attributes.Attribute
284 288
285 289 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
286 290
287 291 def slotAttr = Attribute.of('test.slot', String)
288 292
289 293 variants.layers.create('main')
290 294 variants.roles.create('main')
291 295 variants.variant('browser') {
292 296 role('main') {
293 297 layers('main')
294 298 }
295 299 }
296 300
297 301 variantArtifacts {
298 302 variant('browser') {
299 303 primarySlot('typesPackage') {
300 304 from(layout.projectDirectory.file('inputs/typesPackage'))
301 305 }
302 306 }
303 307
304 308 whenOutgoingConfiguration { publication ->
305 309 publication.configuration {
306 310 outgoing.variants.create('js') { secondary ->
307 311 secondary.artifact(layout.projectDirectory.file('inputs/js'))
308 312 }
309 313 }
310 314 }
311 315
312 316 whenOutgoingSlot { publication ->
313 317 publication.artifactAttributes {
314 318 attribute(slotAttr, publication.artifactSlot.slot.name)
315 319 }
316 320 }
317 321 }
318 322
319 323 tasks.register('probe') {
320 324 doLast {
321 325 def elements = configurations.getByName('browserElements')
322 326 def jsVariant = elements.outgoing.variants.getByName('js')
323 327
324 328 println('primarySlotAttr=' + elements.outgoing.attributes.getAttribute(slotAttr))
325 329 println('jsSlotAttr=' + jsVariant.attributes.getAttribute(slotAttr))
326 330 }
327 331 }
328 332 """);
329 333
330 334 BuildResult result = runner("probe").build();
331 335
332 336 assertTrue(result.getOutput().contains("primarySlotAttr=typesPackage"));
333 337 assertTrue(result.getOutput().contains("jsSlotAttr=js"));
334 338 assertTrue(result.task(":probe").getOutcome() == TaskOutcome.SUCCESS);
335 339 }
336 340
337 341 @Test
338 342 void allowsSingleSlotVariantWithoutExplicitPrimarySlot() throws Exception {
339 343 writeSettings("variant-artifacts-single-slot");
340 344 writeBuildFile("""
341 345 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
342 346
343 347 variants.layers.create('main')
344 348 variants.roles.create('main')
345 349 variants.variant('browser') {
346 350 role('main') {
347 351 layers('main')
348 352 }
349 353 }
350 354
351 355 variantSources.layer('main') {
352 declareOutputs('types')
356 sourceSet {
357 declareOutputs('types')
358 }
353 359 }
354 360
355 361 variantArtifacts {
356 362 variant('browser') {
357 363 slot('typesPackage') {
358 364 fromVariant {
359 365 output('types')
360 366 }
361 367 }
362 368 }
363 369 }
364 370
365 371 tasks.register('probe') {
366 372 doLast {
367 373 variantArtifacts.whenAvailable { ctx ->
368 374 def browser = objects.named(org.implab.gradle.variants.core.Variant, 'browser')
369 375 println('primary=' + ctx.findOutgoing(browser).get().primarySlot.get().name)
370 376 }
371 377 }
372 378 }
373 379 """);
374 380
375 381 BuildResult result = runner("probe").build();
376 382
377 383 assertTrue(result.getOutput().contains("primary=typesPackage"));
378 384 }
379 385
380 386 @Test
381 387 void materializesDirectSlotInputsWithoutVariantSourceBindings() throws Exception {
382 388 writeSettings("variant-artifacts-direct-input");
383 389 writeFile("inputs/bundle.js", "console.log('bundle')\n");
384 390 writeBuildFile("""
385 391 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
386 392
387 393 variants.layers.create('main')
388 394 variants.roles.create('main')
389 395 variants.variant('browser') {
390 396 role('main') {
391 397 layers('main')
392 398 }
393 399 }
394 400
395 401 variantArtifacts {
396 402 variant('browser') {
397 403 primarySlot('bundle') {
398 404 from(layout.projectDirectory.file('inputs/bundle.js'))
399 405 }
400 406 }
401 407 }
402 408
403 409 tasks.register('probe') {
404 410 dependsOn 'assembleVariantArtifactSlot_v7_browser_s6_bundle'
405 411
406 412 doLast {
407 413 def bundleDir = layout.buildDirectory.dir('variant-assemblies/browser/bundle').get().asFile
408 414 assert new File(bundleDir, 'bundle.js').exists()
409 415 }
410 416 }
411 417 """);
412 418
413 419 BuildResult result = runner("probe").build();
414 420
415 421 assertTrue(result.task(":probe").getOutcome() == TaskOutcome.SUCCESS);
416 422 }
417 423
418 424 @Test
419 425 void combinesDirectAndTopologyAwareSlotInputs() throws Exception {
420 426 writeSettings("variant-artifacts-combined-inputs");
421 427 writeFile("inputs/base.js", "console.log('base')\n");
422 428 writeFile("inputs/marker.txt", "marker\n");
423 429 writeBuildFile("""
424 430 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
425 431
426 432 variants.layers.create('main')
427 433 variants.roles.create('main')
428 434 variants.variant('browser') {
429 435 role('main') {
430 436 layers('main')
431 437 }
432 438 }
433 439
434 440 variantSources.layer('main') {
435 declareOutputs('js')
436 registerOutput('js', layout.projectDirectory.file('inputs/base.js'))
441 sourceSet {
442 declareOutputs('js')
443 registerOutput('js', layout.projectDirectory.file('inputs/base.js'))
444 }
437 445 }
438 446
439 447 variantArtifacts {
440 448 variant('browser') {
441 449 primarySlot('bundle') {
442 450 fromVariant {
443 451 output('js')
444 452 }
445 453 from(layout.projectDirectory.file('inputs/marker.txt'))
446 454 }
447 455 }
448 456 }
449 457
450 458 tasks.register('probe') {
451 459 dependsOn 'assembleVariantArtifactSlot_v7_browser_s6_bundle'
452 460
453 461 doLast {
454 462 def bundleDir = layout.buildDirectory.dir('variant-assemblies/browser/bundle').get().asFile
455 463 assert new File(bundleDir, 'base.js').exists()
456 464 assert new File(bundleDir, 'marker.txt').exists()
457 465 }
458 466 }
459 467 """);
460 468
461 469 BuildResult result = runner("probe").build();
462 470
463 471 assertTrue(result.task(":probe").getOutcome() == TaskOutcome.SUCCESS);
464 472 }
465 473
466 474 @Test
467 475 void failsOnUnknownVariantReference() throws Exception {
468 476 writeSettings("variant-artifacts-missing-variant");
469 477 writeBuildFile("""
470 478 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
471 479
472 480 variants.layers.create('main')
473 481
474 482 variantArtifacts {
475 483 variant('browser') {
476 484 slot('mainJs') {
477 485 fromVariant {
478 486 output('js')
479 487 }
480 488 }
481 489 }
482 490 }
483 491 """);
484 492
485 493 assertBuildFails("isn't declared", "help");
486 494 }
487 495
488 496 @Test
489 497 void failsOnUnknownRoleReference() throws Exception {
490 498 writeSettings("variant-artifacts-missing-role");
491 499 writeBuildFile("""
492 500 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
493 501
494 502 variants.layers.create('main')
495 503 variants.roles.create('main')
496 504 variants.variant('browser') {
497 505 role('main') {
498 506 layers('main')
499 507 }
500 508 }
501 509
502 510 variantArtifacts {
503 511 variant('browser') {
504 512 slot('mainJs') {
505 513 fromRole('test') {
506 514 output('js')
507 515 }
508 516 }
509 517 }
510 518 }
511 519 """);
512 520
513 521 assertBuildFails("Role projection for variant 'browser' and role 'test' not found", "help");
514 522 }
515 523
516 524 @Test
517 525 void failsWhenPrimarySlotIsMissingForMultipleSlots() throws Exception {
518 526 writeSettings("variant-artifacts-missing-primary");
519 527 writeBuildFile("""
520 528 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
521 529
522 530 variants.layers.create('main')
523 531 variants.roles.create('main')
524 532 variants.variant('browser') {
525 533 role('main') {
526 534 layers('main')
527 535 }
528 536 }
529 537
530 538 variantSources.layer('main') {
531 declareOutputs('types', 'js')
539 sourceSet {
540 declareOutputs('types', 'js')
541 }
532 542 }
533 543
534 544 variantArtifacts {
535 545 variant('browser') {
536 546 slot('typesPackage') {
537 547 fromVariant {
538 548 output('types')
539 549 }
540 550 }
541 551 slot('js') {
542 552 fromVariant {
543 553 output('js')
544 554 }
545 555 }
546 556 }
547 557 }
548 558
549 559 tasks.register('probe') {
550 560 doLast {
551 561 variantArtifacts.whenAvailable { ctx ->
552 562 def browser = objects.named(org.implab.gradle.variants.core.Variant, 'browser')
553 563 ctx.findOutgoing(browser).get().primarySlot.get()
554 564 }
555 565 }
556 566 }
557 567 """);
558 568
559 569 assertBuildFails("Multiple slots declared for browser, please specify primary slot explicitly", "probe");
560 570 }
561 571
562 572 @Test
563 573 void failsOnLayerReferenceOutsideVariantTopology() throws Exception {
564 574 writeSettings("variant-artifacts-layer-outside-topology");
565 575 writeBuildFile("""
566 576 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
567 577
568 578 variants.layers.create('mainBase')
569 579 variants.layers.create('extra')
570 580 variants.roles.create('main')
571 581 variants.variant('browser') {
572 582 role('main') {
573 583 layers('mainBase')
574 584 }
575 585 }
576 586
577 587 variantArtifacts {
578 588 variant('browser') {
579 589 slot('extraJs') {
580 590 fromLayer('extra') {
581 591 output('js')
582 592 }
583 593 }
584 594 }
585 595 }
586 596 """);
587 597
588 598 assertBuildFails("Compile unit for variant 'browser' and layer 'extra' not found", "help");
589 599 }
590 600
591 601 @Test
592 602 void preservesPrimaryResolutionAndAllowsSecondaryArtifactSelection() throws Exception {
593 603 writeFile("settings.gradle", """
594 604 rootProject.name = 'variant-artifacts-resolution'
595 605 include 'producer', 'consumer'
596 606 """);
597 607 writeFile("producer/inputs/types.d.ts", "export type Foo = string\n");
598 608 writeFile("producer/inputs/index.js", "export const foo = 'bar'\n");
599 609 writeBuildFile("""
600 610 import org.gradle.api.attributes.Attribute
601 611
602 612 def variantAttr = Attribute.of('test.variant', String)
603 613 def slotAttr = Attribute.of('test.slot', String)
604 614
605 615 subprojects {
606 616 apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin
607 617 }
608 618
609 619 project(':producer') {
610 620 variants.layers.create('main')
611 621 variants.roles.create('main')
612 622 variants.variant('browser') {
613 623 role('main') {
614 624 layers('main')
615 625 }
616 626 }
617 627
618 628 variantSources.layer('main') {
619 declareOutputs('types', 'js')
620 registerOutput('types', layout.projectDirectory.file('inputs/types.d.ts'))
621 registerOutput('js', layout.projectDirectory.file('inputs/index.js'))
629 sourceSet {
630 declareOutputs('types', 'js')
631 registerOutput('types', layout.projectDirectory.file('inputs/types.d.ts'))
632 registerOutput('js', layout.projectDirectory.file('inputs/index.js'))
633 }
622 634 }
623 635
624 636 variantArtifacts {
625 637 variant('browser') {
626 638 primarySlot('typesPackage') {
627 639 fromVariant {
628 640 output('types')
629 641 }
630 642 }
631 643 slot('js') {
632 644 fromVariant {
633 645 output('js')
634 646 }
635 647 }
636 648 }
637 649
638 650 whenOutgoingConfiguration { publication ->
639 651 publication.configuration {
640 652 attributes.attribute(variantAttr, publication.variant.name)
641 653 }
642 654 }
643 655
644 656 whenOutgoingSlot { publication ->
645 657 publication.artifactAttributes {
646 658 attribute(slotAttr, publication.artifactSlot.slot.name)
647 659 }
648 660 }
649 661 }
650 662
651 663 }
652 664
653 665 project(':consumer') {
654 666 configurations {
655 667 compileView {
656 668 canBeResolved = true
657 669 canBeConsumed = false
658 670 canBeDeclared = true
659 671 attributes {
660 672 attribute(variantAttr, 'browser')
661 673 attribute(slotAttr, 'typesPackage')
662 674 }
663 675 }
664 676 }
665 677
666 678 dependencies {
667 679 compileView project(':producer')
668 680 }
669 681
670 682 tasks.register('probe') {
671 683 doLast {
672 684 def compileFiles = configurations.compileView.files.collect { it.name }.sort().join(',')
673 685 def jsFiles = configurations.compileView.incoming.artifactView {
674 686 attributes {
675 687 attribute(slotAttr, 'js')
676 688 }
677 689 }.files.files.collect { it.name }.sort().join(',')
678 690
679 691 println('compileFiles=' + compileFiles)
680 692 println('jsFiles=' + jsFiles)
681 693 }
682 694 }
683 695 }
684 696 """);
685 697
686 698 BuildResult result = runner(":consumer:probe").build();
687 699
688 700 assertTrue(result.getOutput().contains("compileFiles=typesPackage"));
689 701 assertTrue(result.getOutput().contains("jsFiles=js"));
690 702 }
691 703 }
@@ -1,554 +1,580
1 1 package org.implab.gradle.variants;
2 2
3 3 import static org.junit.jupiter.api.Assertions.assertTrue;
4 4
5 5 import org.gradle.testkit.runner.BuildResult;
6 6 import org.junit.jupiter.api.Test;
7 7
8 8 class VariantSourcesPluginFunctionalTest extends AbstractFunctionalTest {
9 9
10 10 @Test
11 11 void exposesDerivedViewsAndStableSourceSetProvider() throws Exception {
12 12 writeSettings("variant-sources-derived-views");
13 13 writeBuildFile("""
14 14 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
15 15
16 16 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
17 17 variantsExt.layers.create('main')
18 18 variantsExt.layers.create('test')
19 19 variantsExt.roles.create('production')
20 20 variantsExt.roles.create('test')
21 21
22 22 variantsExt.variant('browser') {
23 23 role('production') { layers('main') }
24 24 role('test') { layers('main', 'test') }
25 25 }
26 26
27 27 def lines = []
28 28
29 29 variantSources.whenAvailable { ctx ->
30 30 lines << "units=" + ctx.compileUnits.units
31 31 .collect { "${it.variant().name}:${it.layer().name}" }
32 32 .sort()
33 33 .join(',')
34 34
35 35 def browser = ctx.variants.variants.find { it.name == 'browser' }
36 36 def production = ctx.variants.roles.find { it.name == 'production' }
37 37 def mainLayer = ctx.variants.layers.find { it.name == 'main' }
38 38 def projection = ctx.roleProjections.requireProjection(browser, production)
39 39 def unit = ctx.compileUnits.requireUnit(browser, mainLayer)
40 40
41 41 def left = ctx.sourceSets.getSourceSet(unit)
42 42 def right = ctx.sourceSets.getSourceSet(unit)
43 43
44 44 lines << "projectionUnits=" + ctx.roleProjections.getUnits(projection)
45 45 .collect { it.layer().name }
46 46 .sort()
47 47 .join(',')
48 48 lines << "mainSourceSet=" + left.name
49 49 lines << "sameProvider=" + left.is(right)
50 50 }
51 51
52 52 afterEvaluate {
53 53 variantSources.whenAvailable { ctx ->
54 54 lines << "late:variants=" + ctx.variants.variants.collect { it.name }.sort().join(',')
55 55 }
56 56 }
57 57
58 58 tasks.register('probe') {
59 59 doLast {
60 60 lines.each { println(it) }
61 61 }
62 62 }
63 63 """);
64 64
65 65 BuildResult result = runner("probe").build();
66 66
67 67 assertTrue(result.getOutput().contains("units=browser:main,browser:test"));
68 68 assertTrue(result.getOutput().contains("projectionUnits=main"));
69 69 assertTrue(result.getOutput().contains("mainSourceSet=browserMain"));
70 70 assertTrue(result.getOutput().contains("sameProvider=true"));
71 71 assertTrue(result.getOutput().contains("late:variants=browser"));
72 72 }
73 73
74 74 @Test
75 75 void appliesSelectorPrecedenceForFutureMaterialization() throws Exception {
76 76 writeSettings("variant-sources-precedence");
77 77 writeBuildFile("""
78 78 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
79 79
80 80 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
81 81 variantsExt.layers.create('main')
82 82 variantsExt.layers.create('test')
83 83 variantsExt.roles.create('production')
84 84 variantsExt.roles.create('test')
85 85
86 86 variantsExt.variant('browser') {
87 87 role('production') { layers('main') }
88 88 role('test') { layers('main', 'test') }
89 89 }
90 90
91 91 variantsExt.variant('node') {
92 92 role('production') { layers('main') }
93 93 }
94 94
95 95 def events = []
96 96
97 97 variantSources {
98 configureEach {
99 events << "all:" + sourceSet.name
100 events << "context:" + sourceSet.name + ":" + variant.name + ":" + layer.name
101 }
98 102 variant('browser') {
99 events << "variant:" + name
103 events << "variant:" + sourceSet.name
100 104 }
101 105 layer('main') {
102 events << "layer:" + name
106 events << "layer:" + sourceSet.name
103 107 }
104 108 unit('browser', 'main') {
105 events << "unit:" + name
109 events << "unit:" + sourceSet.name
106 110 }
107 111 }
108 112
109 113 afterEvaluate {
110 114 variantSources.whenAvailable { ctx ->
111 115 def browser = ctx.variants.variants.find { it.name == 'browser' }
112 116 def node = ctx.variants.variants.find { it.name == 'node' }
113 117 def mainLayer = ctx.variants.layers.find { it.name == 'main' }
114 118 def testLayer = ctx.variants.layers.find { it.name == 'test' }
115 119
116 120 def browserMain = ctx.sourceSets.getSourceSet(ctx.compileUnits.requireUnit(browser, mainLayer)).get()
117 121 def browserTest = ctx.sourceSets.getSourceSet(ctx.compileUnits.requireUnit(browser, testLayer)).get()
118 122 def nodeMain = ctx.sourceSets.getSourceSet(ctx.compileUnits.requireUnit(node, mainLayer)).get()
119 123 def bySourceSet = events.groupBy { it.split(':', 2)[1] }
120 124
121 125 println("browserMain=" + bySourceSet[browserMain.name].collect { it.split(':', 2)[0] }.join(','))
122 126 println("browserTest=" + bySourceSet[browserTest.name].collect { it.split(':', 2)[0] }.join(','))
123 127 println("nodeMain=" + bySourceSet[nodeMain.name].collect { it.split(':', 2)[0] }.join(','))
128 println("context=" + events.find { it.startsWith("context:" + browserMain.name + ":") })
124 129 }
125 130 }
126 131 """);
127 132
128 133 BuildResult result = runner("help").build();
129 134
130 assertTrue(result.getOutput().contains("browserMain=variant,layer,unit"));
131 assertTrue(result.getOutput().contains("browserTest=variant"));
132 assertTrue(result.getOutput().contains("nodeMain=layer"));
135 assertTrue(result.getOutput().contains("browserMain=all,variant,layer,unit"));
136 assertTrue(result.getOutput().contains("browserTest=all,variant"));
137 assertTrue(result.getOutput().contains("nodeMain=all,layer"));
138 assertTrue(result.getOutput().contains("context=context:browserMain:browser:main"));
133 139 }
134 140
135 141 @Test
136 142 void failsLateConfigurationByDefaultAfterMaterialization() throws Exception {
137 143 writeSettings("variant-sources-late-fail");
138 144 writeBuildFile("""
139 145 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
140 146
141 147 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
142 148 variantsExt.layers.create('main')
143 149 variantsExt.roles.create('production')
144 150 variantsExt.variant('browser') {
145 151 role('production') { layers('main') }
146 152 }
147 153
148 154 afterEvaluate {
149 155 variantSources.whenAvailable { ctx ->
150 156 def browser = ctx.variants.variants.find { it.name == 'browser' }
151 157 def mainLayer = ctx.variants.layers.find { it.name == 'main' }
152 158 def unit = ctx.compileUnits.requireUnit(browser, mainLayer)
153 159
154 160 ctx.sourceSets.getSourceSet(unit).get()
155 161 variantSources.layer('main') {
156 declareOutputs('late')
162 sourceSet {
163 declareOutputs('late')
164 }
157 165 }
158 166 }
159 167 }
160 168 """);
161 169
162 170 assertBuildFails("Source sets for [layer=main] layer already materialized", "help");
163 171 }
164 172
165 173 @Test
166 174 void allowsLateConfigurationWhenSelectedBeforeFirstSelector() throws Exception {
167 175 writeSettings("variant-sources-late-allow");
168 176 writeBuildFile("""
169 177 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
170 178
171 179 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
172 180 variantsExt.layers.create('main')
173 181 variantsExt.roles.create('production')
174 182 variantsExt.variant('browser') {
175 183 role('production') { layers('main') }
176 184 }
177 185
178 186 variantSources {
179 187 lateConfigurationPolicy {
180 188 allowLateConfiguration()
181 189 }
182 190 }
183 191
184 192 afterEvaluate {
185 193 variantSources.whenAvailable { ctx ->
186 194 def browser = ctx.variants.variants.find { it.name == 'browser' }
187 195 def mainLayer = ctx.variants.layers.find { it.name == 'main' }
188 196 def unit = ctx.compileUnits.requireUnit(browser, mainLayer)
189 197
190 def sourceSet = ctx.sourceSets.getSourceSet(unit).get()
198 def unitSourceSet = ctx.sourceSets.getSourceSet(unit).get()
191 199 variantSources.layer('main') {
192 declareOutputs('late')
200 sourceSet {
201 declareOutputs('late')
202 }
193 203 }
194 sourceSet.output('late')
204 unitSourceSet.output('late')
195 205 println('lateAllowed=ok')
196 206 }
197 207 }
198 208 """);
199 209
200 210 BuildResult result = runner("help").build();
201 211 assertTrue(result.getOutput().contains("lateAllowed=ok"));
202 212 }
203 213
204 214 @Test
205 215 void warnsAndAppliesLateConfigurationWhenWarnModeSelected() throws Exception {
206 216 writeSettings("variant-sources-late-warn");
207 217 writeBuildFile("""
208 218 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
209 219
210 220 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
211 221 variantsExt.layers.create('main')
212 222 variantsExt.roles.create('production')
213 223 variantsExt.variant('browser') {
214 224 role('production') { layers('main') }
215 225 }
216 226
217 227 variantSources {
218 228 lateConfigurationPolicy {
219 229 warnOnLateConfiguration()
220 230 }
221 231 }
222 232
223 233 afterEvaluate {
224 234 variantSources.whenAvailable { ctx ->
225 235 def browser = ctx.variants.variants.find { it.name == 'browser' }
226 236 def mainLayer = ctx.variants.layers.find { it.name == 'main' }
227 237 def unit = ctx.compileUnits.requireUnit(browser, mainLayer)
228 238
229 def sourceSet = ctx.sourceSets.getSourceSet(unit).get()
239 def unitSourceSet = ctx.sourceSets.getSourceSet(unit).get()
230 240 variantSources.layer('main') {
231 declareOutputs('late')
241 sourceSet {
242 declareOutputs('late')
243 }
232 244 }
233 sourceSet.output('late')
245 unitSourceSet.output('late')
234 246 println('lateWarn=ok')
235 247 }
236 248 }
237 249 """);
238 250
239 251 BuildResult result = runner("help").build();
240 252
241 253 assertTrue(result.getOutput().contains("Source sets for [layer=main] layer already materialized"));
242 254 assertTrue(result.getOutput().contains("lateWarn=ok"));
243 255 }
244 256
245 257 @Test
246 258 void rejectsChangingLateConfigurationPolicyAfterFirstSelector() throws Exception {
247 259 writeSettings("variant-sources-late-policy-fixed");
248 260 writeBuildFile("""
249 261 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
250 262
251 263 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
252 264 variantsExt.layers.create('main')
253 265 variantsExt.roles.create('production')
254 266 variantsExt.variant('browser') {
255 267 role('production') { layers('main') }
256 268 }
257 269
258 270 variantSources {
259 271 variant('browser') {
260 declareOutputs('js')
272 sourceSet {
273 declareOutputs('js')
274 }
261 275 }
262 276 lateConfigurationPolicy {
263 277 allowLateConfiguration()
264 278 }
265 279 }
266 280 """);
267 281
268 282 assertBuildFails("Lazy configuration policy already applied", "help");
269 283 }
270 284
271 285 @Test
272 286 void failsOnProjectedNameCollisionByDefault() throws Exception {
273 287 writeSettings("variant-sources-name-collision-fail");
274 288 writeBuildFile("""
275 289 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
276 290
277 291 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
278 292 variantsExt.layers.create('variantBar')
279 293 variantsExt.layers.create('bar')
280 294 variantsExt.roles.create('production')
281 295
282 296 variantsExt.variant('foo') {
283 297 role('production') { layers('variantBar') }
284 298 }
285 299 variantsExt.variant('fooVariant') {
286 300 role('production') { layers('bar') }
287 301 }
288 302 """);
289 303
290 304 assertBuildFails("The same source set names are produced by different compile units", "help");
291 305 }
292 306
293 307 @Test
294 308 void resolvesProjectedNameCollisionDeterministicallyWhenConfigured() throws Exception {
295 309 writeSettings("variant-sources-name-collision-resolve");
296 310 writeBuildFile("""
297 311 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
298 312
299 313 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
300 314 variantsExt.layers.create('variantBar')
301 315 variantsExt.layers.create('bar')
302 316 variantsExt.roles.create('production')
303 317
304 318 variantsExt.variant('foo') {
305 319 role('production') { layers('variantBar') }
306 320 }
307 321 variantsExt.variant('fooVariant') {
308 322 role('production') { layers('bar') }
309 323 }
310 324
311 325 variantSources {
312 326 namingPolicy {
313 327 resolveNameCollision()
314 328 }
315 329 }
316 330
317 331 afterEvaluate {
318 332 variantSources.whenAvailable { ctx ->
319 333 def foo = ctx.variants.variants.find { it.name == 'foo' }
320 334 def fooVariant = ctx.variants.variants.find { it.name == 'fooVariant' }
321 335 def variantBar = ctx.variants.layers.find { it.name == 'variantBar' }
322 336 def bar = ctx.variants.layers.find { it.name == 'bar' }
323 337
324 338 def later = ctx.compileUnits.requireUnit(fooVariant, bar)
325 339 def earlier = ctx.compileUnits.requireUnit(foo, variantBar)
326 340
327 341 println("map1=" + later.variant().name + ":" + later.layer().name + "->" + ctx.sourceSets.getSourceSet(later).name)
328 342 println("map2=" + earlier.variant().name + ":" + earlier.layer().name + "->" + ctx.sourceSets.getSourceSet(earlier).name)
329 343 }
330 344 }
331 345 """);
332 346
333 347 BuildResult result = runner("help").build();
334 348
335 349 assertTrue(result.getOutput().contains("map1=fooVariant:bar->fooVariantBar2"));
336 350 assertTrue(result.getOutput().contains("map2=foo:variantBar->fooVariantBar"));
337 351 }
338 352
339 353 @Test
340 354 void failsOnUnknownVariantSelectorTarget() throws Exception {
341 355 writeSettings("variant-sources-missing-variant");
342 356 writeBuildFile("""
343 357 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
344 358
345 359 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
346 360 variantsExt.layers.create('main')
347 361 variantsExt.roles.create('production')
348 362 variantsExt.variant('browser') {
349 363 role('production') { layers('main') }
350 364 }
351 365
352 366 variantSources {
353 367 variant('missing') {
354 declareOutputs('js')
368 sourceSet {
369 declareOutputs('js')
370 }
355 371 }
356 372 }
357 373 """);
358 374
359 375 assertBuildFails("Variant 'missing' isn't declared", "help");
360 376 }
361 377
362 378 @Test
363 379 void failsOnUnknownVariantContextSelectorTarget() throws Exception {
364 380 writeSettings("variant-sources-context-missing-variant");
365 381 writeBuildFile("""
366 382 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
367 383
368 384 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
369 385 variantsExt.layers.create('main')
370 386 variantsExt.roles.create('production')
371 387 variantsExt.variant('browser') {
372 388 role('production') { layers('main') }
373 389 }
374 390
375 391 variantSources.whenAvailable { ctx ->
376 392 def missing = objects.named(org.implab.gradle.variants.core.Variant, 'missing')
377 393 ctx.configureVariant(missing) {
378 declareOutputs('js')
394 sourceSet {
395 declareOutputs('js')
396 }
379 397 }
380 398 }
381 399 """);
382 400
383 401 assertBuildFails("The specified variant 'missing' isn't declared", "help");
384 402 }
385 403
386 404 @Test
387 405 void failsOnUnknownLayerSelectorTarget() throws Exception {
388 406 writeSettings("variant-sources-missing-layer");
389 407 writeBuildFile("""
390 408 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
391 409
392 410 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
393 411 variantsExt.layers.create('main')
394 412 variantsExt.roles.create('production')
395 413 variantsExt.variant('browser') {
396 414 role('production') { layers('main') }
397 415 }
398 416
399 417 variantSources {
400 418 layer('missing') {
401 declareOutputs('js')
419 sourceSet {
420 declareOutputs('js')
421 }
402 422 }
403 423 }
404 424 """);
405 425
406 426 assertBuildFails("Layer 'missing' isn't declared", "help");
407 427 }
408 428
409 429 @Test
410 430 void failsOnUnknownLayerContextSelectorTarget() throws Exception {
411 431 writeSettings("variant-sources-context-missing-layer");
412 432 writeBuildFile("""
413 433 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
414 434
415 435 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
416 436 variantsExt.layers.create('main')
417 437 variantsExt.roles.create('production')
418 438 variantsExt.variant('browser') {
419 439 role('production') { layers('main') }
420 440 }
421 441
422 442 variantSources.whenAvailable { ctx ->
423 443 def missing = objects.named(org.implab.gradle.variants.core.Layer, 'missing')
424 444 ctx.configureLayer(missing) {
425 declareOutputs('js')
445 sourceSet {
446 declareOutputs('js')
447 }
426 448 }
427 449 }
428 450 """);
429 451
430 452 assertBuildFails("The specified layer 'missing' isn't declared", "help");
431 453 }
432 454
433 455 @Test
434 456 void failsOnUndeclaredCompileUnitSelectorTarget() throws Exception {
435 457 writeSettings("variant-sources-missing-unit");
436 458 writeBuildFile("""
437 459 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
438 460
439 461 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
440 462 variantsExt.layers.create('main')
441 463 variantsExt.layers.create('test')
442 464 variantsExt.roles.create('production')
443 465 variantsExt.variant('browser') {
444 466 role('production') { layers('main') }
445 467 }
446 468
447 469 variantSources {
448 470 unit('browser', 'test') {
449 declareOutputs('js')
471 sourceSet {
472 declareOutputs('js')
473 }
450 474 }
451 475 }
452 476 """);
453 477
454 478 assertBuildFails("The CompileUnit isn't declared for variant 'browser', layer 'test'", "help");
455 479 }
456 480
457 481 @Test
458 482 void failsOnUndeclaredCompileUnitContextSelectorTarget() throws Exception {
459 483 writeSettings("variant-sources-context-missing-unit");
460 484 writeBuildFile("""
461 485 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
462 486
463 487 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
464 488 variantsExt.layers.create('main')
465 489 variantsExt.layers.create('test')
466 490 variantsExt.roles.create('production')
467 491 variantsExt.variant('browser') {
468 492 role('production') { layers('main') }
469 493 }
470 494
471 495 variantSources.whenAvailable { ctx ->
472 496 def browser = ctx.variants.variants.find { it.name == 'browser' }
473 497 def testLayer = ctx.variants.layers.find { it.name == 'test' }
474 498 def unit = new org.implab.gradle.variants.sources.CompileUnit(browser, testLayer)
475 499 ctx.configureUnit(unit) {
476 declareOutputs('js')
500 sourceSet {
501 declareOutputs('js')
502 }
477 503 }
478 504 }
479 505 """);
480 506
481 507 assertBuildFails("Compile unit for variant 'browser' and layer 'test' not found", "help");
482 508 }
483 509
484 510 @Test
485 511 void failsOnUndeclaredCompileUnitMaterializerTarget() throws Exception {
486 512 writeSettings("variant-sources-materializer-missing-unit");
487 513 writeBuildFile("""
488 514 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
489 515
490 516 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
491 517 variantsExt.layers.create('main')
492 518 variantsExt.layers.create('test')
493 519 variantsExt.roles.create('production')
494 520 variantsExt.variant('browser') {
495 521 role('production') { layers('main') }
496 522 }
497 523
498 524 variantSources.whenAvailable { ctx ->
499 525 def browser = ctx.variants.variants.find { it.name == 'browser' }
500 526 def testLayer = ctx.variants.layers.find { it.name == 'test' }
501 527 def unit = new org.implab.gradle.variants.sources.CompileUnit(browser, testLayer)
502 528 ctx.sourceSets.getSourceSet(unit)
503 529 }
504 530 """);
505 531
506 532 assertBuildFails("Compile unit for variant 'browser' and layer 'test' not found", "help");
507 533 }
508 534
509 535 @Test
510 536 void rejectsChangingNamingPolicyAfterContextBecomesObservable() throws Exception {
511 537 writeSettings("variant-sources-name-policy-fixed");
512 538 writeBuildFile("""
513 539 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
514 540
515 541 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
516 542 variantsExt.layers.create('main')
517 543 variantsExt.roles.create('production')
518 544 variantsExt.variant('browser') {
519 545 role('production') { layers('main') }
520 546 }
521 547
522 548 variantSources.whenAvailable {
523 549 variantSources.namingPolicy {
524 550 resolveNameCollision()
525 551 }
526 552 }
527 553 """);
528 554
529 555 assertBuildFails("Naming policy already applied", "help");
530 556 }
531 557
532 558 @Test
533 559 void rejectsChangingLateConfigurationPolicyAfterContextBecomesObservable() throws Exception {
534 560 writeSettings("variant-sources-late-policy-context-fixed");
535 561 writeBuildFile("""
536 562 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
537 563
538 564 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
539 565 variantsExt.layers.create('main')
540 566 variantsExt.roles.create('production')
541 567 variantsExt.variant('browser') {
542 568 role('production') { layers('main') }
543 569 }
544 570
545 571 variantSources.whenAvailable {
546 572 variantSources.lateConfigurationPolicy {
547 573 allowLateConfiguration()
548 574 }
549 575 }
550 576 """);
551 577
552 578 assertBuildFails("Lazy configuration policy already applied", "help");
553 579 }
554 580 }
General Comments 0
You need to be logged in to leave comments. Login now