##// END OF EJS Templates
WIP variant artifacts DSL, FilePaths traits
cin -
r50:ca3982e55d9e default
parent child
Show More
@@ -0,0 +1,75
1 package org.implab.gradle.common.core.lang;
2
3 import java.nio.file.Path;
4 import java.nio.file.Paths;
5 import java.util.LinkedList;
6 import java.util.List;
7 import java.util.Set;
8 import java.util.function.BiConsumer;
9 import java.util.function.BinaryOperator;
10 import java.util.function.Function;
11 import java.util.function.Supplier;
12 import java.util.stream.Collector;
13 import java.util.stream.Stream;
14
15 public final class FilePaths {
16 private static PathCollector defaultPathCollector = new PathCollector();
17
18 private FilePaths() {
19 }
20
21 public static String cat(String fist, String... extra) {
22 return catPath(fist, extra).toString();
23 }
24
25 public static Path catPath(String first, String ...extra) {
26 return Stream.concat(Stream.of(first), Stream.of(extra))
27 .collect(asPath());
28 }
29
30 public static Collector<String, List<String>, Path> asPath() {
31 return defaultPathCollector;
32 }
33
34 static class PathCollector implements Collector<String, List<String>, Path> {
35
36 private final Function<String, String> sanitizer;
37
38 public PathCollector() {
39 sanitizer = Strings::sanitizeFileName;
40 }
41
42 @Override
43 public BiConsumer<List<String>, String> accumulator() {
44 return (components, item) -> components.add(sanitizer.apply(item));
45 }
46
47 @Override
48 public Set<Characteristics> characteristics() {
49 return Set.of();
50 }
51
52 @Override
53 public BinaryOperator<List<String>> combiner() {
54 return (first, extra) -> {
55 first.addAll(extra);
56 return first;
57 };
58 }
59
60 @Override
61 public Function<List<String>, Path> finisher() {
62 return components -> Paths.get(
63 components.get(0),
64 components.subList(1, components.size())
65 .toArray(String[]::new));
66
67 }
68
69 @Override
70 public Supplier<List<String>> supplier() {
71 return () -> new LinkedList<>();
72 }
73
74 }
75 }
@@ -0,0 +1,100
1 package org.implab.gradle.variants.artifacts.internal;
2
3 import java.util.HashMap;
4 import java.util.Map;
5
6 import org.gradle.api.Action;
7 import org.gradle.api.file.Directory;
8 import org.gradle.api.file.DirectoryProperty;
9 import org.gradle.api.model.ObjectFactory;
10 import org.gradle.api.provider.Provider;
11 import org.gradle.api.tasks.Copy;
12 import org.gradle.api.tasks.TaskContainer;
13 import org.gradle.language.base.plugins.LifecycleBasePlugin;
14 import org.implab.gradle.common.core.lang.FilePaths;
15 import org.implab.gradle.common.core.lang.Strings;
16 import org.implab.gradle.variants.artifacts.ArtifactAssemblyRegistry;
17 import org.implab.gradle.variants.artifacts.ArtifactSlot;
18 import org.implab.gradle.variants.artifacts.OutgoingVariant;
19 import org.implab.gradle.variants.artifacts.VariantArtifactsSpec;
20 import org.implab.gradle.variants.sources.CompileUnitsView;
21 import org.implab.gradle.variants.sources.RoleProjectionsView;
22 import org.implab.gradle.variants.sources.SourceSetMaterializer;
23
24 public class ArtifactConfigurationHandler {
25 private final ArtifactAssemblyRegistry registry;
26
27 private final CompileUnitsView compileUnitsView;
28
29 private final RoleProjectionsView roleProjectionsView;
30
31 private final SourceSetMaterializer sourceSetMaterializer;
32
33 private final Map<ArtifactSlot, SlotContributionVisitor> visitors = new HashMap<>();
34
35 private final ObjectFactory objectFactory;
36
37 private final DirectoryProperty assembliesDirectory;
38
39 private final TaskContainer tasks;
40
41 public ArtifactConfigurationHandler(
42 ArtifactAssemblyRegistry registry,
43 CompileUnitsView compileUnitsView,
44 RoleProjectionsView roleProjectionsView,
45 SourceSetMaterializer sourceSetMaterializer,
46 ObjectFactory objectFactory,
47 TaskContainer tasks) {
48 this.registry = registry;
49 this.compileUnitsView = compileUnitsView;
50 this.roleProjectionsView = roleProjectionsView;
51 this.sourceSetMaterializer = sourceSetMaterializer;
52 this.objectFactory = objectFactory;
53 this.assembliesDirectory = objectFactory.directoryProperty();
54 this.tasks = tasks;
55 }
56
57 public SlotContributionVisitor slotAssembler(ArtifactSlot artifactSlot) {
58 return visitors.computeIfAbsent(artifactSlot, this::newVisitor);
59 }
60
61 public void configureVariant(OutgoingVariant outgoingVariant, Action<? super VariantArtifactsSpec> action) {
62 var spec = new DefaultVariantArtifactSpec(outgoingVariant, objectFactory, this);
63
64 action.execute(spec);
65 }
66
67 private SlotContributionVisitor newVisitor(ArtifactSlot artifactSlot) {
68 var fileCollection = objectFactory.fileCollection();
69 var outputDirectory = outputDirectory(artifactSlot);
70
71 var task = tasks.register(assembleTaskName(artifactSlot), Copy.class, copy -> {
72 copy.setGroup(LifecycleBasePlugin.BUILD_GROUP);
73 copy.into(outputDirectory);
74 copy.from(fileCollection);
75 });
76
77 // рСгистрируСтся Π·Π°Π΄Π°Ρ‡Π° ΠΈ Π°Ρ€Π΅Ρ„Π°ΠΊΡ‚ сборки слота
78 registry.register(artifactSlot, task, t -> outputDirectory);
79
80 return new SlotInputsAssembler(
81 artifactSlot,
82 fileCollection,
83 compileUnitsView,
84 roleProjectionsView,
85 sourceSetMaterializer);
86 }
87
88 private String assembleTaskName(ArtifactSlot artifactSlot) {
89 return "assemble"
90 + Strings.capitalize(artifactSlot.variant().getName())
91 + Strings.capitalize(artifactSlot.slot().getName());
92 }
93
94 private Provider<Directory> outputDirectory(ArtifactSlot artifactSlot) {
95 return assembliesDirectory.dir(
96 FilePaths.cat(
97 artifactSlot.variant().getName(),
98 artifactSlot.slot().getName()));
99 }
100 }
@@ -0,0 +1,51
1 package org.implab.gradle.variants.artifacts.internal;
2
3 import org.gradle.api.Action;
4 import org.gradle.api.model.ObjectFactory;
5 import org.implab.gradle.variants.artifacts.ArtifactAssemblySpec;
6 import org.implab.gradle.variants.artifacts.ArtifactSlot;
7 import org.implab.gradle.variants.artifacts.OutgoingVariant;
8 import org.implab.gradle.variants.artifacts.Slot;
9 import org.implab.gradle.variants.artifacts.VariantArtifactsSpec;
10
11 class DefaultVariantArtifactSpec implements VariantArtifactsSpec {
12
13 private final ObjectFactory objectFactory;
14 private final ArtifactConfigurationHandler assemblyBuilder;
15
16 private final OutgoingVariant outgoingVariant;
17
18 DefaultVariantArtifactSpec(
19 OutgoingVariant outgoingVariant,
20 ObjectFactory objectFactory,
21 ArtifactConfigurationHandler assemblyBuilder) {
22 this.objectFactory = objectFactory;
23 this.assemblyBuilder = assemblyBuilder;
24 this.outgoingVariant = outgoingVariant;
25 }
26
27 @Override
28 public void slot(String name, Action<? super ArtifactAssemblySpec> action) {
29 var slot = outgoingVariant.getSlots().maybeCreate(name);
30
31 configureSlot(slot, action);
32 }
33
34 @Override
35 public void primarySlot(String name, Action<? super ArtifactAssemblySpec> action) {
36 var slot = outgoingVariant.getSlots().maybeCreate(name);
37 outgoingVariant.getPrimarySlot().set(slot);
38
39 configureSlot(slot, action);
40 }
41
42 private void configureSlot(Slot slot, Action<? super ArtifactAssemblySpec> action) {
43 var artifactSlot = new ArtifactSlot(outgoingVariant.getVariant(), slot);
44
45 var inputsAssembler = assemblyBuilder.slotAssembler(artifactSlot);
46
47 var spec = new DefaultArtifactAssemblySpec(objectFactory, inputsAssembler.consumer());
48 action.execute(spec);
49 }
50
51 }
@@ -1,70 +1,149
1 1 package org.implab.gradle.common.core.lang;
2 2
3 import java.util.function.Consumer;
4 import java.util.function.Function;
3 5 import java.util.regex.Pattern;
4 6
5 7 import org.eclipse.jdt.annotation.NonNullByDefault;
6 8 import org.gradle.api.provider.Provider;
7 9
8 10 @NonNullByDefault
9 11 public class Strings {
10 12
13 private static final boolean[] ALLOWED_FILE_NAME_CHAR = new boolean[128];
14
15 private static final char[] HEX_DIGITS = "0123456789ABCDEF".toCharArray();
16
11 17 private static final Pattern firstLetter = Pattern.compile("^\\w");
12 18
13 19 private static final Pattern INVALID_NAME_CHAR = Pattern.compile("[^A-Za-z0-9_.-]");
14 20
21 static {
22 for (char c = '0'; c <= '9'; c++)
23 ALLOWED_FILE_NAME_CHAR[c] = true;
24 for (char c = 'A'; c <= 'Z'; c++)
25 ALLOWED_FILE_NAME_CHAR[c] = true;
26 for (char c = 'a'; c <= 'z'; c++)
27 ALLOWED_FILE_NAME_CHAR[c] = true;
28 ALLOWED_FILE_NAME_CHAR['.'] = true;
29 ALLOWED_FILE_NAME_CHAR['_'] = true;
30 ALLOWED_FILE_NAME_CHAR['-'] = true;
31 }
32
15 33 public static String capitalize(String string) {
16 34 return string == null ? null
17 35 : string.length() == 0 ? string
18 36 : firstLetter.matcher(string).replaceFirst(m -> m.group().toUpperCase());
19 37 }
20 38
21 39 public static String toCamelCase(String name) {
22 40 if (name == null || name.isEmpty())
23 41 return name;
24 42 StringBuilder out = new StringBuilder(name.length());
25 43 boolean up = false;
26 44 boolean first = true;
27 45 for (int i = 0; i < name.length(); i++) {
28 46 char c = name.charAt(i);
29 47 switch (c) {
30 48 case '-', '_', ' ', '.' -> up = true;
31 49 default -> {
32 50 out.append(
33 51 first ? Character.toLowerCase(c)
34 52 : up ? Character.toUpperCase(c): c);
35 53 up = false;
36 54 first = false;
37 55 }
38 56 }
39 57 }
40 58 return out.toString();
41 59 }
42 60
43 61 public static void argumentNotNullOrEmpty(String value, String argumentName) {
44 62 if (value == null || value.length() == 0)
45 63 throw new IllegalArgumentException(String.format("Argument %s can't be null or empty", argumentName));
46 64 }
47 65
48 66 public static void argumentNotNullOrBlank(String value, String argumentName) {
49 67 if (value == null || value.trim().length() == 0)
50 68 throw new IllegalArgumentException(String.format("Argument %s can't be null or blank", argumentName));
51 69 }
52 70
53 71 public static String requireNonBlank(String value) {
54 72 argumentNotNullOrBlank(value, "value");
55 73 return value;
56 74 }
57 75
76 public static String requireNonEmpty(String value) {
77 argumentNotNullOrEmpty(value, "value");
78 return value;
79 }
80
81
58 82 public static String sanitizeName(String value) {
59 83 return INVALID_NAME_CHAR.matcher(value).replaceAll("_");
60 84 }
61 85
86 public static String sanitizeFileName(String value) {
87 int length = value.length();
88 for (int i = 0; i < length; i++) {
89 char c = value.charAt(i);
90 if (c >= ALLOWED_FILE_NAME_CHAR.length || !ALLOWED_FILE_NAME_CHAR[c])
91 return sanitizeFileName(value, i);
92 }
93 return value;
94 }
95
62 96 public static String asString(Object value) {
63 97 if (value == null)
64 98 return null;
65 99 if (value instanceof Provider<?> provider)
66 100 return asString(provider.get());
67 101 else
68 102 return value.toString();
69 103 }
104
105 private static String sanitizeFileName(String value, int invalidIndex) {
106 int length = value.length();
107 StringBuilder out = new StringBuilder(length + 16);
108 out.append(value, 0, invalidIndex);
109
110 for (int i = invalidIndex; i < length; i++) {
111 char c = value.charAt(i);
112 if (c < ALLOWED_FILE_NAME_CHAR.length && ALLOWED_FILE_NAME_CHAR[c]) {
113 out.append(c);
114 } else if (Character.isHighSurrogate(c) && i + 1 < length && Character.isLowSurrogate(value.charAt(i + 1))) {
115 appendUrlEncodedUtf8(out, Character.toCodePoint(c, value.charAt(++i)));
116 } else if (Character.isSurrogate(c)) {
117 appendUrlEncodedUtf8(out, 0xFFFD);
118 } else {
119 appendUrlEncodedUtf8(out, c);
120 }
121 }
122
123 return out.toString();
124 }
125
126 private static void appendUrlEncodedUtf8(StringBuilder out, int codePoint) {
127 if (codePoint <= 0x7F) {
128 appendUrlEncodedByte(out, codePoint);
129 } else if (codePoint <= 0x7FF) {
130 appendUrlEncodedByte(out, 0xC0 | (codePoint >>> 6));
131 appendUrlEncodedByte(out, 0x80 | (codePoint & 0x3F));
132 } else if (codePoint <= 0xFFFF) {
133 appendUrlEncodedByte(out, 0xE0 | (codePoint >>> 12));
134 appendUrlEncodedByte(out, 0x80 | ((codePoint >>> 6) & 0x3F));
135 appendUrlEncodedByte(out, 0x80 | (codePoint & 0x3F));
136 } else {
137 appendUrlEncodedByte(out, 0xF0 | (codePoint >>> 18));
138 appendUrlEncodedByte(out, 0x80 | ((codePoint >>> 12) & 0x3F));
139 appendUrlEncodedByte(out, 0x80 | ((codePoint >>> 6) & 0x3F));
140 appendUrlEncodedByte(out, 0x80 | (codePoint & 0x3F));
141 }
142 }
143
144 private static void appendUrlEncodedByte(StringBuilder out, int value) {
145 out.append('%');
146 out.append(HEX_DIGITS[(value >>> 4) & 0x0F]);
147 out.append(HEX_DIGITS[value & 0x0F]);
148 }
70 149 }
@@ -1,75 +1,78
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.ArtifactAssemblyRegistry;
8 8 import org.implab.gradle.variants.artifacts.OutgoingConfigurationSpec;
9 9 import org.implab.gradle.variants.artifacts.OutgoingVariantsContext;
10 10 import org.implab.gradle.variants.artifacts.VariantArtifactsExtension;
11 11 import org.implab.gradle.variants.artifacts.VariantArtifactsSpec;
12 12 import org.implab.gradle.variants.artifacts.internal.ArtifactAssemblyBridge;
13 import org.implab.gradle.variants.artifacts.internal.DefaultVariantArtifactSpec;
13 14 import org.implab.gradle.variants.artifacts.internal.OutgoingRegistry;
14 15 import org.implab.gradle.variants.core.Variant;
15 16 import org.implab.gradle.variants.core.VariantsExtension;
16 17
17 18 public abstract class VariantArtifactsPlugin implements Plugin<Project> {
18 19
19 20 @Override
20 21 public void apply(Project target) {
21 22 var extensions = target.getExtensions();
22 23 var objects = target.getObjects();
23 24 var providers = target.getProviders();
24 25 var configurations = target.getConfigurations();
25 26 var tasks = target.getTasks();
26 27
27 28 // Apply the main VariantsPlugin to ensure the core variant model is available.
28 29 target.getPlugins().apply(VariantsPlugin.class);
29 30 // Access the VariantsExtension to configure variant sources.
30 31 var variantsExtension = extensions.getByType(VariantsExtension.class);
31 32
32 33 var outgoing = new OutgoingRegistry(configurations, objects, providers);
33 34 var assemblies = new ArtifactAssemblyRegistry(objects, tasks);
34 35
36 // wire artifact assemblies to configuration variants
35 37 var assembliesBridge = new ArtifactAssemblyBridge(assemblies);
36 38
37 39 var deferred = new Deferred<OutgoingVariantsContext>();
38 40
39 41 deferred.whenResolved(context -> context.all(assembliesBridge));
40 42
41 43 variantsExtension.whenFinalized(variants -> {
42 44
43 45 });
44 46
45 47 var variantArtifacts = new VariantArtifactsExtension() {
46 48
47 49 @Override
48 50 public void variant(String variantName, Action<? super VariantArtifactsSpec> action) {
49 deferred.whenResolved(context -> registry.configureVariant(
50 objects.named(Variant.class, variantName), action));
51 deferred.whenResolved(context -> {
52 new DefaultVariantArtifactSpec();
53 });
51 54 }
52 55
53 56 @Override
54 57 public void whenFinalized(Action<? super OutgoingVariantsContext> action) {
55 58 deferred.whenResolved(registry -> action.execute(registry.variantsContext()));
56 59 }
57 60
58 61 @Override
59 62 public void whenOutgoingVariant(Action<? super OutgoingConfigurationSpec> action) {
60 63 deferred.whenResolved(registry -> registry.configureOutgoing(action));
61 64
62 65 }
63 66
64 67 @Override
65 68 public void whenOutgoingSlot(Action<? super OutgoingArtifactSlotSpec> action) {
66 69 deferred.whenResolved(registry -> registry.configureOutgoingSlot(action));
67 70 }
68 71
69 72 };
70 73
71 74 extensions.add(VariantArtifactsExtension.class, "variantArtifacts", variantArtifacts);
72 75
73 76 }
74 77
75 78 }
@@ -1,43 +1,37
1 1 package org.implab.gradle.variants.artifacts;
2 2
3 3 import org.gradle.api.Task;
4 import org.gradle.api.file.FileCollection;
5 4 import org.gradle.api.file.FileSystemLocation;
6 5 import org.gradle.api.provider.Provider;
7 6 import org.gradle.api.tasks.TaskProvider;
8 7
9 8 /**
10 9 * Materialized body of an {@link ArtifactSlot}.
11 10 *
12 11 * <p>
13 12 * An assembly is a stateful build object obtained on demand from
14 13 * {@link ArtifactAssemblies#require(ArtifactSlot)}. It describes how the
15 14 * slot artifact is produced and
16 15 * where that single published artifact will appear.
17 16 */
18 17 public interface ArtifactAssembly {
19 18
20 19 /**
21 20 * Returns the published artifact produced for the slot.
22 21 *
23 22 * <p>
24 23 * A slot is expected to produce exactly one artifact represented by one file or
25 24 * one directory.
26 25 *
27 26 * @return provider of the produced artifact location
28 27 */
29 28 Provider<? extends FileSystemLocation> getArtifact();
30 29
31 30 /**
32 31 * Returns the task that assembles the slot artifact.
33 32 *
34 33 * @return provider of the assembly task
35 34 */
36 35 TaskProvider<? extends Task> getAssemblyTask();
37 36
38 /**
39 * File collection, contains {@link #getArtifact()} and build dependency on
40 * {@link #getAssemblyTask()}. This is a conventional property.
41 */
42 FileCollection getFileCollection();
43 37 }
@@ -1,124 +1,87
1 1 package org.implab.gradle.variants.artifacts;
2 2
3 3 import java.util.LinkedHashMap;
4 4 import java.util.Map;
5 5 import java.util.Optional;
6 6 import java.util.function.Function;
7 7
8 8 import org.eclipse.jdt.annotation.NonNullByDefault;
9 9 import org.gradle.api.Action;
10 10 import org.gradle.api.InvalidUserDataException;
11 11 import org.gradle.api.Task;
12 import org.gradle.api.file.Directory;
13 import org.gradle.api.file.FileCollection;
14 12 import org.gradle.api.file.FileSystemLocation;
15 import org.gradle.api.model.ObjectFactory;
16 13 import org.gradle.api.provider.Provider;
17 import org.gradle.api.tasks.Copy;
18 import org.gradle.api.tasks.TaskContainer;
19 14 import org.gradle.api.tasks.TaskProvider;
20 import org.gradle.language.base.plugins.LifecycleBasePlugin;
21 15 import org.implab.gradle.common.core.lang.Deferred;
22 16 import org.implab.gradle.internal.ReplayableQueue;
23 17
24 18 @NonNullByDefault
25 19 public class ArtifactAssemblyRegistry implements ArtifactAssemblies {
26 private final ObjectFactory objects;
27 private final TaskContainer tasks;
28 20 private final Map<ArtifactSlot, Deferred<ArtifactAssembly>> assembliesBySlots = new LinkedHashMap<>();
29 21 private final ReplayableQueue<ArtifactAssembly> assemblies = new ReplayableQueue<>();
30 22
31 public ArtifactAssemblyRegistry(ObjectFactory objects, TaskContainer tasks) {
32 this.objects = objects;
33 this.tasks = tasks;
34 }
35
36 public ArtifactAssembly register(
37 ArtifactSlot slot,
38 String taskName,
39 Provider<Directory> outputDirectory,
40 FileCollection sources) {
41
42 var task = tasks.register(taskName, Copy.class, copy -> {
43 copy.setGroup(LifecycleBasePlugin.BUILD_GROUP);
44 copy.into(outputDirectory);
45 copy.from(sources);
46 });
47
48 return register(slot, task, t -> outputDirectory);
23 public ArtifactAssemblyRegistry() {
49 24 }
50 25
51 26 public <T extends Task> ArtifactAssembly register(
52 27 ArtifactSlot slot,
53 28 TaskProvider<T> task,
54 29 Function<? super T, ? extends Provider<? extends FileSystemLocation>> mapOutputArtifact) {
55 30
56 31 var deferred = getDeferred(slot);
57 32 if (deferred.resolved()) {
58 33 throw new InvalidUserDataException("Artifact assembly '" + slot + "' is already registered");
59 34 }
60 35 var outputArtifact = task.flatMap(mapOutputArtifact::apply);
61 36
62 var output = objects.fileCollection()
63 .from(outputArtifact)
64 .builtBy(task);
65
66 var assembly = new Assembly(outputArtifact, task, output);
37 var assembly = new Assembly(outputArtifact, task);
67 38 deferred.resolve(assembly);
68 39 assemblies.add(assembly);
69 40 return assembly;
70 41 }
71 42
72 43 @Override
73 44 public Optional<ArtifactAssembly> find(ArtifactSlot slot) {
74 45 // to prevent creation of map entries on lookup use the map directly
75 46 var deferred = assembliesBySlots.get(slot);
76 47 return deferred != null && deferred.resolved()
77 48 ? Optional.of(deferred.value())
78 49 : Optional.empty();
79 50 }
80 51
81 52 @Override
82 53 public void when(ArtifactSlot slot, Action<? super ArtifactAssembly> action) {
83 54 getDeferred(slot).whenResolved(action::execute);
84 55 }
85 56
86 57 @Override
87 58 public void all(Action<? super ArtifactAssembly> action) {
88 59 assemblies.forEach(action::execute);
89 60 }
90 61
91 62 private Deferred<ArtifactAssembly> getDeferred(ArtifactSlot slot) {
92 63 return assembliesBySlots.computeIfAbsent(slot, k -> new Deferred<>());
93 64 }
94 65
95 66 static class Assembly implements ArtifactAssembly {
96 67
97 68 private final Provider<? extends FileSystemLocation> artifact;
98 69 private final TaskProvider<? extends Task> task;
99 private final FileCollection fileCollection;
100 70
101 Assembly(Provider<? extends FileSystemLocation> artifact, TaskProvider<? extends Task> task,
102 FileCollection fileCollection) {
71 Assembly(Provider<? extends FileSystemLocation> artifact, TaskProvider<? extends Task> task) {
103 72 this.artifact = artifact;
104 73 this.task = task;
105 this.fileCollection = fileCollection;
106 74 }
107 75
108 76 @Override
109 77 public Provider<? extends FileSystemLocation> getArtifact() {
110 78 return artifact;
111 79 }
112 80
113 81 @Override
114 82 public TaskProvider<? extends Task> getAssemblyTask() {
115 83 return task;
116 84 }
117 85
118 @Override
119 public FileCollection getFileCollection() {
120 return fileCollection;
121 }
122
123 86 }
124 87 }
@@ -1,82 +1,77
1 1 package org.implab.gradle.variants.artifacts.internal;
2 2
3 3 import java.util.HashSet;
4 import java.util.LinkedList;
5 import java.util.List;
6 4 import java.util.Set;
7 5 import java.util.function.Consumer;
8 6 import java.util.stream.Stream;
9 7
10 8 import org.gradle.api.Action;
11 9 import org.gradle.api.model.ObjectFactory;
12 10 import org.implab.gradle.common.core.lang.Strings;
13 11 import org.implab.gradle.variants.artifacts.ArtifactAssemblySpec;
14 12 import org.implab.gradle.variants.core.Layer;
15 13 import org.implab.gradle.variants.core.Role;
16 14 import org.implab.gradle.variants.artifacts.OutputSelectionSpec;
17 15
18 16 /**
19 17 * РСализация DSL ΠΌΠΎΠ΄Π΅Π»ΠΈ, строит Π½Π°Π±ΠΎΡ€ {@link SlotContribution}. ΠŸΡ€ΠΈ построСнии Π½Π°Π±ΠΎΡ€Π°
20 18 * ΠΏΡ€Π°Π²ΠΈΠ»Π° ΠΈ ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚Π½ΠΎΡΡ‚ΡŒ Π½Π΅ ΠΏΡ€ΠΎΠ²Π΅Ρ€ΡΡŽΡ‚ΡΡ. По ΠΎΠΊΠΎΠ½Ρ‡Π°Π½ΠΈΠΈ использования ΠΊΠ»ΠΈΠ΅Π½Ρ‚
21 19 * Π²Ρ‹Π·Ρ‹Π²Π°Π΅Ρ‚ ΠΌΠ΅Ρ‚ΠΎΠ΄ {@link #process(Consumer)} для ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ΠΎΠ².
22 20 *
23 21 */
24 final class BoundArtifactAssemblySpec implements ArtifactAssemblySpec {
25 private final List<SlotContribution> contributions = new LinkedList<>();
22 final class DefaultArtifactAssemblySpec implements ArtifactAssemblySpec {
23 private final Consumer<? super SlotContribution> consumer;
26 24 private final ObjectFactory objectFactory;
27 25
28 BoundArtifactAssemblySpec(ObjectFactory objectFactory) {
26 DefaultArtifactAssemblySpec(ObjectFactory objectFactory, Consumer<? super SlotContribution> consumer) {
27 this.consumer = consumer;
29 28 this.objectFactory = objectFactory;
30 29 }
31 30
32 31 @Override
33 32 public void from(Object artifact) {
34 contributions.add(new DirectContribution(artifact));
33 consumer.accept(new DirectContribution(artifact));
35 34 }
36 35
37 36 @Override
38 37 public void fromVariant(Action<? super OutputSelectionSpec> action) {
39 contributions.add(new VariantOutputsContribution(outputs(action)));
38 consumer.accept(new VariantOutputsContribution(outputs(action)));
40 39 }
41 40
42 41 @Override
43 42 public void fromRole(String roleName, Action<? super OutputSelectionSpec> action) {
44 43
45 contributions.add(new RoleOutputsContribution(
44 consumer.accept(new RoleOutputsContribution(
46 45 objectFactory.named(Role.class, roleName),
47 46 outputs(action)));
48 47 }
49 48
50 49 @Override
51 50 public void fromLayer(String layerName, Action<? super OutputSelectionSpec> action) {
52 contributions.add(new LayerOutputsContribution(
51 consumer.accept(new LayerOutputsContribution(
53 52 objectFactory.named(Layer.class, layerName),
54 53 outputs(action)));
55 54 }
56 55
57 void process(Consumer<? super SlotContribution> consumer) {
58 contributions.forEach(consumer);
59 }
60
61 56 private static Set<String> outputs(Action<? super OutputSelectionSpec> action) {
62 57 var spec = new OutputsSetSpec();
63 58 action.execute(spec);
64 59 return spec.outputs();
65 60 }
66 61
67 62 private static class OutputsSetSpec implements OutputSelectionSpec {
68 63 private final Set<String> outputs = new HashSet<>();
69 64
70 65 @Override
71 66 public void output(String name, String... extra) {
72 67 Stream.concat(Stream.of(name), Stream.of(extra))
73 68 .map(Strings::requireNonBlank)
74 69 .forEach(outputs::add);
75 70 }
76 71
77 72 Set<String> outputs() {
78 73 return Set.copyOf(outputs);
79 74 }
80 75
81 76 }
82 77 }
@@ -1,55 +1,96
1 1 package org.implab.gradle.variants.artifacts.internal;
2 2
3 import java.util.Collection;
3 import java.util.HashSet;
4 4 import java.util.Set;
5 import java.util.function.Consumer;
6 import java.util.function.Function;
7 5
6 import org.gradle.api.file.ConfigurableFileCollection;
8 7 import org.implab.gradle.variants.artifacts.ArtifactSlot;
9 import org.implab.gradle.variants.core.VariantsView;
10 8 import org.implab.gradle.variants.sources.CompileUnit;
11 import org.implab.gradle.variants.sources.VariantSourcesContext;
9 import org.implab.gradle.variants.sources.CompileUnitsView;
10 import org.implab.gradle.variants.sources.RoleProjectionsView;
11 import org.implab.gradle.variants.sources.SourceSetMaterializer;
12 12
13 /**
14 * Π‘Π±ΠΎΡ€Ρ‰ΠΈΠΊ входящих элСмСнтов ΠΈΠ· Ρ€Π°Π·Π½Ρ‹Ρ… источников {@link SlotContribution}.
15 * Π₯Ρ€Π°Π½ΠΈΡ‚ {@link ConfigurableFileCollection} ΠΈ добавляСт Π² Π½Π΅Π³ΠΎ Π½ΠΎΠ²Ρ‹Π΅ элСмСнты,
16 * ΠΏΡ€ΠΈ этом дСлаСтся дСдупликация ΠΏΠΎ {@link SlotInputKey}.
17 *
18 */
13 19 public class SlotInputsAssembler implements SlotContributionVisitor {
14 20
15 VariantsView variantView;
16 VariantSourcesContext sources;
17 ArtifactSlot artifactSlot;
21 // sources context
22 private final CompileUnitsView compileUnitsView;
23
24 private final RoleProjectionsView roleProjectionsView;
25
26 private final SourceSetMaterializer sourceSetMaterializer;
27
28 // artifact slot for this assembly
29 private final ArtifactSlot artifactSlot;
30
31 // content for this assembly
32 private final ConfigurableFileCollection artifactInputs;
18 33
19 Set<SlotInputKey> seen;
34 // seen inputs, used for deduplication
35 private final Set<SlotInputKey> seen = new HashSet<>();
36
37 public SlotInputsAssembler(
38 ArtifactSlot artifactSlot,
39 ConfigurableFileCollection artifactInputs,
40 CompileUnitsView compileUnitsView,
41 RoleProjectionsView roleProjectionsView,
42 SourceSetMaterializer sourceSetMaterializer) {
43 this.compileUnitsView = compileUnitsView;
44 this.roleProjectionsView = roleProjectionsView;
45 this.sourceSetMaterializer = sourceSetMaterializer;
46 this.artifactSlot = artifactSlot;
47 this.artifactInputs = artifactInputs;
48 }
20 49
21 50 @Override
22 51 public void visit(DirectContribution contribution) {
23 52 contribute(
24 53 SlotInputKey.newUniqueKey("Direct input for " + artifactSlot),
25 54 contribution.input());
26 55 }
27 56
28 57 @Override
29 58 public void visit(VariantOutputsContribution contribution) {
30 sources.getCompileUnits().getUnitsForVariant(artifactSlot.variant()).stream()
59 var units = compileUnitsView.getUnitsForVariant(artifactSlot.variant());
60 contributeCompileUnits(units, contribution.outputs());
31 61 }
32 62
33 63 @Override
34 64 public void visit(RoleOutputsContribution contribution) {
35 // TODO Auto-generated method stub
36 throw new UnsupportedOperationException("Unimplemented method 'visit'");
65 var roleProjection = roleProjectionsView.requireProjection(artifactSlot.variant(),
66 contribution.role());
67 var units = roleProjectionsView.getUnits(roleProjection);
68
69 contributeCompileUnits(units, contribution.outputs());
70
37 71 }
38 72
39 73 @Override
40 74 public void visit(LayerOutputsContribution contribution) {
41 // TODO Auto-generated method stub
42 throw new UnsupportedOperationException("Unimplemented method 'visit'");
75 var unit = compileUnitsView.requireUnit(artifactSlot.variant(), contribution.layer());
76 contributeCompileUnits(Set.of(unit), contribution.outputs());
43 77 }
44 78
45 private void contribute(SlotInputKey key, Object input) {
46
79 private void contributeCompileUnits(Set<CompileUnit> units, Set<String> outputs) {
80 units.stream()
81 // expand variant compile units, make (compileUnit, outputName) pairs
82 .flatMap(unit -> outputs.stream()
83 .map(output -> new CompileUnitOutputKey(unit, output)))
84 .forEach(key -> contribute(
85 key,
86 sourceSetMaterializer.getSourceSet(key.unit())
87 .map(s -> s.output(key.outputName()))));
47 88 }
48 89
49 private Function<CompileUnit, Object> resolveOutputs(Collection<? extends String> outputs) {
50 return unit -> {
51
52 };
90 private void contribute(SlotInputKey key, Object resolvedInput) {
91 if (!seen.add(key))
92 return;
93 artifactInputs.from(resolvedInput);
53 94 }
54 95
55 96 }
@@ -1,83 +1,83
1 1 package org.implab.gradle.variants.sources;
2 2
3 3 import java.util.HashMap;
4 4 import java.util.Map;
5 5 import java.util.Objects;
6 6 import java.util.Optional;
7 7 import java.util.Set;
8 8 import java.util.stream.Collectors;
9 9
10 10 import org.implab.gradle.variants.core.Layer;
11 11 import org.implab.gradle.variants.core.Role;
12 12 import org.implab.gradle.variants.core.Variant;
13 13 import org.implab.gradle.variants.core.VariantsView;
14 14
15 15 public final class RoleProjectionsView {
16 16 private final VariantsView variants;
17 17
18 18 private final Map<Variant, Set<RoleProjection>> projectionsByVariant = new HashMap<>();
19 19
20 20 private RoleProjectionsView(VariantsView variants) {
21 21 this.variants = variants;
22 22 }
23 23
24 24 public Set<RoleProjection> getProjections() {
25 25 return variants.getEntries().stream()
26 26 .map(RoleProjection::of)
27 27 .collect(Collectors.toUnmodifiableSet());
28 28 }
29 29
30 30 public Set<RoleProjection> getProjectionsForVariant(Variant variant) {
31 31 Objects.requireNonNull(variant, "Variant can't be null");
32 32 return projectionsByVariant.computeIfAbsent(variant, key -> variants
33 33 .getEntriesForVariant(variant).stream()
34 34 .map(RoleProjection::of)
35 35 .collect(Collectors.toUnmodifiableSet()));
36 36 }
37 37
38 38 public Set<RoleProjection> getProjectionsForRole(Role role) {
39 39 Objects.requireNonNull(role, "Role can't be null");
40 40 return variants.getEntriesForRole(role).stream()
41 41 .map(RoleProjection::of)
42 42 .collect(Collectors.toUnmodifiableSet());
43 43 }
44 44
45 45 public Optional<RoleProjection> findProjection(Variant variant, Role role) {
46 46 Objects.requireNonNull(variant, "Variant can't be null");
47 47 Objects.requireNonNull(role, "Role can't be null");
48 48 return variants.getEntriesForVariant(variant).stream()
49 49 .filter(entry -> entry.role().equals(role))
50 50 .map(RoleProjection::of)
51 51 .findAny();
52 52 }
53 53
54 54 public boolean contains(Variant variant, Role role) {
55 55 return findProjection(variant, role).isPresent();
56 56 }
57 57
58 58 public Set<CompileUnit> getUnits(RoleProjection projection) {
59 59 Objects.requireNonNull(projection, "Role projection can't be null");
60 60 return variants.getEntriesForVariant(projection.variant()).stream()
61 61 .filter(entry -> entry.role().equals(projection.role()))
62 62 .map(CompileUnit::of)
63 63 .collect(Collectors.toUnmodifiableSet());
64 64
65 65 }
66 66
67 public RoleProjection getProjection(Variant variant, Role role) {
67 public RoleProjection requireProjection(Variant variant, Role role) {
68 68 return findProjection(variant, role)
69 69 .orElseThrow(() -> new IllegalArgumentException(
70 70 "Role projection for variant '" + variant.getName()
71 71 + "' and role '" + role.getName() + "' not found"));
72 72 }
73 73
74 74 public Set<Layer> getLayers(RoleProjection projection) {
75 75 return getUnits(projection).stream()
76 76 .map(CompileUnit::layer)
77 77 .collect(java.util.stream.Collectors.toUnmodifiableSet());
78 78 }
79 79
80 80 public static RoleProjectionsView of(VariantsView variantsView) {
81 81 return new RoleProjectionsView(variantsView);
82 82 }
83 83 } No newline at end of file
@@ -1,105 +1,105
1 1 package org.implab.gradle.variants.sources;
2 2
3 3 import static org.junit.jupiter.api.Assertions.assertEquals;
4 4 import static org.junit.jupiter.api.Assertions.assertThrows;
5 5 import static org.junit.jupiter.api.Assertions.assertTrue;
6 6
7 7 import java.lang.reflect.Constructor;
8 8 import java.util.Set;
9 9
10 10 import org.implab.gradle.variants.core.Layer;
11 11 import org.implab.gradle.variants.core.Role;
12 12 import org.implab.gradle.variants.core.Variant;
13 13 import org.implab.gradle.variants.core.VariantsView;
14 14 import org.implab.gradle.variants.core.VariantsView.VariantRoleLayer;
15 15 import org.junit.jupiter.api.Test;
16 16
17 17 class RoleProjectionsViewTest {
18 18 @Test
19 19 void exposesRoleProjectionsAndTheirCompileUnits() {
20 20 var browser = new TestVariant("browser");
21 21 var main = new TestLayer("main");
22 22 var test = new TestLayer("test");
23 23 var production = new TestRole("production");
24 24 var qa = new TestRole("test");
25 25
26 26 var view = view(
27 27 Set.of(main, test),
28 28 Set.of(production, qa),
29 29 Set.of(browser),
30 30 Set.of(
31 31 new VariantRoleLayer(browser, production, main),
32 32 new VariantRoleLayer(browser, qa, main),
33 33 new VariantRoleLayer(browser, qa, test)));
34 34
35 35 var projections = RoleProjectionsView.of(view);
36 var productionProjection = projections.getProjection(browser, production);
37 var qaProjection = projections.getProjection(browser, qa);
36 var productionProjection = projections.requireProjection(browser, production);
37 var qaProjection = projections.requireProjection(browser, qa);
38 38
39 39 assertEquals(Set.of(productionProjection, qaProjection), projections.getProjections());
40 40 assertEquals(Set.of(productionProjection, qaProjection), projections.getProjectionsForVariant(browser));
41 41 assertEquals(Set.of(qaProjection), projections.getProjectionsForRole(qa));
42 42 assertEquals(Set.of(new CompileUnit(browser, main)), projections.getUnits(productionProjection));
43 43 assertEquals(Set.of(new CompileUnit(browser, main), new CompileUnit(browser, test)), projections.getUnits(qaProjection));
44 44 assertEquals(Set.of(main, test), projections.getLayers(qaProjection));
45 45 assertTrue(projections.contains(browser, production));
46 46 }
47 47
48 48 @Test
49 49 void rejectsMissingProjectionLookup() {
50 50 var browser = new TestVariant("browser");
51 51 var node = new TestVariant("node");
52 52 var main = new TestLayer("main");
53 53 var production = new TestRole("production");
54 54
55 55 var view = view(
56 56 Set.of(main),
57 57 Set.of(production),
58 58 Set.of(browser, node),
59 59 Set.of(new VariantRoleLayer(browser, production, main)));
60 60
61 61 var projections = RoleProjectionsView.of(view);
62 62
63 var ex = assertThrows(IllegalArgumentException.class, () -> projections.getProjection(node, production));
63 var ex = assertThrows(IllegalArgumentException.class, () -> projections.requireProjection(node, production));
64 64 assertTrue(ex.getMessage().contains("Role projection for variant 'node' and role 'production' not found"));
65 65 }
66 66
67 67 private static VariantsView view(
68 68 Set<Layer> layers,
69 69 Set<Role> roles,
70 70 Set<Variant> variants,
71 71 Set<VariantRoleLayer> entries) {
72 72 try {
73 73 Constructor<VariantsView> ctor = VariantsView.class.getDeclaredConstructor(
74 74 Set.class,
75 75 Set.class,
76 76 Set.class,
77 77 Set.class);
78 78 ctor.setAccessible(true);
79 79 return ctor.newInstance(layers, roles, variants, entries);
80 80 } catch (Exception e) {
81 81 throw new RuntimeException("Unable to create VariantsView fixture", e);
82 82 }
83 83 }
84 84
85 85 private record TestVariant(String value) implements Variant {
86 86 @Override
87 87 public String getName() {
88 88 return value;
89 89 }
90 90 }
91 91
92 92 private record TestLayer(String value) implements Layer {
93 93 @Override
94 94 public String getName() {
95 95 return value;
96 96 }
97 97 }
98 98
99 99 private record TestRole(String value) implements Role {
100 100 @Override
101 101 public String getName() {
102 102 return value;
103 103 }
104 104 }
105 105 }
General Comments 0
You need to be logged in to leave comments. Login now