| @@ -281,6 +281,16 variantArtifacts { | |||||
| 281 | } |
|
281 | } | |
| 282 | ``` |
|
282 | ``` | |
| 283 |
|
283 | |||
|
|
284 | Slot bodies have two assembly modes: | |||
|
|
285 | ||||
|
|
286 | - contribution-based assembly with `from(...)`, `fromVariant(...)`, | |||
|
|
287 | `fromRole(...)`, or `fromLayer(...)`; the plugin copies selected inputs into a | |||
|
|
288 | managed directory and publishes that directory; | |||
|
|
289 | - task-produced assembly with `producedBy(task) { outputFile }`; the mapped task | |||
|
|
290 | output file or directory is published directly. | |||
|
|
291 | ||||
|
|
292 | These modes are mutually exclusive for one slot. | |||
|
|
293 | ||||
| 284 | The artifact API is still considered pre-1.0 and may change. |
|
294 | The artifact API is still considered pre-1.0 and may change. | |
| 285 |
|
295 | |||
| 286 | ## Publication Status |
|
296 | ## Publication Status | |
| @@ -279,6 +279,9 final class VariantArtifactsRegistry imp | |||||
| 279 |
|
279 | |||
| 280 | interface ArtifactAssemblyRules { |
|
280 | interface ArtifactAssemblyRules { | |
| 281 | void from(Object input); |
|
281 | void from(Object input); | |
|
|
282 | <T extends Task> void producedBy( | |||
|
|
283 | TaskProvider<T> task, | |||
|
|
284 | Function<? super T, ? extends Provider<? extends FileSystemLocation>> output); | |||
| 282 | void fromVariant(Action<? super OutputSelectionSpec> action); |
|
285 | void fromVariant(Action<? super OutputSelectionSpec> action); | |
| 283 | void fromRole(String roleName, Action<? super OutputSelectionSpec> action); |
|
286 | void fromRole(String roleName, Action<? super OutputSelectionSpec> action); | |
| 284 | void fromLayer(String layerName, Action<? super OutputSelectionSpec> action); |
|
287 | void fromLayer(String layerName, Action<? super OutputSelectionSpec> action); | |
| @@ -339,6 +342,8 This means: | |||||
| 339 | - slot inputs remain live; |
|
342 | - slot inputs remain live; | |
| 340 | - `from(...)`, `fromVariant(...)`, `fromRole(...)`, `fromLayer(...)` may keep |
|
343 | - `from(...)`, `fromVariant(...)`, `fromRole(...)`, `fromLayer(...)` may keep | |
| 341 | contributing inputs until task execution; |
|
344 | contributing inputs until task execution; | |
|
|
345 | - `producedBy(...)` publishes an existing task output directly and does not | |||
|
|
346 | create the managed copy assembly for that slot; | |||
| 342 | - `ArtifactAssembly` may expose live `FileCollection`, `Provider`, and task |
|
347 | - `ArtifactAssembly` may expose live `FileCollection`, `Provider`, and task | |
| 343 | wiring; |
|
348 | wiring; | |
| 344 | - external task outputs remain outside the control of this model and must be |
|
349 | - external task outputs remain outside the control of this model and must be | |
| @@ -412,8 +417,9 variantArtifacts { | |||||
| 412 | } |
|
417 | } | |
| 413 |
|
418 | |||
| 414 | slot("bundleMetadata") { |
|
419 | slot("bundleMetadata") { | |
| 415 | from(someTask) |
|
420 | producedBy(writePackageMetadata) { | |
| 416 | from(layout.buildDirectory.file("generated/meta.json")) |
|
421 | outputFile | |
|
|
422 | } | |||
| 417 | } |
|
423 | } | |
| 418 | } |
|
424 | } | |
| 419 | } |
|
425 | } | |
| @@ -428,7 +434,10 variantArtifacts { | |||||
| 428 | belong to the given role projection; |
|
434 | belong to the given role projection; | |
| 429 | - `fromLayer(layer) { output(...) }` selects named outputs from the compile unit |
|
435 | - `fromLayer(layer) { output(...) }` selects named outputs from the compile unit | |
| 430 | of the current variant and the given layer, if such unit exists. |
|
436 | of the current variant and the given layer, if such unit exists. | |
|
|
437 | - `producedBy(task) { outputFile }` maps an existing producing task to the single | |||
|
|
438 | file or directory published for the slot. | |||
| 431 |
|
439 | |||
|
|
440 | Contribution forms and `producedBy(...)` are mutually exclusive for one slot. | |||
| 432 | The DSL stores declarations, not resolved file collections. |
|
441 | The DSL stores declarations, not resolved file collections. | |
| 433 |
|
442 | |||
| 434 | --- |
|
443 | --- | |
| @@ -499,6 +508,9 For one outgoing variant: | |||||
| 499 | - expands to one compile unit `(variant, layer)` when it exists; |
|
508 | - expands to one compile unit `(variant, layer)` when it exists; | |
| 500 | - `from(Object)` |
|
509 | - `from(Object)` | |
| 501 | - bypasses `variantSources` completely. |
|
510 | - bypasses `variantSources` completely. | |
|
|
511 | - `producedBy(task)` | |||
|
|
512 | - bypasses contribution resolution and registers the task output as the slot | |||
|
|
513 | artifact directly. | |||
| 502 |
|
514 | |||
| 503 | After compile units are known, the bridge asks |
|
515 | After compile units are known, the bridge asks | |
| 504 | `ctx.getSourceSets().getSourceSet(unit)` for each selected unit and resolves the |
|
516 | `ctx.getSourceSets().getSourceSet(unit)` for each selected unit and resolves the | |
| @@ -1,6 +1,13 | |||||
| 1 | package org.implab.gradle.variants.artifacts; |
|
1 | package org.implab.gradle.variants.artifacts; | |
| 2 |
|
2 | |||
|
|
3 | import java.util.function.Function; | |||
|
|
4 | ||||
| 3 | import org.gradle.api.Action; |
|
5 | import org.gradle.api.Action; | |
|
|
6 | import org.gradle.api.InvalidUserDataException; | |||
|
|
7 | import org.gradle.api.Task; | |||
|
|
8 | import org.gradle.api.file.FileSystemLocation; | |||
|
|
9 | import org.gradle.api.provider.Provider; | |||
|
|
10 | import org.gradle.api.tasks.TaskProvider; | |||
| 4 | import groovy.lang.Closure; |
|
11 | import groovy.lang.Closure; | |
| 5 | import org.implab.gradle.common.core.lang.Closures; |
|
12 | import org.implab.gradle.common.core.lang.Closures; | |
| 6 |
|
13 | |||
| @@ -24,6 +31,45 public interface ArtifactAssemblySpec { | |||||
| 24 | void from(Object artifact); |
|
31 | void from(Object artifact); | |
| 25 |
|
32 | |||
| 26 | /** |
|
33 | /** | |
|
|
34 | * Registers a task that directly produces the published slot artifact. | |||
|
|
35 | * | |||
|
|
36 | * <p>Use this method when the slot is produced as one file or directory by an | |||
|
|
37 | * existing task, for example generated package metadata. Unlike {@link #from(Object)} | |||
|
|
38 | * and topology-aware selectors, this does not copy inputs into a managed assembly | |||
|
|
39 | * directory. The mapped task output becomes the published artifact itself. | |||
|
|
40 | * | |||
|
|
41 | * <p>This mode is mutually exclusive with contribution-based assembly methods | |||
|
|
42 | * such as {@link #from(Object)}, {@link #fromVariant(Action)}, {@link #fromRole(String, Action)}, | |||
|
|
43 | * and {@link #fromLayer(String, Action)} for the same slot. | |||
|
|
44 | * | |||
|
|
45 | * @param <T> task type | |||
|
|
46 | * @param task task provider producing the artifact | |||
|
|
47 | * @param artifact maps the producing task to its output file or directory provider | |||
|
|
48 | */ | |||
|
|
49 | <T extends Task> void producedBy( | |||
|
|
50 | TaskProvider<T> task, | |||
|
|
51 | Function<? super T, ? extends Provider<? extends FileSystemLocation>> artifact); | |||
|
|
52 | ||||
|
|
53 | default <T extends Task> void producedBy(TaskProvider<T> task, Closure<?> closure) { | |||
|
|
54 | producedBy(task, taskInstance -> producedArtifact(closure, taskInstance)); | |||
|
|
55 | } | |||
|
|
56 | ||||
|
|
57 | @SuppressWarnings("unchecked") | |||
|
|
58 | private static Provider<? extends FileSystemLocation> producedArtifact(Closure<?> closure, Task task) { | |||
|
|
59 | var c = (Closure<?>) closure.clone(); | |||
|
|
60 | c.setResolveStrategy(Closure.DELEGATE_FIRST); | |||
|
|
61 | c.setDelegate(task); | |||
|
|
62 | ||||
|
|
63 | var artifact = c.call(task); | |||
|
|
64 | if (artifact instanceof Provider<?>) { | |||
|
|
65 | return (Provider<? extends FileSystemLocation>) artifact; | |||
|
|
66 | } | |||
|
|
67 | ||||
|
|
68 | throw new InvalidUserDataException("Produced artifact mapper for task '" + task.getName() | |||
|
|
69 | + "' must return Provider<? extends FileSystemLocation>"); | |||
|
|
70 | } | |||
|
|
71 | ||||
|
|
72 | /** | |||
| 27 | * Selects outputs from the whole variant scope. |
|
73 | * Selects outputs from the whole variant scope. | |
| 28 | * |
|
74 | * | |
| 29 | * @param action output selection rule |
|
75 | * @param action output selection rule | |
| @@ -32,7 +32,9 public class ArtifactAssemblyBinder impl | |||||
| 32 | // Bind the primary artifact set to the root outgoing configuration. |
|
32 | // Bind the primary artifact set to the root outgoing configuration. | |
| 33 | resolver.when( |
|
33 | resolver.when( | |
| 34 | new ArtifactSlot(variant, primarySlot), |
|
34 | new ArtifactSlot(variant, primarySlot), | |
| 35 |
assembly -> outgoing.artifact( |
|
35 | assembly -> outgoing.artifact( | |
|
|
36 | assembly.getArtifact(), | |||
|
|
37 | artifact -> artifact.builtBy(assembly.getAssemblyTask()))); | |||
| 36 |
|
38 | |||
| 37 | // Bind non-primary slots to Gradle secondary artifact variants. |
|
39 | // Bind non-primary slots to Gradle secondary artifact variants. | |
| 38 | slots.all(slot -> { |
|
40 | slots.all(slot -> { | |
| @@ -46,7 +48,9 public class ArtifactAssemblyBinder impl | |||||
| 46 | // otherwise be realized only after dependency resolution starts. |
|
48 | // otherwise be realized only after dependency resolution starts. | |
| 47 | assembly -> outgoing.getVariants() |
|
49 | assembly -> outgoing.getVariants() | |
| 48 | .create(slot.getName()) |
|
50 | .create(slot.getName()) | |
| 49 |
.artifact( |
|
51 | .artifact( | |
|
|
52 | assembly.getArtifact(), | |||
|
|
53 | artifact -> artifact.builtBy(assembly.getAssemblyTask()))); | |||
| 50 | }); |
|
54 | }); | |
| 51 | }); |
|
55 | }); | |
| 52 | } |
|
56 | } | |
| @@ -4,38 +4,57 import java.util.HashMap; | |||||
| 4 | import java.util.HashSet; |
|
4 | import java.util.HashSet; | |
| 5 | import java.util.Map; |
|
5 | import java.util.Map; | |
| 6 | import java.util.Set; |
|
6 | import java.util.Set; | |
|
|
7 | import java.util.function.Function; | |||
|
|
8 | import java.util.stream.Stream; | |||
| 7 |
|
9 | |||
| 8 | import org.eclipse.jdt.annotation.NonNullByDefault; |
|
10 | import org.eclipse.jdt.annotation.NonNullByDefault; | |
| 9 | import org.gradle.api.Action; |
|
11 | import org.gradle.api.Action; | |
|
|
12 | import org.gradle.api.InvalidUserDataException; | |||
|
|
13 | import org.gradle.api.Task; | |||
| 10 | import org.gradle.api.file.ConfigurableFileCollection; |
|
14 | import org.gradle.api.file.ConfigurableFileCollection; | |
| 11 | import org.gradle.api.file.Directory; |
|
15 | import org.gradle.api.file.Directory; | |
| 12 | import org.gradle.api.file.DirectoryProperty; |
|
16 | import org.gradle.api.file.DirectoryProperty; | |
| 13 | import org.gradle.api.file.FileCollection; |
|
17 | import org.gradle.api.file.FileCollection; | |
|
|
18 | import org.gradle.api.file.FileSystemLocation; | |||
| 14 | import org.gradle.api.model.ObjectFactory; |
|
19 | import org.gradle.api.model.ObjectFactory; | |
| 15 | import org.gradle.api.provider.Provider; |
|
20 | import org.gradle.api.provider.Provider; | |
| 16 | import org.gradle.api.tasks.Sync; |
|
21 | import org.gradle.api.tasks.Sync; | |
| 17 | import org.gradle.api.tasks.TaskContainer; |
|
22 | import org.gradle.api.tasks.TaskContainer; | |
|
|
23 | import org.gradle.api.tasks.TaskProvider; | |||
| 18 | import org.gradle.language.base.plugins.LifecycleBasePlugin; |
|
24 | import org.gradle.language.base.plugins.LifecycleBasePlugin; | |
| 19 | import org.implab.gradle.common.core.lang.FilePaths; |
|
25 | import org.implab.gradle.common.core.lang.FilePaths; | |
|
|
26 | import org.implab.gradle.common.core.lang.Strings; | |||
| 20 | import org.implab.gradle.variants.artifacts.ArtifactAssembly; |
|
27 | import org.implab.gradle.variants.artifacts.ArtifactAssembly; | |
| 21 | import org.implab.gradle.variants.artifacts.ArtifactAssemblySpec; |
|
28 | import org.implab.gradle.variants.artifacts.ArtifactAssemblySpec; | |
| 22 | import org.implab.gradle.variants.artifacts.ArtifactSlot; |
|
29 | import org.implab.gradle.variants.artifacts.ArtifactSlot; | |
|
|
30 | import org.implab.gradle.variants.artifacts.OutputSelectionSpec; | |||
|
|
31 | import org.implab.gradle.variants.core.Layer; | |||
|
|
32 | import org.implab.gradle.variants.core.Role; | |||
| 23 | import org.implab.gradle.variants.sources.CompileUnit; |
|
33 | import org.implab.gradle.variants.sources.CompileUnit; | |
| 24 | import org.implab.gradle.variants.sources.CompileUnitsView; |
|
34 | import org.implab.gradle.variants.sources.CompileUnitsView; | |
| 25 | import org.implab.gradle.variants.sources.RoleProjectionsView; |
|
35 | import org.implab.gradle.variants.sources.RoleProjectionsView; | |
| 26 | import org.implab.gradle.variants.sources.SourceSetMaterializer; |
|
36 | import org.implab.gradle.variants.sources.SourceSetMaterializer; | |
| 27 |
|
37 | |||
| 28 | /** |
|
38 | /** | |
| 29 |
* Adapts slot contribution declarations to materialized |
|
39 | * Adapts slot contribution declarations to materialized | |
|
|
40 | * {@link ArtifactAssembly} | |||
| 30 | * handles. |
|
41 | * handles. | |
| 31 | * |
|
42 | * | |
| 32 | * <p>The handler creates one {@link Sync} task per {@link ArtifactSlot}. The task |
|
43 | * <p> | |
| 33 | * copies all collected slot inputs into a single output directory. That output |
|
44 | * Contribution-based assemblies create one {@link Sync} task per | |
| 34 | * directory is then registered in {@link ArtifactAssemblyRegistry} as the |
|
45 | * {@link ArtifactSlot}. The task copies all collected slot inputs into a single | |
| 35 | * published artifact for the slot. |
|
46 | * output directory. That output directory is then registered in | |
|
|
47 | * {@link ArtifactAssemblyRegistry} as the published artifact for the slot. | |||
| 36 | * |
|
48 | * | |
| 37 | * <p>Input collection uses {@link SlotContributionVisitor}. Each contribution is |
|
49 | * <p> | |
| 38 | * converted to a {@link SlotInputKey}; duplicate keys are ignored so that repeated |
|
50 | * Task-produced assemblies bypass the managed copy task. The producer task is | |
|
|
51 | * registered directly in {@link ArtifactAssemblyRegistry}, and its mapped output | |||
|
|
52 | * file or directory becomes the published slot artifact. | |||
|
|
53 | * | |||
|
|
54 | * <p> | |||
|
|
55 | * Input collection uses {@link SlotContributionVisitor}. Each contribution is | |||
|
|
56 | * converted to a {@link SlotInputKey}; duplicate keys are ignored so that | |||
|
|
57 | * repeated | |||
| 39 | * topology-based selections do not add the same input twice. |
|
58 | * topology-based selections do not add the same input twice. | |
| 40 | */ |
|
59 | */ | |
| 41 | @NonNullByDefault |
|
60 | @NonNullByDefault | |
| @@ -56,6 +75,8 public class ArtifactAssemblyHandler { | |||||
| 56 |
|
75 | |||
| 57 | private final Map<ArtifactSlot, SlotAssembly> slotInputs = new HashMap<>(); |
|
76 | private final Map<ArtifactSlot, SlotAssembly> slotInputs = new HashMap<>(); | |
| 58 |
|
77 | |||
|
|
78 | private final Map<ArtifactSlot, AssemblyMode> assemblyModes = new HashMap<>(); | |||
|
|
79 | ||||
| 59 | public ArtifactAssemblyHandler( |
|
80 | public ArtifactAssemblyHandler( | |
| 60 | ObjectFactory objects, |
|
81 | ObjectFactory objects, | |
| 61 | TaskContainer tasks, |
|
82 | TaskContainer tasks, | |
| @@ -78,18 +99,21 public class ArtifactAssemblyHandler { | |||||
| 78 | } |
|
99 | } | |
| 79 |
|
100 | |||
| 80 | public void configureAssembly(ArtifactSlot artifactSlot, Action<? super ArtifactAssemblySpec> action) { |
|
101 | public void configureAssembly(ArtifactSlot artifactSlot, Action<? super ArtifactAssemblySpec> action) { | |
| 81 |
var |
|
102 | var spec = new DefaultArtifactAssemblySpec(artifactSlot); | |
| 82 | var spec = new DefaultArtifactAssemblySpec(objects, c -> c.accept(visitor)); |
|
|||
| 83 | action.execute(spec); |
|
103 | action.execute(spec); | |
| 84 | } |
|
104 | } | |
| 85 |
|
105 | |||
| 86 | public SlotContributionVisitor contributionVisitor(ArtifactSlot artifactSlot) { |
|
106 | private void useAssemblyMode(ArtifactSlot artifactSlot, AssemblyMode mode) { | |
| 87 |
var |
|
107 | var previous = assemblyModes.putIfAbsent(artifactSlot, mode); | |
| 88 | return new ContributionVisitor(artifactSlot, assembly); |
|
108 | if (previous != null && previous != mode) { | |
|
|
109 | throw new InvalidUserDataException("Artifact slot '" + artifactSlot | |||
|
|
110 | + "' cannot mix task-produced artifact and contribution-based assembly"); | |||
|
|
111 | } | |||
| 89 | } |
|
112 | } | |
| 90 |
|
113 | |||
| 91 | /** |
|
114 | /** | |
| 92 |
* Creates the assembly task for the given slot and registers its output |
|
115 | * Creates the assembly task for the given slot and registers its output | |
|
|
116 | * artifact. | |||
| 93 | */ |
|
117 | */ | |
| 94 | private SlotAssembly createSlotAssembly(ArtifactSlot artifactSlot) { |
|
118 | private SlotAssembly createSlotAssembly(ArtifactSlot artifactSlot) { | |
| 95 | var assembly = new SlotAssembly(); |
|
119 | var assembly = new SlotAssembly(); | |
| @@ -199,4 +223,99 public class ArtifactAssemblyHandler { | |||||
| 199 | return inputs; |
|
223 | return inputs; | |
| 200 | } |
|
224 | } | |
| 201 | } |
|
225 | } | |
|
|
226 | ||||
|
|
227 | private enum AssemblyMode { | |||
|
|
228 | CONTRIBUTIONS, | |||
|
|
229 | TASK_PRODUCER | |||
|
|
230 | } | |||
|
|
231 | ||||
|
|
232 | /** | |||
|
|
233 | * Default DSL facade for collecting {@link SlotContribution} declarations. | |||
|
|
234 | * | |||
|
|
235 | * <p> | |||
|
|
236 | * The spec does not validate topology references immediately. It translates DSL | |||
|
|
237 | * calls to contribution objects and passes them to the supplied consumer; | |||
|
|
238 | * semantic | |||
|
|
239 | * validation happens later when the assembly handler resolves contributions | |||
|
|
240 | * against the finalized source model. | |||
|
|
241 | */ | |||
|
|
242 | class DefaultArtifactAssemblySpec implements ArtifactAssemblySpec { | |||
|
|
243 | ||||
|
|
244 | private final ArtifactSlot artifactSlot; | |||
|
|
245 | ||||
|
|
246 | DefaultArtifactAssemblySpec(ArtifactSlot artifactSlot) { | |||
|
|
247 | this.artifactSlot = artifactSlot; | |||
|
|
248 | } | |||
|
|
249 | ||||
|
|
250 | @Override | |||
|
|
251 | public void from(Object artifact) { | |||
|
|
252 | contribute(new DirectContribution(artifact)); | |||
|
|
253 | } | |||
|
|
254 | ||||
|
|
255 | @Override | |||
|
|
256 | public <T extends Task> void producedBy( | |||
|
|
257 | TaskProvider<T> task, | |||
|
|
258 | Function<? super T, ? extends Provider<? extends FileSystemLocation>> artifact) { | |||
|
|
259 | registerProducedArtifact(task, artifact); | |||
|
|
260 | } | |||
|
|
261 | ||||
|
|
262 | @Override | |||
|
|
263 | public void fromVariant(Action<? super OutputSelectionSpec> action) { | |||
|
|
264 | contribute(new VariantOutputsContribution(outputs(action))); | |||
|
|
265 | } | |||
|
|
266 | ||||
|
|
267 | @Override | |||
|
|
268 | public void fromRole(String roleName, Action<? super OutputSelectionSpec> action) { | |||
|
|
269 | ||||
|
|
270 | contribute(new RoleOutputsContribution( | |||
|
|
271 | objects.named(Role.class, roleName), | |||
|
|
272 | outputs(action))); | |||
|
|
273 | } | |||
|
|
274 | ||||
|
|
275 | @Override | |||
|
|
276 | public void fromLayer(String layerName, Action<? super OutputSelectionSpec> action) { | |||
|
|
277 | contribute(new LayerOutputsContribution( | |||
|
|
278 | objects.named(Layer.class, layerName), | |||
|
|
279 | outputs(action))); | |||
|
|
280 | } | |||
|
|
281 | ||||
|
|
282 | private static Set<String> outputs(Action<? super OutputSelectionSpec> action) { | |||
|
|
283 | var spec = new OutputsSetSpec(); | |||
|
|
284 | action.execute(spec); | |||
|
|
285 | return spec.outputs(); | |||
|
|
286 | } | |||
|
|
287 | ||||
|
|
288 | void contribute(SlotContribution contribution) { | |||
|
|
289 | useAssemblyMode(artifactSlot, AssemblyMode.CONTRIBUTIONS); | |||
|
|
290 | var assembly = slotInputs.computeIfAbsent(artifactSlot, ArtifactAssemblyHandler.this::createSlotAssembly); | |||
|
|
291 | var contributionVisitor = new ContributionVisitor(artifactSlot, assembly); | |||
|
|
292 | contribution.accept(contributionVisitor); | |||
|
|
293 | } | |||
|
|
294 | ||||
|
|
295 | <T extends Task> void registerProducedArtifact( | |||
|
|
296 | TaskProvider<T> task, | |||
|
|
297 | Function<? super T, ? extends Provider<? extends FileSystemLocation>> artifact) { | |||
|
|
298 | useAssemblyMode(artifactSlot, AssemblyMode.TASK_PRODUCER); | |||
|
|
299 | assemblyRegistry.register(artifactSlot, task, artifact); | |||
|
|
300 | } | |||
|
|
301 | ||||
|
|
302 | } | |||
|
|
303 | ||||
|
|
304 | /** Simple implementation of {@link OutputSelectionSpec}. */ | |||
|
|
305 | static class OutputsSetSpec implements OutputSelectionSpec { | |||
|
|
306 | private final Set<String> outputs = new HashSet<>(); | |||
|
|
307 | ||||
|
|
308 | @Override | |||
|
|
309 | public void output(String name, String... extra) { | |||
|
|
310 | Stream.concat(Stream.of(name), Stream.of(extra)) | |||
|
|
311 | .map(Strings::requireNonBlank) | |||
|
|
312 | .forEach(outputs::add); | |||
|
|
313 | } | |||
|
|
314 | ||||
|
|
315 | Set<String> outputs() { | |||
|
|
316 | return Set.copyOf(outputs); | |||
|
|
317 | } | |||
|
|
318 | ||||
|
|
319 | } | |||
|
|
320 | ||||
| 202 | } |
|
321 | } | |
| @@ -422,6 +422,170 class VariantArtifactsPluginFunctionalTe | |||||
| 422 | } |
|
422 | } | |
| 423 |
|
423 | |||
| 424 | @Test |
|
424 | @Test | |
|
|
425 | void publishesTaskProducedFileArtifactDirectly() throws Exception { | |||
|
|
426 | writeFile("settings.gradle", """ | |||
|
|
427 | rootProject.name = 'variant-artifacts-task-produced-file' | |||
|
|
428 | include 'producer', 'consumer' | |||
|
|
429 | """); | |||
|
|
430 | writeBuildFile(""" | |||
|
|
431 | import org.gradle.api.DefaultTask | |||
|
|
432 | import org.gradle.api.attributes.Attribute | |||
|
|
433 | import org.gradle.api.file.RegularFileProperty | |||
|
|
434 | import org.gradle.api.tasks.OutputFile | |||
|
|
435 | import org.gradle.api.tasks.TaskAction | |||
|
|
436 | ||||
|
|
437 | def variantAttr = Attribute.of('test.variant', String) | |||
|
|
438 | def slotAttr = Attribute.of('test.slot', String) | |||
|
|
439 | ||||
|
|
440 | abstract class WritePackageMetadata extends DefaultTask { | |||
|
|
441 | @OutputFile | |||
|
|
442 | abstract RegularFileProperty getOutputFile() | |||
|
|
443 | ||||
|
|
444 | @TaskAction | |||
|
|
445 | void write() { | |||
|
|
446 | def file = outputFile.get().asFile | |||
|
|
447 | file.parentFile.mkdirs() | |||
|
|
448 | file.text = '{"name":"demo"}\\n' | |||
|
|
449 | } | |||
|
|
450 | } | |||
|
|
451 | ||||
|
|
452 | project(':producer') { | |||
|
|
453 | apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin | |||
|
|
454 | ||||
|
|
455 | variants.layers.create('main') | |||
|
|
456 | variants.roles.create('main') | |||
|
|
457 | variants.variant('browser') { | |||
|
|
458 | role('main') { | |||
|
|
459 | layers('main') | |||
|
|
460 | } | |||
|
|
461 | } | |||
|
|
462 | ||||
|
|
463 | def writePackageMetadata = tasks.register('writePackageMetadata', WritePackageMetadata) { | |||
|
|
464 | outputFile.set(layout.buildDirectory.file('generated/package.json')) | |||
|
|
465 | } | |||
|
|
466 | ||||
|
|
467 | variantArtifacts { | |||
|
|
468 | variant('browser') { | |||
|
|
469 | primarySlot('packageMetadata') { | |||
|
|
470 | producedBy(writePackageMetadata) { | |||
|
|
471 | outputFile | |||
|
|
472 | } | |||
|
|
473 | } | |||
|
|
474 | } | |||
|
|
475 | ||||
|
|
476 | whenOutgoingConfiguration { publication -> | |||
|
|
477 | publication.configuration { | |||
|
|
478 | attributes.attribute(variantAttr, publication.variant.name) | |||
|
|
479 | } | |||
|
|
480 | } | |||
|
|
481 | ||||
|
|
482 | whenOutgoingSlot { publication -> | |||
|
|
483 | publication.artifactAttributes { | |||
|
|
484 | attribute(slotAttr, publication.artifactSlot.slot.name) | |||
|
|
485 | } | |||
|
|
486 | } | |||
|
|
487 | } | |||
|
|
488 | ||||
|
|
489 | tasks.register('checkNoManagedAssembly') { | |||
|
|
490 | doLast { | |||
|
|
491 | def assemblyTasks = tasks.names | |||
|
|
492 | .findAll { it.startsWith('assembleVariantArtifactSlot') } | |||
|
|
493 | .sort() | |||
|
|
494 | println('producerAssemblyTasks=' + assemblyTasks.join(',')) | |||
|
|
495 | assert assemblyTasks.empty | |||
|
|
496 | } | |||
|
|
497 | } | |||
|
|
498 | } | |||
|
|
499 | ||||
|
|
500 | project(':consumer') { | |||
|
|
501 | configurations { | |||
|
|
502 | compileView { | |||
|
|
503 | canBeResolved = true | |||
|
|
504 | canBeConsumed = false | |||
|
|
505 | canBeDeclared = true | |||
|
|
506 | attributes { | |||
|
|
507 | attribute(variantAttr, 'browser') | |||
|
|
508 | attribute(slotAttr, 'packageMetadata') | |||
|
|
509 | } | |||
|
|
510 | } | |||
|
|
511 | } | |||
|
|
512 | ||||
|
|
513 | dependencies { | |||
|
|
514 | compileView project(':producer') | |||
|
|
515 | } | |||
|
|
516 | ||||
|
|
517 | tasks.register('probe') { | |||
|
|
518 | dependsOn configurations.compileView | |||
|
|
519 | dependsOn ':producer:checkNoManagedAssembly' | |||
|
|
520 | ||||
|
|
521 | doLast { | |||
|
|
522 | def files = configurations.compileView.files | |||
|
|
523 | println('resolvedFiles=' + files.collect { it.name }.sort().join(',')) | |||
|
|
524 | println('metadata=' + files.iterator().next().text.trim()) | |||
|
|
525 | } | |||
|
|
526 | } | |||
|
|
527 | } | |||
|
|
528 | """); | |||
|
|
529 | ||||
|
|
530 | BuildResult result = runner(":consumer:probe").build(); | |||
|
|
531 | ||||
|
|
532 | assertTrue(result.getOutput().contains("producerAssemblyTasks=")); | |||
|
|
533 | assertTrue(result.getOutput().contains("resolvedFiles=package.json")); | |||
|
|
534 | assertTrue(result.getOutput().contains("metadata={\"name\":\"demo\"}")); | |||
|
|
535 | assertTrue(result.task(":producer:writePackageMetadata").getOutcome() == TaskOutcome.SUCCESS); | |||
|
|
536 | assertTrue(result.task(":consumer:probe").getOutcome() == TaskOutcome.SUCCESS); | |||
|
|
537 | } | |||
|
|
538 | ||||
|
|
539 | @Test | |||
|
|
540 | void failsWhenTaskProducedArtifactIsMixedWithContributionAssembly() throws Exception { | |||
|
|
541 | writeSettings("variant-artifacts-mixed-assembly-mode"); | |||
|
|
542 | writeFile("inputs/marker.txt", "marker\n"); | |||
|
|
543 | writeBuildFile(""" | |||
|
|
544 | import org.gradle.api.DefaultTask | |||
|
|
545 | import org.gradle.api.file.RegularFileProperty | |||
|
|
546 | import org.gradle.api.tasks.OutputFile | |||
|
|
547 | import org.gradle.api.tasks.TaskAction | |||
|
|
548 | ||||
|
|
549 | apply plugin: org.implab.gradle.variants.VariantArtifactsPlugin | |||
|
|
550 | ||||
|
|
551 | abstract class WritePackageMetadata extends DefaultTask { | |||
|
|
552 | @OutputFile | |||
|
|
553 | abstract RegularFileProperty getOutputFile() | |||
|
|
554 | ||||
|
|
555 | @TaskAction | |||
|
|
556 | void write() { | |||
|
|
557 | outputFile.get().asFile.text = '{}\\n' | |||
|
|
558 | } | |||
|
|
559 | } | |||
|
|
560 | ||||
|
|
561 | variants.layers.create('main') | |||
|
|
562 | variants.roles.create('main') | |||
|
|
563 | variants.variant('browser') { | |||
|
|
564 | role('main') { | |||
|
|
565 | layers('main') | |||
|
|
566 | } | |||
|
|
567 | } | |||
|
|
568 | ||||
|
|
569 | def writePackageMetadata = tasks.register('writePackageMetadata', WritePackageMetadata) { | |||
|
|
570 | outputFile.set(layout.buildDirectory.file('generated/package.json')) | |||
|
|
571 | } | |||
|
|
572 | ||||
|
|
573 | variantArtifacts { | |||
|
|
574 | variant('browser') { | |||
|
|
575 | primarySlot('packageMetadata') { | |||
|
|
576 | producedBy(writePackageMetadata) { | |||
|
|
577 | outputFile | |||
|
|
578 | } | |||
|
|
579 | from(layout.projectDirectory.file('inputs/marker.txt')) | |||
|
|
580 | } | |||
|
|
581 | } | |||
|
|
582 | } | |||
|
|
583 | """); | |||
|
|
584 | ||||
|
|
585 | assertBuildFails("cannot mix task-produced artifact and contribution-based assembly", "help"); | |||
|
|
586 | } | |||
|
|
587 | ||||
|
|
588 | @Test | |||
| 425 | void combinesDirectAndTopologyAwareSlotInputs() throws Exception { |
|
589 | void combinesDirectAndTopologyAwareSlotInputs() throws Exception { | |
| 426 | writeSettings("variant-artifacts-combined-inputs"); |
|
590 | writeSettings("variant-artifacts-combined-inputs"); | |
| 427 | writeFile("inputs/base.js", "console.log('base')\n"); |
|
591 | writeFile("inputs/base.js", "console.log('base')\n"); | |
| 1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed |
General Comments 0
You need to be logged in to leave comments.
Login now
