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