##// 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 }
@@ -4,6 +4,7
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
@@ -6,6 +6,7 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 /**
@@ -16,12 +17,27 import org.implab.gradle.variants.source
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
@@ -32,7 +48,8 public abstract class SourcesPlugin impl
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());
@@ -21,6 +21,7 import org.implab.gradle.variants.source
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) {
@@ -105,7 +106,7 public abstract class VariantArtifactsPl
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
@@ -1,12 +1,9
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;
@@ -14,13 +11,13 import org.implab.gradle.common.core.lan
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;
@@ -32,6 +29,10 import org.implab.gradle.variants.source
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();
@@ -40,7 +41,8 public abstract class VariantSourcesPlug
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
@@ -53,25 +55,31 public abstract class VariantSourcesPlug
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 .addUnits(compileUnits.getUnits())
63 .nameCollisionPolicy(namingPolicy.policy())
64 .build();
64 .addUnits(compileUnits.getUnits())
65 .nameCollisionPolicy(namingPolicy.policy())
66 .build();
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);
65 74
66 75 // create the context
67 76 var context = new DefaultVariantSourcesContext(
68 variants,
69 compileUnits,
70 roleProjections,
71 compileUnitNamer,
72 sourceSetRegistry,
73 sourceSetConfiguration
74 );
77 variants,
78 compileUnits,
79 roleProjections,
80 compileUnitNamer,
81 sourceSetRegistry,
82 sourceSetConfiguration);
75 83 deferred.resolve(context);
76 84 });
77 85
@@ -98,7 +106,9 public abstract class VariantSourcesPlug
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
@@ -109,7 +119,9 public abstract class VariantSourcesPlug
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
@@ -120,7 +132,11 public abstract class VariantSourcesPlug
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
@@ -136,31 +152,4 public abstract class VariantSourcesPlug
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 }
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 155 }
@@ -23,13 +23,15 import org.implab.gradle.variants.intern
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 }
@@ -1,7 +1,6
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;
@@ -22,7 +21,6 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;
@@ -33,9 +31,10 import org.gradle.api.tasks.TaskProvider
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>
@@ -62,7 +61,7 public abstract class GenericSourceSet i
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
@@ -75,14 +74,6 public abstract class GenericSourceSet i
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
@@ -91,14 +82,24 public abstract class GenericSourceSet i
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
@@ -63,6 +63,8 class PluginMarkerFunctionalTest extends
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 }
@@ -71,6 +73,8 class PluginMarkerFunctionalTest extends
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
@@ -40,12 +40,15 class VariantSourcesPluginFunctionalTest
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
@@ -67,6 +70,8 class VariantSourcesPluginFunctionalTest
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 }
@@ -372,7 +377,7 class VariantSourcesPluginFunctionalTest
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
@@ -423,7 +428,7 class VariantSourcesPluginFunctionalTest
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
@@ -475,7 +480,7 class VariantSourcesPluginFunctionalTest
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
General Comments 0
You need to be logged in to leave comments. Login now