##// END OF EJS Templates
variants: move source set layout conventions out of model...
cin -
r59:780370baa54c default
parent child
Show More
@@ -0,0 +1,45
1 package org.implab.gradle.variants.sources.internal;
2
3 import org.gradle.api.Action;
4 import org.gradle.api.file.Directory;
5 import org.gradle.api.provider.Provider;
6 import org.implab.gradle.common.core.lang.FilePaths;
7 import org.implab.gradle.variants.sources.CompileUnitSourceSetSpec;
8
9 /**
10 * Applies the default layout policy for source sets materialized from variant
11 * compile units.
12 *
13 * <p>
14 * The convention keeps sources grouped by layer and outputs grouped by
15 * variant/layer. It only sets directory properties on the materialized source
16 * set; declaring source directories and named outputs remains the
17 * responsibility of adapters or user DSL.
18 * </p>
19 */
20 public class CompileUnitLayoutConvention implements Action<CompileUnitSourceSetSpec> {
21 private final Provider<Directory> sourcesBaseDir;
22 private final Provider<Directory> outputsBaseDir;
23
24 public CompileUnitLayoutConvention(
25 Provider<Directory> baseDirectory,
26 Provider<Directory> outputsBaseDir) {
27 this.sourcesBaseDir = baseDirectory;
28 this.outputsBaseDir = outputsBaseDir;
29 }
30
31 @Override
32 public void execute(CompileUnitSourceSetSpec spec) {
33 var sourceSet = spec.getSourceSet();
34 var unit = spec.getCompileUnit();
35 var layerName = unit.layer().getName();
36 var variantName = unit.variant().getName();
37
38 sourceSet.getSourceSetDir()
39 .convention(sourcesBaseDir.map(base -> base.dir(FilePaths.cat(layerName))));
40
41 sourceSet.getOutputsDir()
42 .convention(outputsBaseDir.map(base -> base.dir(FilePaths.cat(variantName, layerName))));
43 }
44
45 }
@@ -1,9 +1,10
1 1 {
2 2 "java.configuration.updateBuildConfiguration": "automatic",
3 3 "java.compile.nullAnalysis.mode": "automatic",
4 4 "cSpell.words": [
5 5 "implab",
6 6 "materializer",
7 "rawtypes"
7 "rawtypes",
8 "Replayable"
8 9 ]
9 10 } No newline at end of file
@@ -1,41 +1,58
1 1 package org.implab.gradle.variants;
2 2
3 3 import org.gradle.api.GradleException;
4 4 import org.gradle.api.NamedDomainObjectContainer;
5 5 import org.gradle.api.Plugin;
6 6 import org.gradle.api.Project;
7 7 import org.gradle.api.logging.Logger;
8 8 import org.gradle.api.logging.Logging;
9 import org.implab.gradle.common.core.lang.FilePaths;
9 10 import org.implab.gradle.variants.sources.GenericSourceSet;
10 11
11 12 /**
12 13 * This plugin creates a {@code sources} extension which is
13 14 * a container for {@link GenericSourceSet}.
14 15 *
15 16 */
16 17 public abstract class SourcesPlugin implements Plugin<Project> {
17 18 private static final Logger logger = Logging.getLogger(SourcesPlugin.class);
18 19
19 private static final String SOURCES_EXTENSION_NAME = "sources";
20 public static final String SOURCES_EXTENSION_NAME = "sources";
21
22 private static final String SOURCES_DIRECTORY = "src";
23
24 private static final String OUTPUTS_DIRECTORY = "out";
20 25
21 26 @Override
22 27 public void apply(Project target) {
23 28 logger.debug("Registering '{}' extension on project '{}'", SOURCES_EXTENSION_NAME, target.getPath());
29
30 var layout = target.getLayout();
31
24 32 var sources = target.getObjects().domainObjectContainer(GenericSourceSet.class);
33 sources.configureEach(sourceSet -> {
34 sourceSet.getSourceSetDir()
35 .convention(layout.getProjectDirectory()
36 .dir(FilePaths.cat(SOURCES_DIRECTORY, sourceSet.getName())));
37 sourceSet.getOutputsDir()
38 .convention(layout.getBuildDirectory()
39 .dir(FilePaths.cat(OUTPUTS_DIRECTORY, sourceSet.getName())));
40 });
25 41 target.getExtensions().add(SOURCES_EXTENSION_NAME, sources);
26 42 }
27 43
28 44 public static NamedDomainObjectContainer<GenericSourceSet> getSourcesExtension(Project target) {
29 45 var extensions = target.getExtensions();
30 46
31 47 @SuppressWarnings("unchecked")
32 48 var extension = (NamedDomainObjectContainer<GenericSourceSet>) extensions.findByName(SOURCES_EXTENSION_NAME);
33 49
34 50 if (extension == null) {
35 logger.error("Sources extension '{}' isn't found on project '{}'", SOURCES_EXTENSION_NAME, target.getPath());
51 logger.error("Sources extension '{}' isn't found on project '{}'", SOURCES_EXTENSION_NAME,
52 target.getPath());
36 53 throw new GradleException("Sources extension isn't found");
37 54 }
38 55 logger.debug("Resolved '{}' extension on project '{}'", SOURCES_EXTENSION_NAME, target.getPath());
39 56 return extension;
40 57 }
41 58 }
@@ -1,112 +1,113
1 1 package org.implab.gradle.variants;
2 2
3 3 import org.gradle.api.Action;
4 4 import org.gradle.api.Plugin;
5 5 import org.gradle.api.Project;
6 6 import org.implab.gradle.common.core.lang.Deferred;
7 7 import org.implab.gradle.variants.artifacts.OutgoingConfigurationSpec;
8 8 import org.implab.gradle.variants.artifacts.OutgoingVariantsContext;
9 9 import org.implab.gradle.variants.artifacts.VariantArtifactsExtension;
10 10 import org.implab.gradle.variants.artifacts.VariantArtifactsSpec;
11 11 import org.implab.gradle.variants.artifacts.internal.ArtifactAssemblyBinder;
12 12 import org.implab.gradle.variants.artifacts.internal.ArtifactAssemblyHandler;
13 13 import org.implab.gradle.variants.artifacts.internal.ArtifactAssemblyRegistry;
14 14 import org.implab.gradle.variants.artifacts.internal.DefaultOutgoingVariantsContext;
15 15 import org.implab.gradle.variants.artifacts.internal.MaterializationPolicyHandler;
16 16 import org.implab.gradle.variants.artifacts.internal.OutgoingRegistry;
17 17 import org.implab.gradle.variants.artifacts.internal.SingleSlotConvention;
18 18 import org.implab.gradle.variants.core.Variant;
19 19 import org.implab.gradle.variants.core.VariantsExtension;
20 20 import org.implab.gradle.variants.sources.VariantSourcesExtension;
21 21 import org.implab.gradle.variants.artifacts.OutgoingConfigurationSlotSpec;
22 22
23 23 public abstract class VariantArtifactsPlugin implements Plugin<Project> {
24 public static final String VARIANT_ARTIFACTS_EXTENSION = "variantArtifacts";
24 25
25 26 @Override
26 27 public void apply(Project target) {
27 28 var extensions = target.getExtensions();
28 29 var objects = target.getObjects();
29 30 var providers = target.getProviders();
30 31 var plugins = target.getPlugins();
31 32 var configurations = target.getConfigurations();
32 33 var tasks = target.getTasks();
33 34 var layout = target.getLayout();
34 35
35 36 // Apply the main VariantsPlugin to ensure the core variant model is available.
36 37 plugins.apply(VariantsPlugin.class);
37 38 plugins.apply(VariantSourcesPlugin.class);
38 39 // Access the VariantsExtension to configure variant sources.
39 40 var variantsExtension = extensions.getByType(VariantsExtension.class);
40 41 var sourcesExtension = extensions.getByType(VariantSourcesExtension.class);
41 42
42 43 var deferred = new Deferred<OutgoingVariantsContext>();
43 44
44 45 variantsExtension.whenFinalized(variants -> {
45 46
46 47 var outgoing = new OutgoingRegistry(configurations, objects, variants.getVariants());
47 48 var assemblies = new ArtifactAssemblyRegistry();
48 49
49 50 // wire artifact assemblies to configuration variants
50 51 var assembliesBridge = new ArtifactAssemblyBinder(assemblies);
51 52 var primarySlotConvention = new SingleSlotConvention(providers);
52 53 var materializationHandler = new MaterializationPolicyHandler(objects);
53 54
54 55 // bind slot assemblies to outgoing variants
55 56 outgoing.configureEach(assembliesBridge::execute);
56 57 // apply primary slot convention when outgoing variant has a single slot
57 58 outgoing.configureEach(primarySlotConvention::execute);
58 59 // apply materialization policy hooks to outgoing variants
59 60 outgoing.configureEach(materializationHandler::execute);
60 61
61 62 sourcesExtension.whenAvailable(sources -> {
62 63 var assemblyHandler = new ArtifactAssemblyHandler(
63 64 objects,
64 65 tasks,
65 66 assemblies,
66 67 sources.getCompileUnits(),
67 68 sources.getRoleProjections(),
68 69 sources.getSourceSets());
69 70 assemblyHandler.getAssembliesDirectory().set(layout.getBuildDirectory().dir("variant-assemblies"));
70 71
71 72 deferred.resolve(new DefaultOutgoingVariantsContext(
72 73 assemblies,
73 74 outgoing,
74 75 assemblyHandler,
75 76 materializationHandler));
76 77 });
77 78
78 79 });
79 80
80 81 var variantArtifacts = new VariantArtifactsExtension() {
81 82
82 83 @Override
83 84 public void variant(String variantName, Action<? super VariantArtifactsSpec> action) {
84 85 deferred.whenResolved(context -> {
85 86 var variant = objects.named(Variant.class, variantName);
86 87 context.configureVariant(variant, action);
87 88 });
88 89 }
89 90
90 91 @Override
91 92 public void whenAvailable(Action<? super OutgoingVariantsContext> action) {
92 93 deferred.whenResolved(handler -> action.execute(handler));
93 94 }
94 95
95 96 @Override
96 97 public void whenOutgoingConfiguration(Action<? super OutgoingConfigurationSpec> action) {
97 98 deferred.whenResolved(registry -> registry.whenOutgoingConfiguration(action));
98 99
99 100 }
100 101
101 102 @Override
102 103 public void whenOutgoingSlot(Action<? super OutgoingConfigurationSlotSpec> action) {
103 104 deferred.whenResolved(registry -> registry.whenOutgoingSlot(action));
104 105 }
105 106
106 107 };
107 108
108 extensions.add(VariantArtifactsExtension.class, "variantArtifacts", variantArtifacts);
109 extensions.add(VariantArtifactsExtension.class, VARIANT_ARTIFACTS_EXTENSION, variantArtifacts);
109 110
110 111 }
111 112
112 113 }
@@ -1,166 +1,155
1 1 package org.implab.gradle.variants;
2 2
3 3 import java.util.Objects;
4 import java.util.function.Predicate;
5 4
6 5 import org.eclipse.jdt.annotation.NonNullByDefault;
7 6 import org.gradle.api.Action;
8 import org.gradle.api.InvalidUserDataException;
9 import org.gradle.api.Named;
10 7 import org.gradle.api.Plugin;
11 8 import org.gradle.api.Project;
12 9 import org.implab.gradle.common.core.lang.Deferred;
13 10 import org.implab.gradle.common.core.lang.Strings;
14 11 import org.implab.gradle.variants.core.Layer;
15 12 import org.implab.gradle.variants.core.Variant;
16 13 import org.implab.gradle.variants.core.VariantsExtension;
17 import org.implab.gradle.variants.core.VariantsView;
18 14 import org.implab.gradle.variants.sources.CompileUnit;
19 15 import org.implab.gradle.variants.sources.CompileUnitSourceSetSpec;
20 16 import org.implab.gradle.variants.sources.CompileUnitsView;
21 17 import org.implab.gradle.variants.sources.RoleProjectionsView;
22 18 import org.implab.gradle.variants.sources.VariantSourcesContext;
23 19 import org.implab.gradle.variants.sources.VariantSourcesExtension;
20 import org.implab.gradle.variants.sources.internal.CompileUnitLayoutConvention;
24 21 import org.implab.gradle.variants.sources.internal.CompileUnitNamer;
25 22 import org.implab.gradle.variants.sources.internal.DefaultCompileUnitNamingPolicy;
26 23 import org.implab.gradle.variants.sources.internal.DefaultLateConfigurationPolicySpec;
27 24 import org.implab.gradle.variants.sources.internal.DefaultVariantSourcesContext;
28 25 import org.implab.gradle.variants.sources.internal.SourceSetConfigurationRegistry;
29 26 import org.implab.gradle.variants.sources.internal.SourceSetRegistry;
30 27
31 28 @NonNullByDefault
32 29 public abstract class VariantSourcesPlugin implements Plugin<Project> {
33 30 public static final String VARIANT_SOURCES_EXTENSION = "variantSources";
34 31
32 private static final String VARIANT_OUTPUTS_DIRECTORY = "variants";
33
34 private static final String VARIANT_SOURCES_DIRECTORY = "src";
35
35 36 @Override
36 37 public void apply(Project target) {
37 38 var extensions = target.getExtensions();
38 39
39 40 // Apply the main VariantsPlugin to ensure the core variant model is available.
40 41 target.getPlugins().apply(VariantsPlugin.class);
41 42 // Access the VariantsExtension to configure variant sources.
42 43 var variantsExtension = extensions.getByType(VariantsExtension.class);
43 var objectFactory = target.getObjects();
44 var objects = target.getObjects();
45 var providers = target.getProviders();
44 46
45 47 var deferred = new Deferred<VariantSourcesContext>();
46 48
47 49 var lateConfigurationPolicy = new DefaultLateConfigurationPolicySpec();
48 50 var namingPolicy = new DefaultCompileUnitNamingPolicy();
49 51
50 52 variantsExtension.whenFinalized(variants -> {
51 53 // create variant views
52 54 var compileUnits = CompileUnitsView.of(variants);
53 55 var roleProjections = RoleProjectionsView.of(variants);
54 56
55 57 // create registries
56 var sourceSetRegistry = new SourceSetRegistry(objectFactory);
58 var sourceSetRegistry = new SourceSetRegistry(objects);
57 59 lateConfigurationPolicy.finalizePolicy();
58 60 var sourceSetConfiguration = new SourceSetConfigurationRegistry(lateConfigurationPolicy::mode);
59 61
60 62 // build compile unit namer
61 63 var compileUnitNamer = CompileUnitNamer.builder()
62 64 .addUnits(compileUnits.getUnits())
63 65 .nameCollisionPolicy(namingPolicy.policy())
64 66 .build();
65 67
68 var compileUnitLayoutConvention = new CompileUnitLayoutConvention(
69 providers.provider(() -> target.getLayout().getProjectDirectory().dir(VARIANT_SOURCES_DIRECTORY)),
70 target.getLayout().getBuildDirectory().dir(VARIANT_OUTPUTS_DIRECTORY));
71
72 // apply layout convention to all compile unit source sets
73 sourceSetConfiguration.addAction(compileUnitLayoutConvention);
74
66 75 // create the context
67 76 var context = new DefaultVariantSourcesContext(
68 77 variants,
69 78 compileUnits,
70 79 roleProjections,
71 80 compileUnitNamer,
72 81 sourceSetRegistry,
73 sourceSetConfiguration
74 );
82 sourceSetConfiguration);
75 83 deferred.resolve(context);
76 84 });
77 85
78 86 var variantSourcesExtension = new VariantSourcesExtension() {
79 87 @Override
80 88 public void whenAvailable(Action<? super VariantSourcesContext> action) {
81 89 deferred.whenResolved(action::execute);
82 90 }
83 91
84 92 @Override
85 93 public void lateConfigurationPolicy(Action<? super LateConfigurationPolicySpec> action) {
86 94 action.execute(lateConfigurationPolicy);
87 95 }
88 96
89 97 @Override
90 98 public void namingPolicy(Action<? super NamingPolicySpec> action) {
91 99 action.execute(namingPolicy);
92 100 }
93 101
94 102 @Override
95 103 public void variant(String variantName, Action<? super CompileUnitSourceSetSpec> action) {
96 104 Strings.argumentNotNullOrBlank(variantName, "variantName");
97 105 Objects.requireNonNull(action, "action can't be null");
98 106
99 107 lateConfigurationPolicy.finalizePolicy();
100 108
101 whenAvailable(ctx -> ctx.configureVariant(resolveVariant(ctx.getVariants(), variantName), action));
109 var variant = objects.named(Variant.class, variantName);
110
111 whenAvailable(ctx -> ctx.configureVariant(variant, action));
102 112 }
103 113
104 114 @Override
105 115 public void layer(String layerName, Action<? super CompileUnitSourceSetSpec> action) {
106 116 // protect external DSL
107 117 Strings.argumentNotNullOrBlank(layerName, "layerName");
108 118 Objects.requireNonNull(action, "action can't be null");
109 119
110 120 lateConfigurationPolicy.finalizePolicy();
111 121
112 whenAvailable(ctx -> ctx.configureLayer(resolveLayer(ctx.getVariants(), layerName), action));
122 var layer = objects.named(Layer.class, layerName);
123
124 whenAvailable(ctx -> ctx.configureLayer(layer, action));
113 125 }
114 126
115 127 @Override
116 128 public void unit(String variantName, String layerName, Action<? super CompileUnitSourceSetSpec> action) {
117 129 Strings.argumentNotNullOrBlank(layerName, "layerName");
118 130 Strings.argumentNotNullOrBlank(variantName, "variantName");
119 131 Objects.requireNonNull(action, "action can't be null");
120 132
121 133 lateConfigurationPolicy.finalizePolicy();
122 134
123 whenAvailable(ctx -> ctx.configureUnit(resolveCompileUnit(ctx, variantName, layerName), action));
135 var variant = objects.named(Variant.class, variantName);
136 var layer = objects.named(Layer.class, layerName);
137 var unit = new CompileUnit(variant, layer);
138
139 whenAvailable(ctx -> ctx.configureUnit(unit, action));
124 140 }
125 141
126 142 @Override
127 143 public void configureEach(Action<? super CompileUnitSourceSetSpec> action) {
128 144 Objects.requireNonNull(action, "action can't be null");
129 145
130 146 lateConfigurationPolicy.finalizePolicy();
131 147
132 148 whenAvailable(ctx -> ctx.configureEach(action));
133 149 }
134 150 };
135 151
136 152 extensions.add(VariantSourcesExtension.class, VARIANT_SOURCES_EXTENSION, variantSourcesExtension);
137 153
138 154 }
139
140 private static Layer resolveLayer(VariantsView variants, String name) {
141 return variants.getLayers().stream()
142 .filter(named(name))
143 .findAny()
144 .orElseThrow(() -> new InvalidUserDataException("Layer '" + name + "' isn't declared"));
145 155 }
146
147 private static Variant resolveVariant(VariantsView variants, String name) {
148 return variants.getVariants().stream()
149 .filter(named(name))
150 .findAny()
151 .orElseThrow(() -> new InvalidUserDataException("Variant '" + name + "' isn't declared"));
152 }
153
154 private static CompileUnit resolveCompileUnit(VariantSourcesContext ctx, String variantName, String layerName) {
155 return ctx.getCompileUnits().findUnit(
156 resolveVariant(ctx.getVariants(), variantName),
157 resolveLayer(ctx.getVariants(), layerName))
158 .orElseThrow(() -> new InvalidUserDataException(
159 "The CompileUnit isn't declared for variant '" + variantName + "', layer '" + layerName + "'"));
160 }
161
162 private static Predicate<Named> named(String name) {
163 return named -> named.getName().equals(name);
164 }
165
166 }
@@ -1,102 +1,104
1 1 package org.implab.gradle.variants;
2 2
3 3 import org.gradle.api.Action;
4 4 import org.gradle.api.NamedDomainObjectContainer;
5 5 import org.gradle.api.Plugin;
6 6 import org.gradle.api.Project;
7 7 import org.gradle.api.model.ObjectFactory;
8 8 import org.implab.gradle.common.core.lang.Deferred;
9 9 import org.implab.gradle.variants.core.Layer;
10 10 import org.implab.gradle.variants.core.Role;
11 11 import org.implab.gradle.variants.core.Variant;
12 12 import org.implab.gradle.variants.core.VariantDefinition;
13 13 import org.implab.gradle.variants.core.VariantsExtension;
14 14 import org.implab.gradle.variants.core.VariantsView;
15 15 import org.implab.gradle.variants.internal.IdentityContainerFactory;
16 16
17 17 /**
18 18 * <ul>
19 19 * <li> {@link Variant} defines compilation semantics
20 20 * <li> {@link Layer} defines compilation partition
21 21 * <li> {@link Role} defines result grouping / publication intent
22 22 * </ul>
23 23 *
24 24 */
25 25 public abstract class VariantsPlugin implements Plugin<Project> {
26 public static final String VARIANTS_EXTENSION = "variants";
27
26 28 @Override
27 29 public void apply(Project target) {
28 30
29 31 var objectFactory = target.getObjects();
30 32
31 33 var variantsExtension = new DefaultVariantsExtension(objectFactory);
32 target.getExtensions().add(VariantsExtension.class, "variants", variantsExtension);
34 target.getExtensions().add(VariantsExtension.class, VARIANTS_EXTENSION, variantsExtension);
33 35 target.afterEvaluate(t -> variantsExtension.finalizeExtension());
34 36
35 37 }
36 38
37 39 static class DefaultVariantsExtension implements VariantsExtension {
38 40
39 41 private final NamedDomainObjectContainer<Layer> layers;
40 42 private final NamedDomainObjectContainer<Role> roles;
41 43 private final NamedDomainObjectContainer<VariantDefinition> variantDefinitions;
42 44 private final Deferred<VariantsView> finalizedResult = new Deferred<>();
43 45 private final ObjectFactory objectFactory;
44 46 private boolean finalized = false;
45 47
46 48 public DefaultVariantsExtension(ObjectFactory objectFactory) {
47 49 this.objectFactory = objectFactory;
48 50 this.layers = IdentityContainerFactory.create(objectFactory, Layer.class);
49 51 this.roles = IdentityContainerFactory.create(objectFactory, Role.class);
50 52 this.variantDefinitions = objectFactory.domainObjectContainer(VariantDefinition.class);
51 53 }
52 54
53 55 @Override
54 56 public NamedDomainObjectContainer<Layer> getLayers() {
55 57 return layers;
56 58 }
57 59
58 60 @Override
59 61 public NamedDomainObjectContainer<Role> getRoles() {
60 62 return roles;
61 63 }
62 64
63 65 @Override
64 66 public NamedDomainObjectContainer<VariantDefinition> getVariantDefinitions() {
65 67 return variantDefinitions;
66 68 }
67 69
68 70 @Override
69 71 public void whenFinalized(Action<? super VariantsView> action) {
70 72 finalizedResult.whenResolved(action::execute);
71 73 }
72 74
73 75 void finalizeExtension() {
74 76 if (finalized)
75 77 return;
76 78
77 79 finalized = true;
78 80
79 81 // freeze defined variants
80 82 variantDefinitions.forEach(VariantDefinition::finalizeVariant);
81 83
82 84 // build a snapshot
83 85 var viewBuilder = VariantsView.builder();
84 86
85 87 // calculate and add variants
86 88 variantDefinitions.stream()
87 89 .map(def -> objectFactory.named(Variant.class, def.getName()))
88 90 .forEach(viewBuilder::addVariant);
89 91 // add layers
90 92 layers.forEach(viewBuilder::addLayer);
91 93 // add roles
92 94 roles.forEach(viewBuilder::addRole);
93 95 // add definitions
94 96 variantDefinitions.forEach(viewBuilder::addDefinition);
95 97 // assemble the view
96 98 var view = viewBuilder.build();
97 99 // set the result and call hooks
98 100 finalizedResult.resolve(view);
99 101 }
100 102 }
101 103
102 104 }
@@ -1,191 +1,192
1 1 package org.implab.gradle.variants.sources;
2 2
3 3 import java.io.File;
4 import java.nio.file.Paths;
5 4 import java.util.HashSet;
6 5 import java.util.LinkedHashMap;
7 6 import java.util.List;
8 7 import java.util.Map;
9 8 import java.util.Objects;
10 9 import java.util.Set;
11 10 import java.util.concurrent.Callable;
12 11 import java.util.function.Function;
13 12 import java.util.stream.Collectors;
14 13 import java.util.stream.Stream;
15 14
16 15 import javax.inject.Inject;
17 16
18 17 import org.gradle.api.InvalidUserDataException;
19 18 import org.gradle.api.Named;
20 19 import org.gradle.api.NamedDomainObjectContainer;
21 20 import org.gradle.api.Task;
22 21 import org.gradle.api.file.ConfigurableFileCollection;
23 22 import org.gradle.api.file.DirectoryProperty;
24 23 import org.gradle.api.file.FileCollection;
25 import org.gradle.api.file.ProjectLayout;
26 24 import org.gradle.api.file.SourceDirectorySet;
27 25 import org.gradle.api.model.ObjectFactory;
28 26 import org.gradle.api.tasks.TaskProvider;
29 27
30 28 /**
31 29 * A configurable source set abstraction with named outputs.
32 30 *
33 31 * <p>
34 32 * Each instance aggregates multiple {@link SourceDirectorySet source sets}
35 33 * under a shared name and exposes typed outputs that must be declared up front.
36 * Default locations are {@code src/<name>} for sources and
37 * {@code build/<name>} for outputs, both of which can be customized via the
38 * exposed {@link DirectoryProperty} setters.
34 * Source and output base directories are explicit model properties. They do
35 * not define source directories or outputs by themselves; plugins and adapters
36 * may attach conventions or use them as layout hints for their own
37 * materialization rules.
39 38 * </p>
40 39 *
41 40 * <p>
42 41 * Outputs are grouped by names to make task wiring explicit. An output must be
43 42 * declared with {@link #declareOutputs(String, String...)} before files can be
44 43 * registered against it. Attempting to register or retrieve an undeclared
45 44 * output results in
46 45 * {@link InvalidUserDataException}.
47 46 * </p>
48 47 */
49 48 public abstract class GenericSourceSet implements Named {
50 49 private final String name;
51 50
52 51 private final NamedDomainObjectContainer<SourceDirectorySet> sourceDirectorySets;
53 52
54 53 private final Map<String, ConfigurableFileCollection> outputs;
55 54
56 55 private final FileCollection allOutputs;
57 56
58 57 private final FileCollection allSourceDirectories;
59 58
60 59 private final ObjectFactory objects;
61 60
62 61 private final Set<String> declaredOutputs = new HashSet<>();
63 62
64 63 @Inject
65 public GenericSourceSet(String name, ObjectFactory objects, ProjectLayout layout) {
64 public GenericSourceSet(String name, ObjectFactory objects) {
66 65 this.name = name;
67 66 this.objects = objects;
68 67
69 68 sourceDirectorySets = objects.domainObjectContainer(
70 69 SourceDirectorySet.class,
71 70 this::createSourceDirectorySet);
72 71
73 72 outputs = new LinkedHashMap<>();
74 73
75 74 allSourceDirectories = objects.fileCollection().from(sourceDirectoriesProvider());
76 75
77 76 allOutputs = objects.fileCollection().from(outputsProvider());
78
79 getSourceSetDir().convention(layout
80 .getProjectDirectory()
81 .dir(Paths.get("src", name).toString()));
82
83 getOutputsDir().convention(layout
84 .getBuildDirectory()
85 .dir(name));
86 77 }
87 78
88 79 @Override
89 80 public String getName() {
90 81 return name;
91 82 }
92 83
93 84 /**
94 * Base directory for this source set. Defaults to {@code src/<name>} under
95 * the project directory.
85 * Base directory associated with this source set.
86 *
87 * <p>
88 * This property is intentionally convention-free in the base model. A
89 * plugin may attach a convention when it knows the surrounding layout
90 * policy, for example when materializing variant compile units.
91 * </p>
96 92 */
97 93 public abstract DirectoryProperty getSourceSetDir();
98 94
99 95 /**
100 * Base directory for outputs of this source set. Defaults to
101 * {@code build/<name>}.
96 * Base directory associated with outputs of this source set.
97 *
98 * <p>
99 * This property is a layout hint. Actual named outputs are declared and
100 * populated through {@link #declareOutputs(String, String...)} and
101 * {@link #registerOutput(String, Object...)}.
102 * </p>
102 103 */
103 104 public abstract DirectoryProperty getOutputsDir();
104 105
105 106 /**
106 107 * The container of {@link SourceDirectorySet} instances that belong to this
107 108 * logical source set.
108 109 */
109 110 public NamedDomainObjectContainer<SourceDirectorySet> getSets() {
110 111 return sourceDirectorySets;
111 112 }
112 113
113 114 /**
114 115 * All registered outputs grouped across output names.
115 116 */
116 117 public FileCollection getAllOutputs() {
117 118 return allOutputs;
118 119 }
119 120
120 121 /**
121 122 * All source directories from every contained {@link SourceDirectorySet}.
122 123 */
123 124 public FileCollection getAllSourceDirectories() {
124 125 return allSourceDirectories;
125 126 }
126 127
127 128 /**
128 129 * Returns the file collection for the specified output name, creating it
129 130 * if necessary.
130 131 *
131 132 * @throws InvalidUserDataException if the output was not declared
132 133 */
133 134 public FileCollection output(String name) {
134 135 return configurableOutput(name);
135 136 }
136 137
137 138 private ConfigurableFileCollection configurableOutput(String name) {
138 139 requireDeclaredOutput(name);
139 140 return outputs.computeIfAbsent(name, key -> objects.fileCollection());
140 141 }
141 142
142 143 /**
143 144 * Declares allowed output names. Outputs must be declared before registering
144 145 * files under them.
145 146 */
146 147 public void declareOutputs(String name, String... extra) {
147 148 Stream.concat(Stream.of(name), Stream.of(extra))
148 149 .map(Objects::requireNonNull)
149 150 .forEach(declaredOutputs::add);
150 151 }
151 152
152 153 /**
153 154 * Registers files produced elsewhere under the given output.
154 155 */
155 156 public void registerOutput(String name, Object... files) {
156 157 configurableOutput(name).from(files);
157 158 }
158 159
159 160 /**
160 161 * Registers output files produced by a task, using a mapper to extract the
161 162 * output from the task. The task will be added as a build dependency of this
162 163 * output.
163 164 */
164 165 public <T extends Task> void registerOutput(String name, TaskProvider<T> task,
165 166 Function<? super T, ?> mapper) {
166 167 configurableOutput(name).from(task.map(mapper::apply))
167 168 .builtBy(task);
168 169 }
169 170
170 171 private SourceDirectorySet createSourceDirectorySet(String name) {
171 172 return objects.sourceDirectorySet(name, name);
172 173 }
173 174
174 175 private void requireDeclaredOutput(String outputName) {
175 176 if (!declaredOutputs.contains(outputName)) {
176 177 throw new InvalidUserDataException(
177 178 "Output '" + outputName + "' is not declared for source set '" + name + "'");
178 179 }
179 180 }
180 181
181 182 private Callable<List<? extends FileCollection>> outputsProvider() {
182 183 return () -> outputs.values().stream().toList();
183 184 }
184 185
185 186 private Callable<Set<File>> sourceDirectoriesProvider() {
186 187 return () -> sourceDirectorySets.stream()
187 188 .flatMap(x -> x.getSrcDirs().stream())
188 189 .collect(Collectors.toSet());
189 190 }
190 191
191 192 }
@@ -1,86 +1,90
1 1 package org.implab.gradle.variants;
2 2
3 3 import static org.junit.jupiter.api.Assertions.assertEquals;
4 4 import static org.junit.jupiter.api.Assertions.assertNotNull;
5 5 import static org.junit.jupiter.api.Assertions.assertTrue;
6 6
7 7 import java.io.IOException;
8 8 import java.util.Properties;
9 9
10 10 import org.gradle.testkit.runner.BuildResult;
11 11 import org.junit.jupiter.api.Test;
12 12
13 13 class PluginMarkerFunctionalTest extends AbstractFunctionalTest {
14 14
15 15 @Test
16 16 void pluginMarkersPointToCurrentImplementations() throws Exception {
17 17 assertMarker("org.implab.gradle-variants.properties", VariantsPlugin.class);
18 18 assertMarker("org.implab.gradle-sources.properties", SourcesPlugin.class);
19 19 assertMarker("org.implab.gradle-variants-sources.properties", VariantSourcesPlugin.class);
20 20 assertMarker("org.implab.gradle-variants-artifacts.properties", VariantArtifactsPlugin.class);
21 21 }
22 22
23 23 @Test
24 24 void pluginIdsApplyCurrentExtensions() throws Exception {
25 25 writeSettings("variants-plugin-ids");
26 26 writeBuildFile("""
27 27 apply plugin: 'org.implab.gradle-variants'
28 28 apply plugin: 'org.implab.gradle-sources'
29 29 apply plugin: 'org.implab.gradle-variants-sources'
30 30 apply plugin: 'org.implab.gradle-variants-artifacts'
31 31
32 32 tasks.register('probe') {
33 33 doLast {
34 34 println("variantsExtension=" + (project.extensions.findByName('variants') != null))
35 35 println("sourcesExtension=" + (project.extensions.findByName('sources') != null))
36 36 println("variantSourcesExtension=" + (project.extensions.findByName('variantSources') != null))
37 37 println("variantArtifactsExtension=" + (project.extensions.findByName('variantArtifacts') != null))
38 38 }
39 39 }
40 40 """);
41 41
42 42 BuildResult result = runner("probe").build();
43 43
44 44 assertTrue(result.getOutput().contains("variantsExtension=true"));
45 45 assertTrue(result.getOutput().contains("sourcesExtension=true"));
46 46 assertTrue(result.getOutput().contains("variantSourcesExtension=true"));
47 47 assertTrue(result.getOutput().contains("variantArtifactsExtension=true"));
48 48 }
49 49
50 50 @Test
51 51 void sourcesPluginSupportsStandaloneSourceSetWorkflow() throws Exception {
52 52 writeSettings("sources-plugin-standalone");
53 53 writeFile("inputs/main.js", "console.log('main')\n");
54 54 writeBuildFile("""
55 55 apply plugin: 'org.implab.gradle-sources'
56 56
57 57 sources.create('main') {
58 58 declareOutputs('js')
59 59 registerOutput('js', layout.projectDirectory.file('inputs/main.js'))
60 60 }
61 61
62 62 tasks.register('probe') {
63 63 doLast {
64 64 def main = sources.named('main').get()
65 65 println("sourceSet=" + main.name)
66 println("sourceSetDir=" + project.relativePath(main.sourceSetDir.get().asFile))
67 println("outputsDir=" + project.relativePath(main.outputsDir.get().asFile))
66 68 println("jsFiles=" + main.output('js').files.collect { it.name }.sort().join(','))
67 69 }
68 70 }
69 71 """);
70 72
71 73 BuildResult result = runner("probe").build();
72 74
73 75 assertTrue(result.getOutput().contains("sourceSet=main"));
76 assertTrue(result.getOutput().contains("sourceSetDir=src/main"));
77 assertTrue(result.getOutput().contains("outputsDir=build/out/main"));
74 78 assertTrue(result.getOutput().contains("jsFiles=main.js"));
75 79 }
76 80
77 81 private static void assertMarker(String markerName, Class<?> implementationClass) throws IOException {
78 82 var resourceName = "META-INF/gradle-plugins/" + markerName;
79 83 var resource = PluginMarkerFunctionalTest.class.getClassLoader().getResourceAsStream(resourceName);
80 84 assertNotNull(resource, "Missing plugin marker " + resourceName);
81 85
82 86 var properties = new Properties();
83 87 properties.load(resource);
84 88 assertEquals(implementationClass.getName(), properties.getProperty("implementation-class"));
85 89 }
86 90 }
@@ -1,580 +1,585
1 1 package org.implab.gradle.variants;
2 2
3 3 import static org.junit.jupiter.api.Assertions.assertTrue;
4 4
5 5 import org.gradle.testkit.runner.BuildResult;
6 6 import org.junit.jupiter.api.Test;
7 7
8 8 class VariantSourcesPluginFunctionalTest extends AbstractFunctionalTest {
9 9
10 10 @Test
11 11 void exposesDerivedViewsAndStableSourceSetProvider() throws Exception {
12 12 writeSettings("variant-sources-derived-views");
13 13 writeBuildFile("""
14 14 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
15 15
16 16 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
17 17 variantsExt.layers.create('main')
18 18 variantsExt.layers.create('test')
19 19 variantsExt.roles.create('production')
20 20 variantsExt.roles.create('test')
21 21
22 22 variantsExt.variant('browser') {
23 23 role('production') { layers('main') }
24 24 role('test') { layers('main', 'test') }
25 25 }
26 26
27 27 def lines = []
28 28
29 29 variantSources.whenAvailable { ctx ->
30 30 lines << "units=" + ctx.compileUnits.units
31 31 .collect { "${it.variant().name}:${it.layer().name}" }
32 32 .sort()
33 33 .join(',')
34 34
35 35 def browser = ctx.variants.variants.find { it.name == 'browser' }
36 36 def production = ctx.variants.roles.find { it.name == 'production' }
37 37 def mainLayer = ctx.variants.layers.find { it.name == 'main' }
38 38 def projection = ctx.roleProjections.requireProjection(browser, production)
39 39 def unit = ctx.compileUnits.requireUnit(browser, mainLayer)
40 40
41 41 def left = ctx.sourceSets.getSourceSet(unit)
42 42 def right = ctx.sourceSets.getSourceSet(unit)
43 def sourceSet = left.get()
43 44
44 45 lines << "projectionUnits=" + ctx.roleProjections.getUnits(projection)
45 46 .collect { it.layer().name }
46 47 .sort()
47 48 .join(',')
48 lines << "mainSourceSet=" + left.name
49 lines << "mainSourceSet=" + sourceSet.name
50 lines << "mainSourceSetDir=" + project.relativePath(sourceSet.sourceSetDir.get().asFile)
51 lines << "mainOutputsDir=" + project.relativePath(sourceSet.outputsDir.get().asFile)
49 52 lines << "sameProvider=" + left.is(right)
50 53 }
51 54
52 55 afterEvaluate {
53 56 variantSources.whenAvailable { ctx ->
54 57 lines << "late:variants=" + ctx.variants.variants.collect { it.name }.sort().join(',')
55 58 }
56 59 }
57 60
58 61 tasks.register('probe') {
59 62 doLast {
60 63 lines.each { println(it) }
61 64 }
62 65 }
63 66 """);
64 67
65 68 BuildResult result = runner("probe").build();
66 69
67 70 assertTrue(result.getOutput().contains("units=browser:main,browser:test"));
68 71 assertTrue(result.getOutput().contains("projectionUnits=main"));
69 72 assertTrue(result.getOutput().contains("mainSourceSet=browserMain"));
73 assertTrue(result.getOutput().contains("mainSourceSetDir=src/main"));
74 assertTrue(result.getOutput().contains("mainOutputsDir=build/variants/browser/main"));
70 75 assertTrue(result.getOutput().contains("sameProvider=true"));
71 76 assertTrue(result.getOutput().contains("late:variants=browser"));
72 77 }
73 78
74 79 @Test
75 80 void appliesSelectorPrecedenceForFutureMaterialization() throws Exception {
76 81 writeSettings("variant-sources-precedence");
77 82 writeBuildFile("""
78 83 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
79 84
80 85 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
81 86 variantsExt.layers.create('main')
82 87 variantsExt.layers.create('test')
83 88 variantsExt.roles.create('production')
84 89 variantsExt.roles.create('test')
85 90
86 91 variantsExt.variant('browser') {
87 92 role('production') { layers('main') }
88 93 role('test') { layers('main', 'test') }
89 94 }
90 95
91 96 variantsExt.variant('node') {
92 97 role('production') { layers('main') }
93 98 }
94 99
95 100 def events = []
96 101
97 102 variantSources {
98 103 configureEach {
99 104 events << "all:" + sourceSet.name
100 105 events << "context:" + sourceSet.name + ":" + variant.name + ":" + layer.name
101 106 }
102 107 variant('browser') {
103 108 events << "variant:" + sourceSet.name
104 109 }
105 110 layer('main') {
106 111 events << "layer:" + sourceSet.name
107 112 }
108 113 unit('browser', 'main') {
109 114 events << "unit:" + sourceSet.name
110 115 }
111 116 }
112 117
113 118 afterEvaluate {
114 119 variantSources.whenAvailable { ctx ->
115 120 def browser = ctx.variants.variants.find { it.name == 'browser' }
116 121 def node = ctx.variants.variants.find { it.name == 'node' }
117 122 def mainLayer = ctx.variants.layers.find { it.name == 'main' }
118 123 def testLayer = ctx.variants.layers.find { it.name == 'test' }
119 124
120 125 def browserMain = ctx.sourceSets.getSourceSet(ctx.compileUnits.requireUnit(browser, mainLayer)).get()
121 126 def browserTest = ctx.sourceSets.getSourceSet(ctx.compileUnits.requireUnit(browser, testLayer)).get()
122 127 def nodeMain = ctx.sourceSets.getSourceSet(ctx.compileUnits.requireUnit(node, mainLayer)).get()
123 128 def bySourceSet = events.groupBy { it.split(':', 2)[1] }
124 129
125 130 println("browserMain=" + bySourceSet[browserMain.name].collect { it.split(':', 2)[0] }.join(','))
126 131 println("browserTest=" + bySourceSet[browserTest.name].collect { it.split(':', 2)[0] }.join(','))
127 132 println("nodeMain=" + bySourceSet[nodeMain.name].collect { it.split(':', 2)[0] }.join(','))
128 133 println("context=" + events.find { it.startsWith("context:" + browserMain.name + ":") })
129 134 }
130 135 }
131 136 """);
132 137
133 138 BuildResult result = runner("help").build();
134 139
135 140 assertTrue(result.getOutput().contains("browserMain=all,variant,layer,unit"));
136 141 assertTrue(result.getOutput().contains("browserTest=all,variant"));
137 142 assertTrue(result.getOutput().contains("nodeMain=all,layer"));
138 143 assertTrue(result.getOutput().contains("context=context:browserMain:browser:main"));
139 144 }
140 145
141 146 @Test
142 147 void failsLateConfigurationByDefaultAfterMaterialization() throws Exception {
143 148 writeSettings("variant-sources-late-fail");
144 149 writeBuildFile("""
145 150 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
146 151
147 152 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
148 153 variantsExt.layers.create('main')
149 154 variantsExt.roles.create('production')
150 155 variantsExt.variant('browser') {
151 156 role('production') { layers('main') }
152 157 }
153 158
154 159 afterEvaluate {
155 160 variantSources.whenAvailable { ctx ->
156 161 def browser = ctx.variants.variants.find { it.name == 'browser' }
157 162 def mainLayer = ctx.variants.layers.find { it.name == 'main' }
158 163 def unit = ctx.compileUnits.requireUnit(browser, mainLayer)
159 164
160 165 ctx.sourceSets.getSourceSet(unit).get()
161 166 variantSources.layer('main') {
162 167 sourceSet {
163 168 declareOutputs('late')
164 169 }
165 170 }
166 171 }
167 172 }
168 173 """);
169 174
170 175 assertBuildFails("Source sets for [layer=main] layer already materialized", "help");
171 176 }
172 177
173 178 @Test
174 179 void allowsLateConfigurationWhenSelectedBeforeFirstSelector() throws Exception {
175 180 writeSettings("variant-sources-late-allow");
176 181 writeBuildFile("""
177 182 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
178 183
179 184 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
180 185 variantsExt.layers.create('main')
181 186 variantsExt.roles.create('production')
182 187 variantsExt.variant('browser') {
183 188 role('production') { layers('main') }
184 189 }
185 190
186 191 variantSources {
187 192 lateConfigurationPolicy {
188 193 allowLateConfiguration()
189 194 }
190 195 }
191 196
192 197 afterEvaluate {
193 198 variantSources.whenAvailable { ctx ->
194 199 def browser = ctx.variants.variants.find { it.name == 'browser' }
195 200 def mainLayer = ctx.variants.layers.find { it.name == 'main' }
196 201 def unit = ctx.compileUnits.requireUnit(browser, mainLayer)
197 202
198 203 def unitSourceSet = ctx.sourceSets.getSourceSet(unit).get()
199 204 variantSources.layer('main') {
200 205 sourceSet {
201 206 declareOutputs('late')
202 207 }
203 208 }
204 209 unitSourceSet.output('late')
205 210 println('lateAllowed=ok')
206 211 }
207 212 }
208 213 """);
209 214
210 215 BuildResult result = runner("help").build();
211 216 assertTrue(result.getOutput().contains("lateAllowed=ok"));
212 217 }
213 218
214 219 @Test
215 220 void warnsAndAppliesLateConfigurationWhenWarnModeSelected() throws Exception {
216 221 writeSettings("variant-sources-late-warn");
217 222 writeBuildFile("""
218 223 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
219 224
220 225 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
221 226 variantsExt.layers.create('main')
222 227 variantsExt.roles.create('production')
223 228 variantsExt.variant('browser') {
224 229 role('production') { layers('main') }
225 230 }
226 231
227 232 variantSources {
228 233 lateConfigurationPolicy {
229 234 warnOnLateConfiguration()
230 235 }
231 236 }
232 237
233 238 afterEvaluate {
234 239 variantSources.whenAvailable { ctx ->
235 240 def browser = ctx.variants.variants.find { it.name == 'browser' }
236 241 def mainLayer = ctx.variants.layers.find { it.name == 'main' }
237 242 def unit = ctx.compileUnits.requireUnit(browser, mainLayer)
238 243
239 244 def unitSourceSet = ctx.sourceSets.getSourceSet(unit).get()
240 245 variantSources.layer('main') {
241 246 sourceSet {
242 247 declareOutputs('late')
243 248 }
244 249 }
245 250 unitSourceSet.output('late')
246 251 println('lateWarn=ok')
247 252 }
248 253 }
249 254 """);
250 255
251 256 BuildResult result = runner("help").build();
252 257
253 258 assertTrue(result.getOutput().contains("Source sets for [layer=main] layer already materialized"));
254 259 assertTrue(result.getOutput().contains("lateWarn=ok"));
255 260 }
256 261
257 262 @Test
258 263 void rejectsChangingLateConfigurationPolicyAfterFirstSelector() throws Exception {
259 264 writeSettings("variant-sources-late-policy-fixed");
260 265 writeBuildFile("""
261 266 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
262 267
263 268 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
264 269 variantsExt.layers.create('main')
265 270 variantsExt.roles.create('production')
266 271 variantsExt.variant('browser') {
267 272 role('production') { layers('main') }
268 273 }
269 274
270 275 variantSources {
271 276 variant('browser') {
272 277 sourceSet {
273 278 declareOutputs('js')
274 279 }
275 280 }
276 281 lateConfigurationPolicy {
277 282 allowLateConfiguration()
278 283 }
279 284 }
280 285 """);
281 286
282 287 assertBuildFails("Lazy configuration policy already applied", "help");
283 288 }
284 289
285 290 @Test
286 291 void failsOnProjectedNameCollisionByDefault() throws Exception {
287 292 writeSettings("variant-sources-name-collision-fail");
288 293 writeBuildFile("""
289 294 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
290 295
291 296 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
292 297 variantsExt.layers.create('variantBar')
293 298 variantsExt.layers.create('bar')
294 299 variantsExt.roles.create('production')
295 300
296 301 variantsExt.variant('foo') {
297 302 role('production') { layers('variantBar') }
298 303 }
299 304 variantsExt.variant('fooVariant') {
300 305 role('production') { layers('bar') }
301 306 }
302 307 """);
303 308
304 309 assertBuildFails("The same source set names are produced by different compile units", "help");
305 310 }
306 311
307 312 @Test
308 313 void resolvesProjectedNameCollisionDeterministicallyWhenConfigured() throws Exception {
309 314 writeSettings("variant-sources-name-collision-resolve");
310 315 writeBuildFile("""
311 316 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
312 317
313 318 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
314 319 variantsExt.layers.create('variantBar')
315 320 variantsExt.layers.create('bar')
316 321 variantsExt.roles.create('production')
317 322
318 323 variantsExt.variant('foo') {
319 324 role('production') { layers('variantBar') }
320 325 }
321 326 variantsExt.variant('fooVariant') {
322 327 role('production') { layers('bar') }
323 328 }
324 329
325 330 variantSources {
326 331 namingPolicy {
327 332 resolveNameCollision()
328 333 }
329 334 }
330 335
331 336 afterEvaluate {
332 337 variantSources.whenAvailable { ctx ->
333 338 def foo = ctx.variants.variants.find { it.name == 'foo' }
334 339 def fooVariant = ctx.variants.variants.find { it.name == 'fooVariant' }
335 340 def variantBar = ctx.variants.layers.find { it.name == 'variantBar' }
336 341 def bar = ctx.variants.layers.find { it.name == 'bar' }
337 342
338 343 def later = ctx.compileUnits.requireUnit(fooVariant, bar)
339 344 def earlier = ctx.compileUnits.requireUnit(foo, variantBar)
340 345
341 346 println("map1=" + later.variant().name + ":" + later.layer().name + "->" + ctx.sourceSets.getSourceSet(later).name)
342 347 println("map2=" + earlier.variant().name + ":" + earlier.layer().name + "->" + ctx.sourceSets.getSourceSet(earlier).name)
343 348 }
344 349 }
345 350 """);
346 351
347 352 BuildResult result = runner("help").build();
348 353
349 354 assertTrue(result.getOutput().contains("map1=fooVariant:bar->fooVariantBar2"));
350 355 assertTrue(result.getOutput().contains("map2=foo:variantBar->fooVariantBar"));
351 356 }
352 357
353 358 @Test
354 359 void failsOnUnknownVariantSelectorTarget() throws Exception {
355 360 writeSettings("variant-sources-missing-variant");
356 361 writeBuildFile("""
357 362 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
358 363
359 364 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
360 365 variantsExt.layers.create('main')
361 366 variantsExt.roles.create('production')
362 367 variantsExt.variant('browser') {
363 368 role('production') { layers('main') }
364 369 }
365 370
366 371 variantSources {
367 372 variant('missing') {
368 373 sourceSet {
369 374 declareOutputs('js')
370 375 }
371 376 }
372 377 }
373 378 """);
374 379
375 assertBuildFails("Variant 'missing' isn't declared", "help");
380 assertBuildFails("The specified variant 'missing' isn't declared", "help");
376 381 }
377 382
378 383 @Test
379 384 void failsOnUnknownVariantContextSelectorTarget() throws Exception {
380 385 writeSettings("variant-sources-context-missing-variant");
381 386 writeBuildFile("""
382 387 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
383 388
384 389 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
385 390 variantsExt.layers.create('main')
386 391 variantsExt.roles.create('production')
387 392 variantsExt.variant('browser') {
388 393 role('production') { layers('main') }
389 394 }
390 395
391 396 variantSources.whenAvailable { ctx ->
392 397 def missing = objects.named(org.implab.gradle.variants.core.Variant, 'missing')
393 398 ctx.configureVariant(missing) {
394 399 sourceSet {
395 400 declareOutputs('js')
396 401 }
397 402 }
398 403 }
399 404 """);
400 405
401 406 assertBuildFails("The specified variant 'missing' isn't declared", "help");
402 407 }
403 408
404 409 @Test
405 410 void failsOnUnknownLayerSelectorTarget() throws Exception {
406 411 writeSettings("variant-sources-missing-layer");
407 412 writeBuildFile("""
408 413 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
409 414
410 415 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
411 416 variantsExt.layers.create('main')
412 417 variantsExt.roles.create('production')
413 418 variantsExt.variant('browser') {
414 419 role('production') { layers('main') }
415 420 }
416 421
417 422 variantSources {
418 423 layer('missing') {
419 424 sourceSet {
420 425 declareOutputs('js')
421 426 }
422 427 }
423 428 }
424 429 """);
425 430
426 assertBuildFails("Layer 'missing' isn't declared", "help");
431 assertBuildFails("The specified layer 'missing' isn't declared", "help");
427 432 }
428 433
429 434 @Test
430 435 void failsOnUnknownLayerContextSelectorTarget() throws Exception {
431 436 writeSettings("variant-sources-context-missing-layer");
432 437 writeBuildFile("""
433 438 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
434 439
435 440 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
436 441 variantsExt.layers.create('main')
437 442 variantsExt.roles.create('production')
438 443 variantsExt.variant('browser') {
439 444 role('production') { layers('main') }
440 445 }
441 446
442 447 variantSources.whenAvailable { ctx ->
443 448 def missing = objects.named(org.implab.gradle.variants.core.Layer, 'missing')
444 449 ctx.configureLayer(missing) {
445 450 sourceSet {
446 451 declareOutputs('js')
447 452 }
448 453 }
449 454 }
450 455 """);
451 456
452 457 assertBuildFails("The specified layer 'missing' isn't declared", "help");
453 458 }
454 459
455 460 @Test
456 461 void failsOnUndeclaredCompileUnitSelectorTarget() throws Exception {
457 462 writeSettings("variant-sources-missing-unit");
458 463 writeBuildFile("""
459 464 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
460 465
461 466 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
462 467 variantsExt.layers.create('main')
463 468 variantsExt.layers.create('test')
464 469 variantsExt.roles.create('production')
465 470 variantsExt.variant('browser') {
466 471 role('production') { layers('main') }
467 472 }
468 473
469 474 variantSources {
470 475 unit('browser', 'test') {
471 476 sourceSet {
472 477 declareOutputs('js')
473 478 }
474 479 }
475 480 }
476 481 """);
477 482
478 assertBuildFails("The CompileUnit isn't declared for variant 'browser', layer 'test'", "help");
483 assertBuildFails("Compile unit for variant 'browser' and layer 'test' not found", "help");
479 484 }
480 485
481 486 @Test
482 487 void failsOnUndeclaredCompileUnitContextSelectorTarget() throws Exception {
483 488 writeSettings("variant-sources-context-missing-unit");
484 489 writeBuildFile("""
485 490 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
486 491
487 492 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
488 493 variantsExt.layers.create('main')
489 494 variantsExt.layers.create('test')
490 495 variantsExt.roles.create('production')
491 496 variantsExt.variant('browser') {
492 497 role('production') { layers('main') }
493 498 }
494 499
495 500 variantSources.whenAvailable { ctx ->
496 501 def browser = ctx.variants.variants.find { it.name == 'browser' }
497 502 def testLayer = ctx.variants.layers.find { it.name == 'test' }
498 503 def unit = new org.implab.gradle.variants.sources.CompileUnit(browser, testLayer)
499 504 ctx.configureUnit(unit) {
500 505 sourceSet {
501 506 declareOutputs('js')
502 507 }
503 508 }
504 509 }
505 510 """);
506 511
507 512 assertBuildFails("Compile unit for variant 'browser' and layer 'test' not found", "help");
508 513 }
509 514
510 515 @Test
511 516 void failsOnUndeclaredCompileUnitMaterializerTarget() throws Exception {
512 517 writeSettings("variant-sources-materializer-missing-unit");
513 518 writeBuildFile("""
514 519 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
515 520
516 521 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
517 522 variantsExt.layers.create('main')
518 523 variantsExt.layers.create('test')
519 524 variantsExt.roles.create('production')
520 525 variantsExt.variant('browser') {
521 526 role('production') { layers('main') }
522 527 }
523 528
524 529 variantSources.whenAvailable { ctx ->
525 530 def browser = ctx.variants.variants.find { it.name == 'browser' }
526 531 def testLayer = ctx.variants.layers.find { it.name == 'test' }
527 532 def unit = new org.implab.gradle.variants.sources.CompileUnit(browser, testLayer)
528 533 ctx.sourceSets.getSourceSet(unit)
529 534 }
530 535 """);
531 536
532 537 assertBuildFails("Compile unit for variant 'browser' and layer 'test' not found", "help");
533 538 }
534 539
535 540 @Test
536 541 void rejectsChangingNamingPolicyAfterContextBecomesObservable() throws Exception {
537 542 writeSettings("variant-sources-name-policy-fixed");
538 543 writeBuildFile("""
539 544 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
540 545
541 546 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
542 547 variantsExt.layers.create('main')
543 548 variantsExt.roles.create('production')
544 549 variantsExt.variant('browser') {
545 550 role('production') { layers('main') }
546 551 }
547 552
548 553 variantSources.whenAvailable {
549 554 variantSources.namingPolicy {
550 555 resolveNameCollision()
551 556 }
552 557 }
553 558 """);
554 559
555 560 assertBuildFails("Naming policy already applied", "help");
556 561 }
557 562
558 563 @Test
559 564 void rejectsChangingLateConfigurationPolicyAfterContextBecomesObservable() throws Exception {
560 565 writeSettings("variant-sources-late-policy-context-fixed");
561 566 writeBuildFile("""
562 567 apply plugin: org.implab.gradle.variants.VariantSourcesPlugin
563 568
564 569 def variantsExt = extensions.getByType(org.implab.gradle.variants.core.VariantsExtension)
565 570 variantsExt.layers.create('main')
566 571 variantsExt.roles.create('production')
567 572 variantsExt.variant('browser') {
568 573 role('production') { layers('main') }
569 574 }
570 575
571 576 variantSources.whenAvailable {
572 577 variantSources.lateConfigurationPolicy {
573 578 allowLateConfiguration()
574 579 }
575 580 }
576 581 """);
577 582
578 583 assertBuildFails("Lazy configuration policy already applied", "help");
579 584 }
580 585 }
General Comments 0
You need to be logged in to leave comments. Login now