##// END OF EJS Templates
refactor: replace configureAssembly with configureTask
cin -
r37:3be316021669 default
parent child
Show More
@@ -1,53 +1,19
1 package org.implab.gradle.common.sources;
1 package org.implab.gradle.common.sources;
2
2
3 import org.eclipse.jdt.annotation.NonNullByDefault;
3 import org.eclipse.jdt.annotation.NonNullByDefault;
4 import org.gradle.api.Named;
4 import org.gradle.api.Task;
5 import org.gradle.api.file.ConfigurableFileCollection;
5 import org.gradle.api.file.ConfigurableFileCollection;
6 import org.gradle.api.file.Directory;
6 import org.gradle.api.file.Directory;
7 import org.gradle.api.file.FileCollection;
8 import org.gradle.api.provider.Provider;
7 import org.gradle.api.provider.Provider;
9 import org.gradle.api.tasks.Copy;
10 import org.gradle.api.tasks.TaskProvider;
8 import org.gradle.api.tasks.TaskProvider;
11
9
12 @NonNullByDefault
10 @NonNullByDefault
13 public final class ArtifactAssembly implements Named {
11 public final record ArtifactAssembly(
14 private final String name;
15 private final ConfigurableFileCollection sources;
16 private final ConfigurableFileCollection output;
17 private final Provider<Directory> outputDirectory;
18 private final TaskProvider<Copy> task;
19
20 ArtifactAssembly(
21 String name,
12 String name,
22 ConfigurableFileCollection sources,
23 Provider<Directory> outputDirectory,
13 Provider<Directory> outputDirectory,
24 TaskProvider<Copy> task,
14 TaskProvider<? extends Task> task,
25 ConfigurableFileCollection output) {
15 ConfigurableFileCollection output
26 this.name = name;
16 ) {
27 this.sources = sources;
28 this.outputDirectory = outputDirectory;
29 this.task = task;
30 this.output = output;
31 }
32
17
33 @Override
18 };
34 public String getName() {
35 return name;
36 }
37
38 public ConfigurableFileCollection getSources() {
39 return sources;
40 }
41
19
42 public FileCollection getOutput() {
43 return output;
44 }
45
46 public Provider<Directory> getOutputDirectory() {
47 return outputDirectory;
48 }
49
50 public TaskProvider<Copy> getTask() {
51 return task;
52 }
53 }
@@ -1,64 +1,76
1 package org.implab.gradle.common.sources;
1 package org.implab.gradle.common.sources;
2
2
3 import java.util.LinkedHashMap;
3 import java.util.LinkedHashMap;
4 import java.util.Map;
4 import java.util.Map;
5 import java.util.Optional;
5 import java.util.Optional;
6 import java.util.function.Function;
6
7
7 import org.eclipse.jdt.annotation.NonNullByDefault;
8 import org.eclipse.jdt.annotation.NonNullByDefault;
8 import org.gradle.api.Action;
9 import org.gradle.api.Action;
9 import org.gradle.api.InvalidUserDataException;
10 import org.gradle.api.InvalidUserDataException;
11 import org.gradle.api.Task;
10 import org.gradle.api.file.ConfigurableFileCollection;
12 import org.gradle.api.file.ConfigurableFileCollection;
11 import org.gradle.api.file.Directory;
13 import org.gradle.api.file.Directory;
12 import org.gradle.api.model.ObjectFactory;
14 import org.gradle.api.model.ObjectFactory;
13 import org.gradle.api.provider.Provider;
15 import org.gradle.api.provider.Provider;
14 import org.gradle.api.tasks.Copy;
16 import org.gradle.api.tasks.Copy;
15 import org.gradle.api.tasks.TaskContainer;
17 import org.gradle.api.tasks.TaskContainer;
18 import org.gradle.api.tasks.TaskProvider;
16 import org.gradle.language.base.plugins.LifecycleBasePlugin;
19 import org.gradle.language.base.plugins.LifecycleBasePlugin;
17
20
18 @NonNullByDefault
21 @NonNullByDefault
19 public final class ArtifactAssemblyRegistry {
22 public final class ArtifactAssemblyRegistry {
20 private final ObjectFactory objects;
23 private final ObjectFactory objects;
21 private final TaskContainer tasks;
24 private final TaskContainer tasks;
22 private final Map<String, ArtifactAssembly> assemblies = new LinkedHashMap<>();
25 private final Map<String, ArtifactAssembly> assemblies = new LinkedHashMap<>();
23
26
24 public ArtifactAssemblyRegistry(ObjectFactory objects, TaskContainer tasks) {
27 public ArtifactAssemblyRegistry(ObjectFactory objects, TaskContainer tasks) {
25 this.objects = objects;
28 this.objects = objects;
26 this.tasks = tasks;
29 this.tasks = tasks;
27 }
30 }
28
31
29 public ArtifactAssembly register(
32 public ArtifactAssembly register(
30 String name,
33 String name,
31 String taskName,
34 String taskName,
32 Provider<Directory> outputDirectory,
35 Provider<Directory> outputDirectory,
33 Action<? super ConfigurableFileCollection> configureSources) {
36 Action<? super ConfigurableFileCollection> configureSources) {
34 if (assemblies.containsKey(name)) {
35 throw new InvalidUserDataException("Artifact assembly '" + name + "' is already registered");
36 }
37
37
38 var sources = objects.fileCollection();
38 var sources = objects.fileCollection();
39 configureSources.execute(sources);
39 configureSources.execute(sources);
40
40
41 var task = tasks.register(taskName, Copy.class, copy -> {
41 var task = tasks.register(taskName, Copy.class, copy -> {
42 copy.setGroup(LifecycleBasePlugin.BUILD_GROUP);
42 copy.setGroup(LifecycleBasePlugin.BUILD_GROUP);
43 copy.into(outputDirectory);
43 copy.into(outputDirectory);
44 copy.from(sources);
44 copy.from(sources);
45 });
45 });
46
46
47 return register(name, task, t -> outputDirectory);
48 }
49
50 public <T extends Task> ArtifactAssembly register(
51 String name,
52 TaskProvider<T> task,
53 Function<? super T, ? extends Provider<Directory>> mapOutputDirectory) {
54 if (assemblies.containsKey(name)) {
55 throw new InvalidUserDataException("Artifact assembly '" + name + "' is already registered");
56 }
57 var outputDirectory = task.flatMap(t -> mapOutputDirectory.apply(t));
58
47 var output = objects.fileCollection()
59 var output = objects.fileCollection()
48 .from(outputDirectory)
60 .from(outputDirectory)
49 .builtBy(task);
61 .builtBy(task);
50
62
51 var assembly = new ArtifactAssembly(name, sources, outputDirectory, task, output);
63 var assembly = new ArtifactAssembly(name, outputDirectory, task, output);
52 assemblies.put(name, assembly);
64 assemblies.put(name, assembly);
53 return assembly;
65 return assembly;
54 }
66 }
55
67
56 public Optional<ArtifactAssembly> find(String name) {
68 public Optional<ArtifactAssembly> find(String name) {
57 return Optional.ofNullable(assemblies.get(name));
69 return Optional.ofNullable(assemblies.get(name));
58 }
70 }
59
71
60 public ArtifactAssembly require(String name) {
72 public ArtifactAssembly require(String name) {
61 return find(name)
73 return find(name)
62 .orElseThrow(() -> new InvalidUserDataException("Artifact assembly '" + name + "' isn't registered"));
74 .orElseThrow(() -> new InvalidUserDataException("Artifact assembly '" + name + "' isn't registered"));
63 }
75 }
64 }
76 }
@@ -1,205 +1,206
1 package org.implab.gradle.common.sources;
1 package org.implab.gradle.common.sources;
2
2
3 import java.io.File;
3 import java.io.File;
4 import java.nio.file.Paths;
4 import java.nio.file.Paths;
5 import java.util.HashSet;
5 import java.util.HashSet;
6 import java.util.LinkedHashMap;
6 import java.util.LinkedHashMap;
7 import java.util.List;
7 import java.util.List;
8 import java.util.Map;
8 import java.util.Map;
9 import java.util.Objects;
9 import java.util.Objects;
10 import java.util.Set;
10 import java.util.Set;
11 import java.util.concurrent.Callable;
11 import java.util.concurrent.Callable;
12 import java.util.function.Function;
12 import java.util.function.Function;
13 import java.util.stream.Collectors;
13 import java.util.stream.Collectors;
14 import java.util.stream.Stream;
14
15
15 import javax.inject.Inject;
16 import javax.inject.Inject;
16
17
17 import org.gradle.api.InvalidUserDataException;
18 import org.gradle.api.InvalidUserDataException;
18 import org.gradle.api.Named;
19 import org.gradle.api.Named;
19 import org.gradle.api.NamedDomainObjectContainer;
20 import org.gradle.api.NamedDomainObjectContainer;
20 import org.gradle.api.Task;
21 import org.gradle.api.Task;
21 import org.gradle.api.file.ConfigurableFileCollection;
22 import org.gradle.api.file.ConfigurableFileCollection;
22 import org.gradle.api.file.DirectoryProperty;
23 import org.gradle.api.file.DirectoryProperty;
23 import org.gradle.api.file.FileCollection;
24 import org.gradle.api.file.FileCollection;
24 import org.gradle.api.file.ProjectLayout;
25 import org.gradle.api.file.ProjectLayout;
25 import org.gradle.api.file.SourceDirectorySet;
26 import org.gradle.api.file.SourceDirectorySet;
26 import org.gradle.api.model.ObjectFactory;
27 import org.gradle.api.model.ObjectFactory;
27 import org.gradle.api.tasks.TaskProvider;
28 import org.gradle.api.tasks.TaskProvider;
28 import org.gradle.util.Configurable;
29 import org.gradle.util.Configurable;
29 import org.implab.gradle.common.core.lang.Closures;
30 import org.implab.gradle.common.core.lang.Closures;
30
31
31 import groovy.lang.Closure;
32 import groovy.lang.Closure;
32
33
33 /**
34 /**
34 * A configurable source set abstraction with named outputs.
35 * A configurable source set abstraction with named outputs.
35 *
36 *
36 * <p>
37 * <p>
37 * Each instance aggregates multiple {@link SourceDirectorySet source sets}
38 * Each instance aggregates multiple {@link SourceDirectorySet source sets}
38 * under a shared name and exposes typed outputs that must be declared up front.
39 * under a shared name and exposes typed outputs that must be declared up front.
39 * Default locations are {@code src/<name>} for sources and
40 * Default locations are {@code src/<name>} for sources and
40 * {@code build/<name>} for outputs, both of which can be customized via the
41 * {@code build/<name>} for outputs, both of which can be customized via the
41 * exposed {@link DirectoryProperty} setters.
42 * exposed {@link DirectoryProperty} setters.
42 * </p>
43 * </p>
43 *
44 *
44 * <p>
45 * <p>
45 * Outputs are grouped by names to make task wiring explicit. An output must be
46 * Outputs are grouped by names to make task wiring explicit. An output must be
46 * declared with {@link #declareOutputs(String, String...)} before files can be
47 * declared with {@link #declareOutputs(String, String...)} before files can be
47 * registered against it. Attempting to register or retrieve an undeclared
48 * registered against it. Attempting to register or retrieve an undeclared
48 * output results in
49 * output results in
49 * {@link InvalidUserDataException}.
50 * {@link InvalidUserDataException}.
50 * </p>
51 * </p>
51 */
52 */
52 public abstract class GenericSourceSet
53 public abstract class GenericSourceSet
53 implements Named, Configurable<GenericSourceSet> {
54 implements Named, Configurable<GenericSourceSet> {
54 private final String name;
55 private final String name;
55
56
56 private final NamedDomainObjectContainer<SourceDirectorySet> sourceDirectorySets;
57 private final NamedDomainObjectContainer<SourceDirectorySet> sourceDirectorySets;
57
58
58 private final Map<String, ConfigurableFileCollection> outputs;
59 private final Map<String, ConfigurableFileCollection> outputs;
59
60
60 private final FileCollection allOutputs;
61 private final FileCollection allOutputs;
61
62
62 private final FileCollection allSourceDirectories;
63 private final FileCollection allSourceDirectories;
63
64
64 private final ObjectFactory objects;
65 private final ObjectFactory objects;
65
66
66 private final Set<String> declaredOutputs = new HashSet<>();
67 private final Set<String> declaredOutputs = new HashSet<>();
67
68
68 @Inject
69 @Inject
69 public GenericSourceSet(String name, ObjectFactory objects, ProjectLayout layout) {
70 public GenericSourceSet(String name, ObjectFactory objects, ProjectLayout layout) {
70 this.name = name;
71 this.name = name;
71 this.objects = objects;
72 this.objects = objects;
72
73
73 sourceDirectorySets = objects.domainObjectContainer(
74 sourceDirectorySets = objects.domainObjectContainer(
74 SourceDirectorySet.class,
75 SourceDirectorySet.class,
75 this::createSourceDirectorySet);
76 this::createSourceDirectorySet);
76
77
77 outputs = new LinkedHashMap<>();
78 outputs = new LinkedHashMap<>();
78
79
79 allSourceDirectories = objects.fileCollection().from(sourceDirectoriesProvider());
80 allSourceDirectories = objects.fileCollection().from(sourceDirectoriesProvider());
80
81
81 allOutputs = objects.fileCollection().from(outputsProvider());
82 allOutputs = objects.fileCollection().from(outputsProvider());
82
83
83 getSourceSetDir().convention(layout
84 getSourceSetDir().convention(layout
84 .getProjectDirectory()
85 .getProjectDirectory()
85 .dir(Paths.get("src", name).toString()));
86 .dir(Paths.get("src", name).toString()));
86
87
87 getOutputsDir().convention(layout
88 getOutputsDir().convention(layout
88 .getBuildDirectory()
89 .getBuildDirectory()
89 .dir(name));
90 .dir(name));
90 }
91 }
91
92
92 @Override
93 @Override
93 public String getName() {
94 public String getName() {
94 return name;
95 return name;
95 }
96 }
96
97
97 /**
98 /**
98 * Base directory for this source set. Defaults to {@code src/<name>} under
99 * Base directory for this source set. Defaults to {@code src/<name>} under
99 * the project directory.
100 * the project directory.
100 */
101 */
101 public abstract DirectoryProperty getSourceSetDir();
102 public abstract DirectoryProperty getSourceSetDir();
102
103
103 /**
104 /**
104 * Base directory for outputs of this source set. Defaults to
105 * Base directory for outputs of this source set. Defaults to
105 * {@code build/<name>}.
106 * {@code build/<name>}.
106 */
107 */
107 public abstract DirectoryProperty getOutputsDir();
108 public abstract DirectoryProperty getOutputsDir();
108
109
109 /**
110 /**
110 * The container of {@link SourceDirectorySet} instances that belong to this
111 * The container of {@link SourceDirectorySet} instances that belong to this
111 * logical source set.
112 * logical source set.
112 */
113 */
113 public NamedDomainObjectContainer<SourceDirectorySet> getSets() {
114 public NamedDomainObjectContainer<SourceDirectorySet> getSets() {
114 return sourceDirectorySets;
115 return sourceDirectorySets;
115 }
116 }
116
117
117 /**
118 /**
118 * All registered outputs grouped across output names.
119 * All registered outputs grouped across output names.
119 */
120 */
120 public FileCollection getAllOutputs() {
121 public FileCollection getAllOutputs() {
121 return allOutputs;
122 return allOutputs;
122 }
123 }
123
124
124 /**
125 /**
125 * All source directories from every contained {@link SourceDirectorySet}.
126 * All source directories from every contained {@link SourceDirectorySet}.
126 */
127 */
127 public FileCollection getAllSourceDirectories() {
128 public FileCollection getAllSourceDirectories() {
128 return allSourceDirectories;
129 return allSourceDirectories;
129 }
130 }
130
131
131 /**
132 /**
132 * Returns the file collection for the specified output name, creating it
133 * Returns the file collection for the specified output name, creating it
133 * if necessary.
134 * if necessary.
134 *
135 *
135 * @throws InvalidUserDataException if the output was not declared
136 * @throws InvalidUserDataException if the output was not declared
136 */
137 */
137 public FileCollection output(String name) {
138 public FileCollection output(String name) {
138 return configurableOutput(name);
139 return configurableOutput(name);
139 }
140 }
140
141
141 private ConfigurableFileCollection configurableOutput(String name) {
142 private ConfigurableFileCollection configurableOutput(String name) {
142 requireDeclaredOutput(name);
143 requireDeclaredOutput(name);
143 return outputs.computeIfAbsent(name, key -> objects.fileCollection());
144 return outputs.computeIfAbsent(name, key -> objects.fileCollection());
144 }
145 }
145
146
146 /**
147 /**
147 * Declares allowed output names. Outputs must be declared before registering
148 * Declares allowed output names. Outputs must be declared before registering
148 * files under them.
149 * files under them.
149 */
150 */
150 public void declareOutputs(String name, String... extra) {
151 public void declareOutputs(String name, String... extra) {
151 declaredOutputs.add(Objects.requireNonNull(name, "declareOutputs: The output name cannot be null"));
152 Stream.concat(Stream.of(name), Stream.of(extra))
152 for (var x : extra)
153 .map(Objects::requireNonNull)
153 declaredOutputs.add(Objects.requireNonNull(x, "declareOutputs: The output name cannot be null"));
154 .forEach(declaredOutputs::add);
154 }
155 }
155
156
156 /**
157 /**
157 * Registers files produced elsewhere under the given output.
158 * Registers files produced elsewhere under the given output.
158 */
159 */
159 public void registerOutput(String name, Object... files) {
160 public void registerOutput(String name, Object... files) {
160 configurableOutput(name).from(files);
161 configurableOutput(name).from(files);
161 }
162 }
162
163
163 /**
164 /**
164 * Registers output files produced by a task, using a mapper to extract the
165 * Registers output files produced by a task, using a mapper to extract the
165 * output from the task. The task will be added as a build dependency of this
166 * output from the task. The task will be added as a build dependency of this
166 * output.
167 * output.
167 */
168 */
168 public <T extends Task> void registerOutput(String name, TaskProvider<T> task,
169 public <T extends Task> void registerOutput(String name, TaskProvider<T> task,
169 Function<? super T, ?> mapper) {
170 Function<? super T, ?> mapper) {
170 configurableOutput(name).from(task.map(mapper::apply))
171 configurableOutput(name).from(task.map(mapper::apply))
171 .builtBy(task);
172 .builtBy(task);
172 }
173 }
173
174
174 /**
175 /**
175 * Applies a Groovy closure to this source set, enabling DSL-style
176 * Applies a Groovy closure to this source set, enabling DSL-style
176 * configuration.
177 * configuration.
177 */
178 */
178 @Override
179 @Override
179 public GenericSourceSet configure(Closure configure) {
180 public GenericSourceSet configure(Closure configure) {
180 Closures.apply(configure, this);
181 Closures.apply(configure, this);
181 return this;
182 return this;
182 }
183 }
183
184
184 private SourceDirectorySet createSourceDirectorySet(String name) {
185 private SourceDirectorySet createSourceDirectorySet(String name) {
185 return objects.sourceDirectorySet(name, name);
186 return objects.sourceDirectorySet(name, name);
186 }
187 }
187
188
188 private void requireDeclaredOutput(String outputName) {
189 private void requireDeclaredOutput(String outputName) {
189 if (!declaredOutputs.contains(outputName)) {
190 if (!declaredOutputs.contains(outputName)) {
190 throw new InvalidUserDataException(
191 throw new InvalidUserDataException(
191 "Output '" + outputName + "' is not declared for source set '" + name + "'");
192 "Output '" + outputName + "' is not declared for source set '" + name + "'");
192 }
193 }
193 }
194 }
194
195
195 private Callable<List<? extends FileCollection>> outputsProvider() {
196 private Callable<List<? extends FileCollection>> outputsProvider() {
196 return () -> outputs.values().stream().toList();
197 return () -> outputs.values().stream().toList();
197 }
198 }
198
199
199 private Callable<Set<File>> sourceDirectoriesProvider() {
200 private Callable<Set<File>> sourceDirectoriesProvider() {
200 return () -> sourceDirectorySets.stream()
201 return () -> sourceDirectorySets.stream()
201 .flatMap(x -> x.getSrcDirs().stream())
202 .flatMap(x -> x.getSrcDirs().stream())
202 .collect(Collectors.toSet());
203 .collect(Collectors.toSet());
203 }
204 }
204
205
205 }
206 }
@@ -1,66 +1,63
1 package org.implab.gradle.common.sources;
1 package org.implab.gradle.common.sources;
2
2
3 import org.eclipse.jdt.annotation.NonNullByDefault;
3 import org.eclipse.jdt.annotation.NonNullByDefault;
4 import org.gradle.api.Action;
4 import org.gradle.api.Action;
5 import org.gradle.api.Task;
5 import org.gradle.api.attributes.AttributeContainer;
6 import org.gradle.api.attributes.AttributeContainer;
6 import org.gradle.api.attributes.HasConfigurableAttributes;
7 import org.gradle.api.attributes.HasConfigurableAttributes;
7 import org.implab.gradle.common.core.lang.Closures;
8 import org.implab.gradle.common.core.lang.Closures;
8
9
9 import groovy.lang.Closure;
10 import groovy.lang.Closure;
10 import groovy.lang.DelegatesTo;
11 import groovy.lang.DelegatesTo;
11
12
12 @NonNullByDefault
13 @NonNullByDefault
13 public final class OutgoingArtifactSlotPublication {
14 public final class OutgoingArtifactSlotPublication {
14 private final String slotName;
15 private final String slotName;
15 private final boolean primary;
16 private final boolean primary;
16 private final VariantArtifactSlot slot;
17 private final VariantArtifactSlot slot;
17 private final ArtifactAssembly assembly;
18 private final ArtifactAssembly assembly;
18 private final HasConfigurableAttributes<?> attributesCarrier;
19 private final HasConfigurableAttributes<?> attributesCarrier;
19
20
20 OutgoingArtifactSlotPublication(
21 OutgoingArtifactSlotPublication(
21 String slotName,
22 String slotName,
22 boolean primary,
23 boolean primary,
23 VariantArtifactSlot slot,
24 VariantArtifactSlot slot,
24 ArtifactAssembly assembly,
25 ArtifactAssembly assembly,
25 HasConfigurableAttributes<?> attributesCarrier) {
26 HasConfigurableAttributes<?> attributesCarrier) {
26 this.slotName = slotName;
27 this.slotName = slotName;
27 this.primary = primary;
28 this.primary = primary;
28 this.slot = slot;
29 this.slot = slot;
29 this.assembly = assembly;
30 this.assembly = assembly;
30 this.attributesCarrier = attributesCarrier;
31 this.attributesCarrier = attributesCarrier;
31 }
32 }
32
33
33 public String slotName() {
34 public String slotName() {
34 return slotName;
35 return slotName;
35 }
36 }
36
37
37 public boolean primary() {
38 public boolean primary() {
38 return primary;
39 return primary;
39 }
40 }
40
41
41 public VariantArtifactSlot slot() {
42 public VariantArtifactSlot slot() {
42 return slot;
43 return slot;
43 }
44 }
44
45
45 public ArtifactAssembly assembly() {
46 public void configureTask(Action<? super Task> action) {
46 return assembly;
47 assembly.task().configure(action::execute);
47 }
48 }
48
49
49 public void configureAssembly(Action<? super ArtifactAssembly> action) {
50 public void configureTask(
50 action.execute(assembly);
51 @DelegatesTo(value = Task.class, strategy = Closure.DELEGATE_FIRST) Closure<?> action) {
51 }
52 configureTask(Closures.action(action));
52
53 public void configureAssembly(
54 @DelegatesTo(value = ArtifactAssembly.class, strategy = Closure.DELEGATE_FIRST) Closure<?> action) {
55 configureAssembly(Closures.action(action));
56 }
53 }
57
54
58 public void configureArtifactAttributes(Action<? super AttributeContainer> action) {
55 public void configureArtifactAttributes(Action<? super AttributeContainer> action) {
59 attributesCarrier.attributes(action);
56 attributesCarrier.attributes(action);
60 }
57 }
61
58
62 public void configureArtifactAttributes(
59 public void configureArtifactAttributes(
63 @DelegatesTo(value = AttributeContainer.class, strategy = Closure.DELEGATE_FIRST) Closure<?> action) {
60 @DelegatesTo(value = AttributeContainer.class, strategy = Closure.DELEGATE_FIRST) Closure<?> action) {
64 configureArtifactAttributes(Closures.action(action));
61 configureArtifactAttributes(Closures.action(action));
65 }
62 }
66 }
63 }
@@ -1,181 +1,181
1 package org.implab.gradle.common.sources;
1 package org.implab.gradle.common.sources;
2
2
3 import java.util.LinkedHashMap;
3 import java.util.LinkedHashMap;
4 import java.util.List;
4 import java.util.List;
5 import java.util.ArrayList;
5 import java.util.ArrayList;
6 import java.util.stream.Collectors;
6 import java.util.stream.Collectors;
7 import java.util.stream.Stream;
7 import java.util.stream.Stream;
8
8
9 import org.gradle.api.GradleException;
9 import org.gradle.api.GradleException;
10 import org.gradle.api.Plugin;
10 import org.gradle.api.Plugin;
11 import org.gradle.api.Project;
11 import org.gradle.api.Project;
12 import org.gradle.api.artifacts.Configuration;
12 import org.gradle.api.artifacts.Configuration;
13 import org.gradle.api.artifacts.ConfigurationPublications;
13 import org.gradle.api.artifacts.ConfigurationPublications;
14 import org.gradle.api.artifacts.ConfigurationVariant;
14 import org.gradle.api.artifacts.ConfigurationVariant;
15 import org.gradle.api.logging.Logger;
15 import org.gradle.api.logging.Logger;
16 import org.gradle.api.logging.Logging;
16 import org.gradle.api.logging.Logging;
17 import org.implab.gradle.common.core.lang.Strings;
17 import org.implab.gradle.common.core.lang.Strings;
18
18
19 public abstract class VariantArtifactsPlugin implements Plugin<Project> {
19 public abstract class VariantArtifactsPlugin implements Plugin<Project> {
20 private static final Logger logger = Logging.getLogger(VariantArtifactsPlugin.class);
20 private static final Logger logger = Logging.getLogger(VariantArtifactsPlugin.class);
21 public static final String VARIANT_ARTIFACTS_EXTENSION_NAME = "variantArtifacts";
21 public static final String VARIANT_ARTIFACTS_EXTENSION_NAME = "variantArtifacts";
22
22
23 @Override
23 @Override
24 public void apply(Project target) {
24 public void apply(Project target) {
25 logger.debug("Registering '{}' extension on project '{}'", VARIANT_ARTIFACTS_EXTENSION_NAME, target.getPath());
25 logger.debug("Registering '{}' extension on project '{}'", VARIANT_ARTIFACTS_EXTENSION_NAME, target.getPath());
26
26
27 target.getPluginManager().apply(VariantsSourcesPlugin.class);
27 target.getPluginManager().apply(VariantsSourcesPlugin.class);
28
28
29 var variants = VariantsPlugin.getVariantsExtension(target);
29 var variants = VariantsPlugin.getVariantsExtension(target);
30 var variantSources = target.getExtensions().getByType(VariantSourcesExtension.class);
30 var variantSources = target.getExtensions().getByType(VariantSourcesExtension.class);
31 var variantArtifacts = target.getExtensions()
31 var variantArtifacts = target.getExtensions()
32 .create(VARIANT_ARTIFACTS_EXTENSION_NAME, VariantArtifactsExtension.class);
32 .create(VARIANT_ARTIFACTS_EXTENSION_NAME, VariantArtifactsExtension.class);
33 var variantArtifactsResolver = new VariantArtifactsResolver(target.getObjects());
33 var variantArtifactsResolver = new VariantArtifactsResolver(target.getObjects());
34 var artifactAssemblies = new ArtifactAssemblyRegistry(target.getObjects(), target.getTasks());
34 var artifactAssemblies = new ArtifactAssemblyRegistry(target.getObjects(), target.getTasks());
35
35
36 // Bind variant artifacts resolution to variant sources registration, so that artifact resolution can be performed
36 // Bind variant artifacts resolution to variant sources registration, so that artifact resolution can be performed
37 variantSources.whenBound(variantArtifactsResolver::recordBinding);
37 variantSources.whenBound(variantArtifactsResolver::recordBinding);
38
38
39 variants.whenFinalized(model -> {
39 variants.whenFinalized(model -> {
40 logger.debug("Finalizing variantArtifacts model on project '{}'", target.getPath());
40 logger.debug("Finalizing variantArtifacts model on project '{}'", target.getPath());
41 variantArtifacts.finalizeModel(model);
41 variantArtifacts.finalizeModel(model);
42 materializeOutgoingVariants(target, model, variantArtifacts, variantArtifactsResolver, artifactAssemblies);
42 materializeOutgoingVariants(target, model, variantArtifacts, variantArtifactsResolver, artifactAssemblies);
43 logger.debug("variantArtifacts model finalized on project '{}'", target.getPath());
43 logger.debug("variantArtifacts model finalized on project '{}'", target.getPath());
44 });
44 });
45 }
45 }
46
46
47 public static VariantArtifactsExtension getVariantArtifactsExtension(Project target) {
47 public static VariantArtifactsExtension getVariantArtifactsExtension(Project target) {
48 var extension = target.getExtensions().findByType(VariantArtifactsExtension.class);
48 var extension = target.getExtensions().findByType(VariantArtifactsExtension.class);
49
49
50 if (extension == null) {
50 if (extension == null) {
51 logger.error("variantArtifacts extension '{}' isn't found on project '{}'",
51 logger.error("variantArtifacts extension '{}' isn't found on project '{}'",
52 VARIANT_ARTIFACTS_EXTENSION_NAME,
52 VARIANT_ARTIFACTS_EXTENSION_NAME,
53 target.getPath());
53 target.getPath());
54 throw new GradleException("variantArtifacts extension isn't found");
54 throw new GradleException("variantArtifacts extension isn't found");
55 }
55 }
56
56
57 logger.debug("Resolved '{}' extension on project '{}'", VARIANT_ARTIFACTS_EXTENSION_NAME, target.getPath());
57 logger.debug("Resolved '{}' extension on project '{}'", VARIANT_ARTIFACTS_EXTENSION_NAME, target.getPath());
58
58
59 return extension;
59 return extension;
60 }
60 }
61
61
62 private static void materializeOutgoingVariants(
62 private static void materializeOutgoingVariants(
63 Project project,
63 Project project,
64 BuildVariantsExtension topology,
64 BuildVariantsExtension topology,
65 VariantArtifactsExtension variantArtifacts,
65 VariantArtifactsExtension variantArtifacts,
66 VariantArtifactsResolver variantArtifactsResolver,
66 VariantArtifactsResolver variantArtifactsResolver,
67 ArtifactAssemblyRegistry artifactAssemblies) {
67 ArtifactAssemblyRegistry artifactAssemblies) {
68 variantArtifacts.getVariants().stream()
68 variantArtifacts.getVariants().stream()
69 .filter(variantArtifact -> !variantArtifact.getSlots().isEmpty())
69 .filter(variantArtifact -> !variantArtifact.getSlots().isEmpty())
70 .forEach(variantArtifact -> materializeOutgoingVariant(
70 .forEach(variantArtifact -> materializeOutgoingVariant(
71 project,
71 project,
72 topology.require(variantArtifact.getName()),
72 topology.require(variantArtifact.getName()),
73 variantArtifact,
73 variantArtifact,
74 variantArtifactsResolver,
74 variantArtifactsResolver,
75 artifactAssemblies,
75 artifactAssemblies,
76 variantArtifacts));
76 variantArtifacts));
77 }
77 }
78
78
79 private static void materializeOutgoingVariant(
79 private static void materializeOutgoingVariant(
80 Project project,
80 Project project,
81 BuildVariant topologyVariant,
81 BuildVariant topologyVariant,
82 VariantArtifact variantArtifact,
82 VariantArtifact variantArtifact,
83 VariantArtifactsResolver variantArtifactsResolver,
83 VariantArtifactsResolver variantArtifactsResolver,
84 ArtifactAssemblyRegistry artifactAssemblies,
84 ArtifactAssemblyRegistry artifactAssemblies,
85 VariantArtifactsExtension variantArtifacts) {
85 VariantArtifactsExtension variantArtifacts) {
86 var assemblies = variantArtifact.getSlots().stream()
86 var assemblies = variantArtifact.getSlots().stream()
87 .collect(Collectors.toMap(
87 .collect(Collectors.toMap(
88 VariantArtifactSlot::getName,
88 VariantArtifactSlot::getName,
89 slot -> registerAssembly(project, variantArtifactsResolver, artifactAssemblies, variantArtifact, slot),
89 slot -> registerAssembly(project, variantArtifactsResolver, artifactAssemblies, variantArtifact, slot),
90 (left, right) -> left,
90 (left, right) -> left,
91 LinkedHashMap::new));
91 LinkedHashMap::new));
92
92
93 var primarySlot = variantArtifact.requirePrimarySlot();
93 var primarySlot = variantArtifact.requirePrimarySlot();
94 var configuration = createOutgoingConfiguration(project, variantArtifact.getName(), primarySlot.getName());
94 var configuration = createOutgoingConfiguration(project, variantArtifact.getName(), primarySlot.getName());
95 var primaryAssembly = assemblies.get(primarySlot.getName());
95 var primaryAssembly = assemblies.get(primarySlot.getName());
96 publishPrimaryArtifact(configuration, primaryAssembly);
96 publishPrimaryArtifact(configuration, primaryAssembly);
97 var primaryPublication = new OutgoingArtifactSlotPublication(
97 var primaryPublication = new OutgoingArtifactSlotPublication(
98 primarySlot.getName(),
98 primarySlot.getName(),
99 true,
99 true,
100 primarySlot,
100 primarySlot,
101 primaryAssembly,
101 primaryAssembly,
102 configuration);
102 configuration);
103 var secondarySlots = variantArtifact.getSlots().stream()
103 var secondarySlots = variantArtifact.getSlots().stream()
104 .filter(slot -> !slot.getName().equals(primarySlot.getName()))
104 .filter(slot -> !slot.getName().equals(primarySlot.getName()))
105 .map(slot -> new SecondarySlot(slot, assemblies.get(slot.getName())))
105 .map(slot -> new SecondarySlot(slot, assemblies.get(slot.getName())))
106 .toList();
106 .toList();
107 var secondaryPublications = new ArrayList<OutgoingArtifactSlotPublication>(secondarySlots.size());
107 var secondaryPublications = new ArrayList<OutgoingArtifactSlotPublication>(secondarySlots.size());
108 secondarySlots.forEach(secondarySlot -> {
108 secondarySlots.forEach(secondarySlot -> {
109 var secondaryVariant = configuration.getOutgoing().getVariants().create(secondarySlot.slot().getName());
109 var secondaryVariant = configuration.getOutgoing().getVariants().create(secondarySlot.slot().getName());
110 publishSecondaryArtifact(secondaryVariant, secondarySlot.assembly());
110 publishSecondaryArtifact(secondaryVariant, secondarySlot.assembly());
111 secondaryPublications.add(new OutgoingArtifactSlotPublication(
111 secondaryPublications.add(new OutgoingArtifactSlotPublication(
112 secondarySlot.slot().getName(),
112 secondarySlot.slot().getName(),
113 false,
113 false,
114 secondarySlot.slot(),
114 secondarySlot.slot(),
115 secondarySlot.assembly(),
115 secondarySlot.assembly(),
116 secondaryVariant));
116 secondaryVariant));
117 });
117 });
118
118
119 var slotPublications = Stream.concat(
119 var slotPublications = Stream.concat(
120 Stream.of(primaryPublication),
120 Stream.of(primaryPublication),
121 secondaryPublications.stream())
121 secondaryPublications.stream())
122 .toList();
122 .toList();
123
123
124 variantArtifacts.notifyOutgoingVariant(new OutgoingVariantPublication(
124 variantArtifacts.notifyOutgoingVariant(new OutgoingVariantPublication(
125 variantArtifact.getName(),
125 variantArtifact.getName(),
126 topologyVariant,
126 topologyVariant,
127 variantArtifact,
127 variantArtifact,
128 configuration,
128 configuration,
129 primaryPublication,
129 primaryPublication,
130 slotPublications));
130 slotPublications));
131 }
131 }
132
132
133 private static ArtifactAssembly registerAssembly(
133 private static ArtifactAssembly registerAssembly(
134 Project project,
134 Project project,
135 VariantArtifactsResolver variantArtifactsResolver,
135 VariantArtifactsResolver variantArtifactsResolver,
136 ArtifactAssemblyRegistry artifactAssemblies,
136 ArtifactAssemblyRegistry artifactAssemblies,
137 VariantArtifact variantArtifact,
137 VariantArtifact variantArtifact,
138 VariantArtifactSlot slot) {
138 VariantArtifactSlot slot) {
139 return artifactAssemblies.register(
139 return artifactAssemblies.register(
140 variantArtifact.getName() + Strings.capitalize(slot.getName()),
140 variantArtifact.getName() + Strings.capitalize(slot.getName()),
141 "process" + Strings.capitalize(variantArtifact.getName()) + Strings.capitalize(slot.getName()),
141 "process" + Strings.capitalize(variantArtifact.getName()) + Strings.capitalize(slot.getName()),
142 project.getLayout().getBuildDirectory()
142 project.getLayout().getBuildDirectory()
143 .dir("variant-artifacts/" + variantArtifact.getName() + "/" + slot.getName()),
143 .dir("variant-artifacts/" + variantArtifact.getName() + "/" + slot.getName()),
144 files -> files.from(variantArtifactsResolver.files(variantArtifact.getName(), slot)));
144 files -> files.from(variantArtifactsResolver.files(variantArtifact.getName(), slot)));
145 }
145 }
146
146
147 private static Configuration createOutgoingConfiguration(
147 private static Configuration createOutgoingConfiguration(
148 Project project,
148 Project project,
149 String variantName,
149 String variantName,
150 String primarySlotName) {
150 String primarySlotName) {
151 var configName = variantName + "Elements";
151 var configName = variantName + "Elements";
152 return project.getConfigurations().consumable(configName, config -> {
152 return project.getConfigurations().consumable(configName, config -> {
153 config.setVisible(true);
153 config.setVisible(true);
154 config.setDescription("Consumable assembled artifacts for variant '" + variantName
154 config.setDescription("Consumable assembled artifacts for variant '" + variantName
155 + "' with primary slot '" + primarySlotName + "'");
155 + "' with primary slot '" + primarySlotName + "'");
156 }).get();
156 }).get();
157 }
157 }
158
158
159 private static void publishPrimaryArtifact(Configuration configuration, ArtifactAssembly assembly) {
159 private static void publishPrimaryArtifact(Configuration configuration, ArtifactAssembly assembly) {
160 publishArtifact(configuration.getOutgoing(), assembly);
160 publishArtifact(configuration.getOutgoing(), assembly);
161 }
161 }
162
162
163 private static void publishSecondaryArtifact(ConfigurationVariant variant, ArtifactAssembly assembly) {
163 private static void publishSecondaryArtifact(ConfigurationVariant variant, ArtifactAssembly assembly) {
164 publishArtifact(variant, assembly);
164 publishArtifact(variant, assembly);
165 }
165 }
166
166
167 private static void publishArtifact(ConfigurationPublications outgoing, ArtifactAssembly assembly) {
167 private static void publishArtifact(ConfigurationPublications outgoing, ArtifactAssembly assembly) {
168 outgoing.artifact(assembly.getOutput().getSingleFile(), published -> {
168 outgoing.artifact(assembly.output().getSingleFile(), published -> {
169 published.builtBy(assembly.getOutput().getBuildDependencies());
169 published.builtBy(assembly.output().getBuildDependencies());
170 });
170 });
171 }
171 }
172
172
173 private static void publishArtifact(ConfigurationVariant variant, ArtifactAssembly assembly) {
173 private static void publishArtifact(ConfigurationVariant variant, ArtifactAssembly assembly) {
174 variant.artifact(assembly.getOutput().getSingleFile(), published -> {
174 variant.artifact(assembly.output().getSingleFile(), published -> {
175 published.builtBy(assembly.getOutput().getBuildDependencies());
175 published.builtBy(assembly.output().getBuildDependencies());
176 });
176 });
177 }
177 }
178
178
179 private record SecondarySlot(VariantArtifactSlot slot, ArtifactAssembly assembly) {
179 private record SecondarySlot(VariantArtifactSlot slot, ArtifactAssembly assembly) {
180 }
180 }
181 }
181 }
@@ -1,608 +1,608
1 package org.implab.gradle.common.sources;
1 package org.implab.gradle.common.sources;
2
2
3 import static org.junit.jupiter.api.Assertions.assertNotNull;
3 import static org.junit.jupiter.api.Assertions.assertNotNull;
4 import static org.junit.jupiter.api.Assertions.assertThrows;
4 import static org.junit.jupiter.api.Assertions.assertThrows;
5 import static org.junit.jupiter.api.Assertions.assertTrue;
5 import static org.junit.jupiter.api.Assertions.assertTrue;
6
6
7 import java.io.File;
7 import java.io.File;
8 import java.io.IOException;
8 import java.io.IOException;
9 import java.nio.file.Files;
9 import java.nio.file.Files;
10 import java.nio.file.Path;
10 import java.nio.file.Path;
11 import java.util.List;
11 import java.util.List;
12 import java.util.stream.Collectors;
12 import java.util.stream.Collectors;
13
13
14 import org.gradle.testkit.runner.BuildResult;
14 import org.gradle.testkit.runner.BuildResult;
15 import org.gradle.testkit.runner.GradleRunner;
15 import org.gradle.testkit.runner.GradleRunner;
16 import org.gradle.testkit.runner.TaskOutcome;
16 import org.gradle.testkit.runner.TaskOutcome;
17 import org.gradle.testkit.runner.UnexpectedBuildFailure;
17 import org.gradle.testkit.runner.UnexpectedBuildFailure;
18 import org.junit.jupiter.api.Test;
18 import org.junit.jupiter.api.Test;
19 import org.junit.jupiter.api.io.TempDir;
19 import org.junit.jupiter.api.io.TempDir;
20
20
21 class VariantsArtifactsPluginFunctionalTest {
21 class VariantsArtifactsPluginFunctionalTest {
22 private static final String SETTINGS_FILE = "settings.gradle";
22 private static final String SETTINGS_FILE = "settings.gradle";
23 private static final String BUILD_FILE = "build.gradle";
23 private static final String BUILD_FILE = "build.gradle";
24 private static final String ROOT_NAME = "rootProject.name = 'variants-artifacts-fixture'\n";
24 private static final String ROOT_NAME = "rootProject.name = 'variants-artifacts-fixture'\n";
25
25
26 @TempDir
26 @TempDir
27 Path testProjectDir;
27 Path testProjectDir;
28
28
29 @Test
29 @Test
30 void materializesVariantArtifactsAndInvokesOutgoingHooks() throws Exception {
30 void materializesVariantArtifactsAndInvokesOutgoingHooks() throws Exception {
31 writeFile(SETTINGS_FILE, ROOT_NAME);
31 writeFile(SETTINGS_FILE, ROOT_NAME);
32 writeFile("inputs/base.js", "console.log('base')\n");
32 writeFile("inputs/base.js", "console.log('base')\n");
33 writeFile("inputs/amd.js", "console.log('amd')\n");
33 writeFile("inputs/amd.js", "console.log('amd')\n");
34 writeFile("inputs/mainJs.txt", "mainJs marker\n");
34 writeFile("inputs/mainJs.txt", "mainJs marker\n");
35 writeFile("inputs/amdJs.txt", "amdJs marker\n");
35 writeFile("inputs/amdJs.txt", "amdJs marker\n");
36 writeFile(BUILD_FILE, """
36 writeFile(BUILD_FILE, """
37 import org.gradle.api.attributes.Attribute
37 import org.gradle.api.attributes.Attribute
38
38
39 plugins {
39 plugins {
40 id 'org.implab.gradle-variants-artifacts'
40 id 'org.implab.gradle-variants-artifacts'
41 }
41 }
42
42
43 variants {
43 variants {
44 layer('mainBase')
44 layer('mainBase')
45 layer('mainAmd')
45 layer('mainAmd')
46
46
47 variant('browser') {
47 variant('browser') {
48 role('main') {
48 role('main') {
49 layers('mainBase', 'mainAmd')
49 layers('mainBase', 'mainAmd')
50 }
50 }
51 }
51 }
52 }
52 }
53
53
54 variantSources {
54 variantSources {
55 bind('mainBase') {
55 bind('mainBase') {
56 configureSourceSet {
56 configureSourceSet {
57 declareOutputs('js')
57 declareOutputs('js')
58 }
58 }
59 }
59 }
60
60
61 bind('mainAmd') {
61 bind('mainAmd') {
62 configureSourceSet {
62 configureSourceSet {
63 declareOutputs('js')
63 declareOutputs('js')
64 }
64 }
65 }
65 }
66
66
67 whenBound { ctx ->
67 whenBound { ctx ->
68 if (ctx.sourceSetName() == 'browserMainBase') {
68 if (ctx.sourceSetName() == 'browserMainBase') {
69 ctx.configureSourceSet {
69 ctx.configureSourceSet {
70 registerOutput('js', layout.projectDirectory.file('inputs/base.js'))
70 registerOutput('js', layout.projectDirectory.file('inputs/base.js'))
71 }
71 }
72 }
72 }
73
73
74 if (ctx.sourceSetName() == 'browserMainAmd') {
74 if (ctx.sourceSetName() == 'browserMainAmd') {
75 ctx.configureSourceSet {
75 ctx.configureSourceSet {
76 registerOutput('js', layout.projectDirectory.file('inputs/amd.js'))
76 registerOutput('js', layout.projectDirectory.file('inputs/amd.js'))
77 }
77 }
78 }
78 }
79 }
79 }
80 }
80 }
81
81
82 variantArtifacts {
82 variantArtifacts {
83 variant('browser') {
83 variant('browser') {
84 primarySlot('mainJs') {
84 primarySlot('mainJs') {
85 fromRole('main') {
85 fromRole('main') {
86 output('js')
86 output('js')
87 }
87 }
88 }
88 }
89
89
90 slot('amdJs') {
90 slot('amdJs') {
91 fromLayer('mainAmd') {
91 fromLayer('mainAmd') {
92 output('js')
92 output('js')
93 }
93 }
94 }
94 }
95 }
95 }
96
96
97 whenOutgoingVariant { publication ->
97 whenOutgoingVariant { publication ->
98 publication.slots().each { slotPublication ->
98 publication.slots().each { slotPublication ->
99 slotPublication.configureAssembly {
99 slotPublication.configureTask {
100 sources.from(layout.projectDirectory.file("inputs/${slotPublication.slotName()}.txt"))
100 from(layout.projectDirectory.file("inputs/${slotPublication.slotName()}.txt"))
101 }
101 }
102
102
103 slotPublication.configureArtifactAttributes {
103 slotPublication.configureArtifactAttributes {
104 attribute(Attribute.of('test.slot', String), slotPublication.slotName())
104 attribute(Attribute.of('test.slot', String), slotPublication.slotName())
105 }
105 }
106 }
106 }
107 }
107 }
108 }
108 }
109
109
110 tasks.register('probe') {
110 tasks.register('probe') {
111 dependsOn 'processBrowserMainJs', 'processBrowserAmdJs'
111 dependsOn 'processBrowserMainJs', 'processBrowserAmdJs'
112
112
113 doLast {
113 doLast {
114 def mainDir = layout.buildDirectory.dir('variant-artifacts/browser/mainJs').get().asFile
114 def mainDir = layout.buildDirectory.dir('variant-artifacts/browser/mainJs').get().asFile
115 def amdDir = layout.buildDirectory.dir('variant-artifacts/browser/amdJs').get().asFile
115 def amdDir = layout.buildDirectory.dir('variant-artifacts/browser/amdJs').get().asFile
116
116
117 assert new File(mainDir, 'base.js').exists()
117 assert new File(mainDir, 'base.js').exists()
118 assert new File(mainDir, 'amd.js').exists()
118 assert new File(mainDir, 'amd.js').exists()
119 assert new File(mainDir, 'mainJs.txt').exists()
119 assert new File(mainDir, 'mainJs.txt').exists()
120
120
121 assert !new File(amdDir, 'base.js').exists()
121 assert !new File(amdDir, 'base.js').exists()
122 assert new File(amdDir, 'amd.js').exists()
122 assert new File(amdDir, 'amd.js').exists()
123 assert new File(amdDir, 'amdJs.txt').exists()
123 assert new File(amdDir, 'amdJs.txt').exists()
124
124
125 def elements = configurations.getByName('browserElements')
125 def elements = configurations.getByName('browserElements')
126 def primaryAttr = elements.attributes.getAttribute(Attribute.of('test.slot', String))
126 def primaryAttr = elements.attributes.getAttribute(Attribute.of('test.slot', String))
127 def amdVariant = elements.outgoing.variants.getByName('amdJs')
127 def amdVariant = elements.outgoing.variants.getByName('amdJs')
128 def amdAttr = amdVariant.attributes.getAttribute(Attribute.of('test.slot', String))
128 def amdAttr = amdVariant.attributes.getAttribute(Attribute.of('test.slot', String))
129
129
130 println('primarySlot=' + variantArtifacts.requireVariant('browser').requirePrimarySlotName())
130 println('primarySlot=' + variantArtifacts.requireVariant('browser').requirePrimarySlotName())
131 println('primaryAttr=' + primaryAttr)
131 println('primaryAttr=' + primaryAttr)
132 println('amdAttr=' + amdAttr)
132 println('amdAttr=' + amdAttr)
133 println('configurations=' + configurations.matching { it.name == 'browserElements' }.collect { it.name }.join(','))
133 println('configurations=' + configurations.matching { it.name == 'browserElements' }.collect { it.name }.join(','))
134 println('secondaryVariants=' + elements.outgoing.variants.collect { it.name }.sort().join(','))
134 println('secondaryVariants=' + elements.outgoing.variants.collect { it.name }.sort().join(','))
135 }
135 }
136 }
136 }
137 """);
137 """);
138
138
139 BuildResult result = runner("probe").build();
139 BuildResult result = runner("probe").build();
140
140
141 assertTrue(result.getOutput().contains("primarySlot=mainJs"));
141 assertTrue(result.getOutput().contains("primarySlot=mainJs"));
142 assertTrue(result.getOutput().contains("primaryAttr=mainJs"));
142 assertTrue(result.getOutput().contains("primaryAttr=mainJs"));
143 assertTrue(result.getOutput().contains("amdAttr=amdJs"));
143 assertTrue(result.getOutput().contains("amdAttr=amdJs"));
144 assertTrue(result.getOutput().contains("configurations=browserElements"));
144 assertTrue(result.getOutput().contains("configurations=browserElements"));
145 assertTrue(result.getOutput().contains("secondaryVariants=amdJs"));
145 assertTrue(result.getOutput().contains("secondaryVariants=amdJs"));
146 assertTrue(result.task(":probe").getOutcome() == TaskOutcome.SUCCESS);
146 assertTrue(result.task(":probe").getOutcome() == TaskOutcome.SUCCESS);
147 }
147 }
148
148
149 @Test
149 @Test
150 void allowsSingleSlotVariantWithoutExplicitPrimarySlot() throws Exception {
150 void allowsSingleSlotVariantWithoutExplicitPrimarySlot() throws Exception {
151 writeFile(SETTINGS_FILE, ROOT_NAME);
151 writeFile(SETTINGS_FILE, ROOT_NAME);
152 writeFile(BUILD_FILE, """
152 writeFile(BUILD_FILE, """
153 plugins {
153 plugins {
154 id 'org.implab.gradle-variants-artifacts'
154 id 'org.implab.gradle-variants-artifacts'
155 }
155 }
156
156
157 variants {
157 variants {
158 layer('main')
158 layer('main')
159
159
160 variant('browser') {
160 variant('browser') {
161 role('main') {
161 role('main') {
162 layers('main')
162 layers('main')
163 }
163 }
164 }
164 }
165 }
165 }
166
166
167 variantArtifacts {
167 variantArtifacts {
168 variant('browser') {
168 variant('browser') {
169 slot('typesPackage') {
169 slot('typesPackage') {
170 fromVariant {
170 fromVariant {
171 output('types')
171 output('types')
172 }
172 }
173 }
173 }
174 }
174 }
175 }
175 }
176
176
177 tasks.register('probe') {
177 tasks.register('probe') {
178 doLast {
178 doLast {
179 println('primary=' + variantArtifacts.requireVariant('browser').requirePrimarySlotName())
179 println('primary=' + variantArtifacts.requireVariant('browser').requirePrimarySlotName())
180 }
180 }
181 }
181 }
182 """);
182 """);
183
183
184 BuildResult result = runner("probe").build();
184 BuildResult result = runner("probe").build();
185 assertTrue(result.getOutput().contains("primary=typesPackage"));
185 assertTrue(result.getOutput().contains("primary=typesPackage"));
186 }
186 }
187
187
188 @Test
188 @Test
189 void materializesDirectSlotInputsWithoutVariantSourceBindings() throws Exception {
189 void materializesDirectSlotInputsWithoutVariantSourceBindings() throws Exception {
190 writeFile(SETTINGS_FILE, ROOT_NAME);
190 writeFile(SETTINGS_FILE, ROOT_NAME);
191 writeFile("inputs/bundle.js", "console.log('bundle')\n");
191 writeFile("inputs/bundle.js", "console.log('bundle')\n");
192 writeFile(BUILD_FILE, """
192 writeFile(BUILD_FILE, """
193 plugins {
193 plugins {
194 id 'org.implab.gradle-variants-artifacts'
194 id 'org.implab.gradle-variants-artifacts'
195 }
195 }
196
196
197 variants {
197 variants {
198 layer('main')
198 layer('main')
199
199
200 variant('browser') {
200 variant('browser') {
201 role('main') {
201 role('main') {
202 layers('main')
202 layers('main')
203 }
203 }
204 }
204 }
205 }
205 }
206
206
207 variantArtifacts {
207 variantArtifacts {
208 variant('browser') {
208 variant('browser') {
209 primarySlot('bundle') {
209 primarySlot('bundle') {
210 from(layout.projectDirectory.file('inputs/bundle.js'))
210 from(layout.projectDirectory.file('inputs/bundle.js'))
211 }
211 }
212 }
212 }
213 }
213 }
214
214
215 tasks.register('probe') {
215 tasks.register('probe') {
216 dependsOn 'processBrowserBundle'
216 dependsOn 'processBrowserBundle'
217
217
218 doLast {
218 doLast {
219 def bundleDir = layout.buildDirectory.dir('variant-artifacts/browser/bundle').get().asFile
219 def bundleDir = layout.buildDirectory.dir('variant-artifacts/browser/bundle').get().asFile
220 assert new File(bundleDir, 'bundle.js').exists()
220 assert new File(bundleDir, 'bundle.js').exists()
221 println('primary=' + variantArtifacts.requireVariant('browser').requirePrimarySlotName())
221 println('primary=' + variantArtifacts.requireVariant('browser').requirePrimarySlotName())
222 }
222 }
223 }
223 }
224 """);
224 """);
225
225
226 BuildResult result = runner("probe").build();
226 BuildResult result = runner("probe").build();
227
227
228 assertTrue(result.getOutput().contains("primary=bundle"));
228 assertTrue(result.getOutput().contains("primary=bundle"));
229 assertTrue(result.task(":probe").getOutcome() == TaskOutcome.SUCCESS);
229 assertTrue(result.task(":probe").getOutcome() == TaskOutcome.SUCCESS);
230 }
230 }
231
231
232 @Test
232 @Test
233 void combinesDirectAndTopologyAwareSlotInputs() throws Exception {
233 void combinesDirectAndTopologyAwareSlotInputs() throws Exception {
234 writeFile(SETTINGS_FILE, ROOT_NAME);
234 writeFile(SETTINGS_FILE, ROOT_NAME);
235 writeFile("inputs/base.js", "console.log('base')\n");
235 writeFile("inputs/base.js", "console.log('base')\n");
236 writeFile("inputs/marker.txt", "marker\n");
236 writeFile("inputs/marker.txt", "marker\n");
237 writeFile(BUILD_FILE, """
237 writeFile(BUILD_FILE, """
238 plugins {
238 plugins {
239 id 'org.implab.gradle-variants-artifacts'
239 id 'org.implab.gradle-variants-artifacts'
240 }
240 }
241
241
242 variants {
242 variants {
243 layer('main')
243 layer('main')
244
244
245 variant('browser') {
245 variant('browser') {
246 role('main') {
246 role('main') {
247 layers('main')
247 layers('main')
248 }
248 }
249 }
249 }
250 }
250 }
251
251
252 variantSources {
252 variantSources {
253 bind('main') {
253 bind('main') {
254 configureSourceSet {
254 configureSourceSet {
255 declareOutputs('js')
255 declareOutputs('js')
256 }
256 }
257 }
257 }
258
258
259 whenBound { ctx ->
259 whenBound { ctx ->
260 ctx.configureSourceSet {
260 ctx.configureSourceSet {
261 registerOutput('js', layout.projectDirectory.file('inputs/base.js'))
261 registerOutput('js', layout.projectDirectory.file('inputs/base.js'))
262 }
262 }
263 }
263 }
264 }
264 }
265
265
266 variantArtifacts {
266 variantArtifacts {
267 variant('browser') {
267 variant('browser') {
268 primarySlot('bundle') {
268 primarySlot('bundle') {
269 fromVariant {
269 fromVariant {
270 output('js')
270 output('js')
271 }
271 }
272 from(layout.projectDirectory.file('inputs/marker.txt'))
272 from(layout.projectDirectory.file('inputs/marker.txt'))
273 }
273 }
274 }
274 }
275 }
275 }
276
276
277 tasks.register('probe') {
277 tasks.register('probe') {
278 dependsOn 'processBrowserBundle'
278 dependsOn 'processBrowserBundle'
279
279
280 doLast {
280 doLast {
281 def bundleDir = layout.buildDirectory.dir('variant-artifacts/browser/bundle').get().asFile
281 def bundleDir = layout.buildDirectory.dir('variant-artifacts/browser/bundle').get().asFile
282 assert new File(bundleDir, 'base.js').exists()
282 assert new File(bundleDir, 'base.js').exists()
283 assert new File(bundleDir, 'marker.txt').exists()
283 assert new File(bundleDir, 'marker.txt').exists()
284 }
284 }
285 }
285 }
286 """);
286 """);
287
287
288 BuildResult result = runner("probe").build();
288 BuildResult result = runner("probe").build();
289
289
290 assertTrue(result.task(":probe").getOutcome() == TaskOutcome.SUCCESS);
290 assertTrue(result.task(":probe").getOutcome() == TaskOutcome.SUCCESS);
291 }
291 }
292
292
293 @Test
293 @Test
294 void failsOnUnknownVariantReference() throws Exception {
294 void failsOnUnknownVariantReference() throws Exception {
295 assertBuildFails("""
295 assertBuildFails("""
296 plugins {
296 plugins {
297 id 'org.implab.gradle-variants-artifacts'
297 id 'org.implab.gradle-variants-artifacts'
298 }
298 }
299
299
300 variants {
300 variants {
301 layer('main')
301 layer('main')
302 }
302 }
303
303
304 variantArtifacts {
304 variantArtifacts {
305 variant('browser') {
305 variant('browser') {
306 slot('mainJs') {
306 slot('mainJs') {
307 fromVariant {
307 fromVariant {
308 output('js')
308 output('js')
309 }
309 }
310 }
310 }
311 }
311 }
312 }
312 }
313 """, "Variant artifact 'browser' references unknown variant 'browser'");
313 """, "Variant artifact 'browser' references unknown variant 'browser'");
314 }
314 }
315
315
316 @Test
316 @Test
317 void failsOnUnknownRoleReference() throws Exception {
317 void failsOnUnknownRoleReference() throws Exception {
318 assertBuildFails("""
318 assertBuildFails("""
319 plugins {
319 plugins {
320 id 'org.implab.gradle-variants-artifacts'
320 id 'org.implab.gradle-variants-artifacts'
321 }
321 }
322
322
323 variants {
323 variants {
324 layer('main')
324 layer('main')
325
325
326 variant('browser') {
326 variant('browser') {
327 role('main') {
327 role('main') {
328 layers('main')
328 layers('main')
329 }
329 }
330 }
330 }
331 }
331 }
332
332
333 variantArtifacts {
333 variantArtifacts {
334 variant('browser') {
334 variant('browser') {
335 slot('mainJs') {
335 slot('mainJs') {
336 fromRole('test') {
336 fromRole('test') {
337 output('js')
337 output('js')
338 }
338 }
339 }
339 }
340 }
340 }
341 }
341 }
342 """, "Variant artifact 'browser', slot 'mainJs' references unknown role 'test'");
342 """, "Variant artifact 'browser', slot 'mainJs' references unknown role 'test'");
343 }
343 }
344
344
345 @Test
345 @Test
346 void failsWhenPrimarySlotIsMissingForMultipleSlots() throws Exception {
346 void failsWhenPrimarySlotIsMissingForMultipleSlots() throws Exception {
347 assertBuildFails("""
347 assertBuildFails("""
348 plugins {
348 plugins {
349 id 'org.implab.gradle-variants-artifacts'
349 id 'org.implab.gradle-variants-artifacts'
350 }
350 }
351
351
352 variants {
352 variants {
353 layer('main')
353 layer('main')
354
354
355 variant('browser') {
355 variant('browser') {
356 role('main') {
356 role('main') {
357 layers('main')
357 layers('main')
358 }
358 }
359 }
359 }
360 }
360 }
361
361
362 variantArtifacts {
362 variantArtifacts {
363 variant('browser') {
363 variant('browser') {
364 slot('typesPackage') {
364 slot('typesPackage') {
365 fromVariant {
365 fromVariant {
366 output('types')
366 output('types')
367 }
367 }
368 }
368 }
369
369
370 slot('js') {
370 slot('js') {
371 fromVariant {
371 fromVariant {
372 output('js')
372 output('js')
373 }
373 }
374 }
374 }
375 }
375 }
376 }
376 }
377 """, "Variant artifact 'browser' must declare primary slot because it has multiple slots");
377 """, "Variant artifact 'browser' must declare primary slot because it has multiple slots");
378 }
378 }
379
379
380 @Test
380 @Test
381 void failsOnLayerReferenceOutsideVariantTopology() throws Exception {
381 void failsOnLayerReferenceOutsideVariantTopology() throws Exception {
382 assertBuildFails("""
382 assertBuildFails("""
383 plugins {
383 plugins {
384 id 'org.implab.gradle-variants-artifacts'
384 id 'org.implab.gradle-variants-artifacts'
385 }
385 }
386
386
387 variants {
387 variants {
388 layer('mainBase')
388 layer('mainBase')
389 layer('extra')
389 layer('extra')
390
390
391 variant('browser') {
391 variant('browser') {
392 role('main') {
392 role('main') {
393 layers('mainBase')
393 layers('mainBase')
394 }
394 }
395 }
395 }
396 }
396 }
397
397
398 variantArtifacts {
398 variantArtifacts {
399 variant('browser') {
399 variant('browser') {
400 slot('extraJs') {
400 slot('extraJs') {
401 fromLayer('extra') {
401 fromLayer('extra') {
402 output('js')
402 output('js')
403 }
403 }
404 }
404 }
405 }
405 }
406 }
406 }
407 """, "Variant artifact 'browser', slot 'extraJs' references unknown layer 'extra'");
407 """, "Variant artifact 'browser', slot 'extraJs' references unknown layer 'extra'");
408 }
408 }
409
409
410 @Test
410 @Test
411 void failsOnLateMutationAfterFinalize() throws Exception {
411 void failsOnLateMutationAfterFinalize() throws Exception {
412 assertBuildFails("""
412 assertBuildFails("""
413 plugins {
413 plugins {
414 id 'org.implab.gradle-variants-artifacts'
414 id 'org.implab.gradle-variants-artifacts'
415 }
415 }
416
416
417 variants {
417 variants {
418 layer('main')
418 layer('main')
419
419
420 variant('browser') {
420 variant('browser') {
421 role('main') {
421 role('main') {
422 layers('main')
422 layers('main')
423 }
423 }
424 }
424 }
425 }
425 }
426
426
427 afterEvaluate {
427 afterEvaluate {
428 variantArtifacts.variant('late') {
428 variantArtifacts.variant('late') {
429 slot('js') {
429 slot('js') {
430 fromVariant {
430 fromVariant {
431 output('js')
431 output('js')
432 }
432 }
433 }
433 }
434 }
434 }
435 }
435 }
436 """, "variantArtifacts model is finalized and cannot configure variants");
436 """, "variantArtifacts model is finalized and cannot configure variants");
437 }
437 }
438
438
439 @Test
439 @Test
440 void preservesPrimaryResolutionAndAllowsSecondaryArtifactSelection() throws Exception {
440 void preservesPrimaryResolutionAndAllowsSecondaryArtifactSelection() throws Exception {
441 writeFile(SETTINGS_FILE, """
441 writeFile(SETTINGS_FILE, """
442 rootProject.name = 'variants-artifacts-fixture'
442 rootProject.name = 'variants-artifacts-fixture'
443 include 'producer', 'consumer'
443 include 'producer', 'consumer'
444 """);
444 """);
445 writeFile("producer/inputs/types.d.ts", "export type Foo = string\n");
445 writeFile("producer/inputs/types.d.ts", "export type Foo = string\n");
446 writeFile("producer/inputs/index.js", "export const foo = 'bar'\n");
446 writeFile("producer/inputs/index.js", "export const foo = 'bar'\n");
447 var buildscriptClasspath = pluginClasspath().stream()
447 var buildscriptClasspath = pluginClasspath().stream()
448 .map(File::getAbsolutePath)
448 .map(File::getAbsolutePath)
449 .map(path -> "'" + path.replace("\\", "\\\\") + "'")
449 .map(path -> "'" + path.replace("\\", "\\\\") + "'")
450 .collect(Collectors.joining(", "));
450 .collect(Collectors.joining(", "));
451 writeFile(BUILD_FILE, """
451 writeFile(BUILD_FILE, """
452 buildscript {
452 buildscript {
453 dependencies {
453 dependencies {
454 classpath files(%s)
454 classpath files(%s)
455 }
455 }
456 }
456 }
457
457
458 import org.gradle.api.attributes.Attribute
458 import org.gradle.api.attributes.Attribute
459
459
460 def variantAttr = Attribute.of('test.variant', String)
460 def variantAttr = Attribute.of('test.variant', String)
461 def slotAttr = Attribute.of('test.slot', String)
461 def slotAttr = Attribute.of('test.slot', String)
462
462
463 subprojects {
463 subprojects {
464 apply plugin: 'org.implab.gradle-variants-artifacts'
464 apply plugin: 'org.implab.gradle-variants-artifacts'
465 }
465 }
466
466
467 project(':producer') {
467 project(':producer') {
468 variants {
468 variants {
469 layer('main')
469 layer('main')
470
470
471 variant('browser') {
471 variant('browser') {
472 role('main') {
472 role('main') {
473 layers('main')
473 layers('main')
474 }
474 }
475 }
475 }
476 }
476 }
477
477
478 variantSources {
478 variantSources {
479 bind('main') {
479 bind('main') {
480 configureSourceSet {
480 configureSourceSet {
481 declareOutputs('types', 'js')
481 declareOutputs('types', 'js')
482 }
482 }
483 }
483 }
484
484
485 whenBound { ctx ->
485 whenBound { ctx ->
486 ctx.configureSourceSet {
486 ctx.configureSourceSet {
487 registerOutput('types', layout.projectDirectory.file('inputs/types.d.ts'))
487 registerOutput('types', layout.projectDirectory.file('inputs/types.d.ts'))
488 registerOutput('js', layout.projectDirectory.file('inputs/index.js'))
488 registerOutput('js', layout.projectDirectory.file('inputs/index.js'))
489 }
489 }
490 }
490 }
491 }
491 }
492
492
493 variantArtifacts {
493 variantArtifacts {
494 variant('browser') {
494 variant('browser') {
495 primarySlot('typesPackage') {
495 primarySlot('typesPackage') {
496 fromVariant {
496 fromVariant {
497 output('types')
497 output('types')
498 }
498 }
499 }
499 }
500
500
501 slot('js') {
501 slot('js') {
502 fromVariant {
502 fromVariant {
503 output('js')
503 output('js')
504 }
504 }
505 }
505 }
506 }
506 }
507
507
508 whenOutgoingVariant { publication ->
508 whenOutgoingVariant { publication ->
509 publication.configureConfiguration {
509 publication.configureConfiguration {
510 attributes.attribute(variantAttr, publication.variantName())
510 attributes.attribute(variantAttr, publication.variantName())
511 }
511 }
512
512
513 publication.primarySlot().configureArtifactAttributes {
513 publication.primarySlot().configureArtifactAttributes {
514 attribute(slotAttr, publication.primarySlot().slotName())
514 attribute(slotAttr, publication.primarySlot().slotName())
515 }
515 }
516
516
517 publication.requireSlot('js').configureArtifactAttributes {
517 publication.requireSlot('js').configureArtifactAttributes {
518 attribute(slotAttr, 'js')
518 attribute(slotAttr, 'js')
519 }
519 }
520 }
520 }
521 }
521 }
522 }
522 }
523
523
524 project(':consumer') {
524 project(':consumer') {
525 configurations {
525 configurations {
526 compileView {
526 compileView {
527 canBeResolved = true
527 canBeResolved = true
528 canBeConsumed = false
528 canBeConsumed = false
529 canBeDeclared = true
529 canBeDeclared = true
530 attributes {
530 attributes {
531 attribute(variantAttr, 'browser')
531 attribute(variantAttr, 'browser')
532 attribute(slotAttr, 'typesPackage')
532 attribute(slotAttr, 'typesPackage')
533 }
533 }
534 }
534 }
535 }
535 }
536
536
537 dependencies {
537 dependencies {
538 compileView project(':producer')
538 compileView project(':producer')
539 }
539 }
540
540
541 tasks.register('probe') {
541 tasks.register('probe') {
542 doLast {
542 doLast {
543 def compileFiles = configurations.compileView.files.collect { it.name }.sort().join(',')
543 def compileFiles = configurations.compileView.files.collect { it.name }.sort().join(',')
544 def jsFiles = configurations.compileView.incoming.artifactView {
544 def jsFiles = configurations.compileView.incoming.artifactView {
545 attributes {
545 attributes {
546 attribute(slotAttr, 'js')
546 attribute(slotAttr, 'js')
547 }
547 }
548 }.files.files.collect { it.name }.sort().join(',')
548 }.files.files.collect { it.name }.sort().join(',')
549
549
550 println('compileFiles=' + compileFiles)
550 println('compileFiles=' + compileFiles)
551 println('jsFiles=' + jsFiles)
551 println('jsFiles=' + jsFiles)
552 }
552 }
553 }
553 }
554 }
554 }
555 """.formatted(buildscriptClasspath));
555 """.formatted(buildscriptClasspath));
556
556
557 BuildResult result = runner(":consumer:probe").build();
557 BuildResult result = runner(":consumer:probe").build();
558
558
559 assertTrue(result.getOutput().contains("compileFiles=typesPackage"));
559 assertTrue(result.getOutput().contains("compileFiles=typesPackage"));
560 assertTrue(result.getOutput().contains("jsFiles=js"));
560 assertTrue(result.getOutput().contains("jsFiles=js"));
561 }
561 }
562
562
563 private GradleRunner runner(String... arguments) {
563 private GradleRunner runner(String... arguments) {
564 return GradleRunner.create()
564 return GradleRunner.create()
565 .withProjectDir(testProjectDir.toFile())
565 .withProjectDir(testProjectDir.toFile())
566 .withPluginClasspath(pluginClasspath())
566 .withPluginClasspath(pluginClasspath())
567 .withArguments(arguments)
567 .withArguments(arguments)
568 .forwardOutput();
568 .forwardOutput();
569 }
569 }
570
570
571 private void assertBuildFails(String buildScript, String expectedError) throws Exception {
571 private void assertBuildFails(String buildScript, String expectedError) throws Exception {
572 writeFile(SETTINGS_FILE, ROOT_NAME);
572 writeFile(SETTINGS_FILE, ROOT_NAME);
573 writeFile(BUILD_FILE, buildScript);
573 writeFile(BUILD_FILE, buildScript);
574
574
575 var ex = assertThrows(UnexpectedBuildFailure.class, () -> runner("help").build());
575 var ex = assertThrows(UnexpectedBuildFailure.class, () -> runner("help").build());
576 var output = ex.getBuildResult().getOutput();
576 var output = ex.getBuildResult().getOutput();
577
577
578 assertTrue(output.contains(expectedError), () -> "Expected [" + expectedError + "] in output:\n" + output);
578 assertTrue(output.contains(expectedError), () -> "Expected [" + expectedError + "] in output:\n" + output);
579 }
579 }
580
580
581 private static List<File> pluginClasspath() {
581 private static List<File> pluginClasspath() {
582 try {
582 try {
583 var classesDir = Path.of(VariantArtifactsPlugin.class
583 var classesDir = Path.of(VariantArtifactsPlugin.class
584 .getProtectionDomain()
584 .getProtectionDomain()
585 .getCodeSource()
585 .getCodeSource()
586 .getLocation()
586 .getLocation()
587 .toURI());
587 .toURI());
588
588
589 var markerResource = VariantArtifactsPlugin.class.getClassLoader()
589 var markerResource = VariantArtifactsPlugin.class.getClassLoader()
590 .getResource("META-INF/gradle-plugins/org.implab.gradle-variants-artifacts.properties");
590 .getResource("META-INF/gradle-plugins/org.implab.gradle-variants-artifacts.properties");
591
591
592 assertNotNull(markerResource, "Plugin marker resource is missing from test classpath");
592 assertNotNull(markerResource, "Plugin marker resource is missing from test classpath");
593
593
594 var markerPath = Path.of(markerResource.toURI());
594 var markerPath = Path.of(markerResource.toURI());
595 var resourcesDir = markerPath.getParent().getParent().getParent();
595 var resourcesDir = markerPath.getParent().getParent().getParent();
596
596
597 return List.of(classesDir.toFile(), resourcesDir.toFile());
597 return List.of(classesDir.toFile(), resourcesDir.toFile());
598 } catch (Exception e) {
598 } catch (Exception e) {
599 throw new RuntimeException("Unable to build plugin classpath for test", e);
599 throw new RuntimeException("Unable to build plugin classpath for test", e);
600 }
600 }
601 }
601 }
602
602
603 private void writeFile(String relativePath, String content) throws IOException {
603 private void writeFile(String relativePath, String content) throws IOException {
604 Path path = testProjectDir.resolve(relativePath);
604 Path path = testProjectDir.resolve(relativePath);
605 Files.createDirectories(path.getParent());
605 Files.createDirectories(path.getParent());
606 Files.writeString(path, content);
606 Files.writeString(path, content);
607 }
607 }
608 }
608 }
General Comments 0
You need to be logged in to leave comments. Login now