Skip to content

Commit ab118a6

Browse files
Add shared space for plugins
This adds a space for plugins to write data that is intended to be consumable by other plugins. This appears under a directory called `shared` in the the projection's output directory.
1 parent d46c25e commit ab118a6

File tree

9 files changed

+206
-4
lines changed

9 files changed

+206
-4
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "feature",
3+
"description": "This adds a space for plugins to write data that is intended to be consumable by other plugins. This appears under a directory called `shared` in the the projection's output directory.",
4+
"pull_requests": [
5+
"[#2764](https://github.com/awslabs/smithy/pull/2764)"
6+
]
7+
}

smithy-build/src/main/java/software/amazon/smithy/build/PluginContext.java

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public final class PluginContext implements ToSmithyBuilder<PluginContext> {
3535
private final List<ValidationEvent> events;
3636
private final ObjectNode settings;
3737
private final FileManifest fileManifest;
38+
private final FileManifest sharedFileManifest;
3839
private final ClassLoader pluginClassLoader;
3940
private final Set<Path> sources;
4041
private final String artifactName;
@@ -44,6 +45,7 @@ public final class PluginContext implements ToSmithyBuilder<PluginContext> {
4445
private PluginContext(Builder builder) {
4546
model = SmithyBuilder.requiredState("model", builder.model);
4647
fileManifest = SmithyBuilder.requiredState("fileManifest", builder.fileManifest);
48+
sharedFileManifest = builder.sharedFileManifest;
4749
artifactName = builder.artifactName;
4850
projection = builder.projection;
4951
projectionName = builder.projectionName;
@@ -144,14 +146,49 @@ public ObjectNode getSettings() {
144146
* Gets the FileManifest used to create files in the projection.
145147
*
146148
* <p>All files written by a plugin should either be written using this
147-
* manifest or added to the manifest via {@link FileManifest#addFile}.
149+
* manifest, the shared manifest ({@link #getSharedFileManifest()}), or
150+
* added to the manifest via {@link FileManifest#addFile}.
151+
*
152+
* <p>Files written to this manifest are specific to this plugin and cannot
153+
* be read or modified by other plugins. To write files that should be
154+
* shared with other plugins, use the shared manifest from
155+
* {@link #getSharedFileManifest()}.
148156
*
149157
* @return Returns the file manifest.
150158
*/
151159
public FileManifest getFileManifest() {
152160
return fileManifest;
153161
}
154162

163+
/**
164+
* Gets the FileManifest used to create files in the projection's shared
165+
* file space.
166+
*
167+
* <p>All files written by a plugin should either be written using this
168+
* manifest, the plugin's isolated manifest ({@link #getFileManifest()}),
169+
* or added to the manifest via {@link FileManifest#addFile}.
170+
*
171+
* <p>Files written to this manifest may be read or modified by other
172+
* plugins. Plugins SHOULD NOT write files to this manifest unless they
173+
* specifically intend for them to be consumed by other plugins. Files
174+
* that are not intended to be shared should be written to the manifest
175+
* from {@link #getFileManifest()}.
176+
*
177+
* @return Returns the file manifest.
178+
*/
179+
public FileManifest getSharedFileManifest() {
180+
// This will always be set in actual Smithy builds, as it is set by
181+
// SmithyBuildImpl. We therefore don't want the return type to be
182+
// optional, since in real usage it isn't. This was introduced after
183+
// the class was made public, however, and this class is likely being
184+
// manually constructed in tests. So instead of checking if it's set
185+
// in the builder, we check when it's actually used.
186+
if (sharedFileManifest == null) {
187+
SmithyBuilder.requiredState("sharedFileManifest", sharedFileManifest);
188+
}
189+
return sharedFileManifest;
190+
}
191+
155192
/**
156193
* Gets the ClassLoader that should be used in build plugins to load
157194
* services.
@@ -274,6 +311,7 @@ public Builder toBuilder() {
274311
.events(events)
275312
.settings(settings)
276313
.fileManifest(fileManifest)
314+
.sharedFileManifest(sharedFileManifest)
277315
.pluginClassLoader(pluginClassLoader)
278316
.sources(sources)
279317
.artifactName(artifactName);
@@ -293,6 +331,7 @@ public static final class Builder implements SmithyBuilder<PluginContext> {
293331
private List<ValidationEvent> events = Collections.emptyList();
294332
private ObjectNode settings = Node.objectNode();
295333
private FileManifest fileManifest;
334+
private FileManifest sharedFileManifest;
296335
private ClassLoader pluginClassLoader;
297336
private final BuilderRef<Set<Path>> sources = BuilderRef.forOrderedSet();
298337
private String artifactName;
@@ -316,6 +355,18 @@ public Builder fileManifest(FileManifest fileManifest) {
316355
return this;
317356
}
318357

358+
/**
359+
* Sets the <strong>required</strong> shared space {@link FileManifest} to use
360+
* in the plugin.
361+
*
362+
* @param sharedFileManifest FileManifest to use for shared space.
363+
* @return Returns the builder.
364+
*/
365+
public Builder sharedFileManifest(FileManifest sharedFileManifest) {
366+
this.sharedFileManifest = sharedFileManifest;
367+
return this;
368+
}
369+
319370
/**
320371
* Sets the <strong>required</strong> model that is being built.
321372
*

smithy-build/src/main/java/software/amazon/smithy/build/ProjectionResult.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,15 @@ public final class ProjectionResult {
2121
private final String projectionName;
2222
private final Model model;
2323
private final Map<String, FileManifest> pluginManifests;
24+
private final FileManifest sharedFileManifest;
2425
private final List<ValidationEvent> events;
2526

2627
private ProjectionResult(Builder builder) {
2728
this.projectionName = SmithyBuilder.requiredState("projectionName", builder.projectionName);
2829
this.model = SmithyBuilder.requiredState("model", builder.model);
2930
this.events = builder.events.copy();
3031
this.pluginManifests = builder.pluginManifests.copy();
32+
this.sharedFileManifest = builder.sharedFileManifest;
3133
}
3234

3335
/**
@@ -102,13 +104,32 @@ public Optional<FileManifest> getPluginManifest(String artifactName) {
102104
return Optional.ofNullable(pluginManifests.get(artifactName));
103105
}
104106

107+
/**
108+
* Gets the shared result manifest.
109+
*
110+
* @return Returns files created by plugins in shared space.
111+
*/
112+
public FileManifest getSharedManifest() {
113+
// This will always be set in actual Smithy builds, as it is set by
114+
// SmithyBuildImpl. We therefore don't want the return type to be
115+
// optional, since in real usage it isn't. This was introduced after
116+
// the class was made public, however, and this class is likely being
117+
// manually constructed in tests. So instead of checking if it's set
118+
// in the builder, we check when it's actually used.
119+
if (sharedFileManifest == null) {
120+
SmithyBuilder.requiredState("sharedFileManifest", sharedFileManifest);
121+
}
122+
return sharedFileManifest;
123+
}
124+
105125
/**
106126
* Builds up a {@link ProjectionResult}.
107127
*/
108128
public static final class Builder implements SmithyBuilder<ProjectionResult> {
109129
private String projectionName;
110130
private Model model;
111131
private final BuilderRef<Map<String, FileManifest>> pluginManifests = BuilderRef.forUnorderedMap();
132+
private FileManifest sharedFileManifest;
112133
private final BuilderRef<List<ValidationEvent>> events = BuilderRef.forList();
113134

114135
@Override
@@ -153,6 +174,17 @@ public Builder addPluginManifest(String artifactName, FileManifest manifest) {
153174
return this;
154175
}
155176

177+
/**
178+
* Sets the shared result manifest.
179+
*
180+
* @param sharedFileManifest File manifest shared by all plugins.
181+
* @return Returns the builder.
182+
*/
183+
public Builder sharedFileManifest(FileManifest sharedFileManifest) {
184+
this.sharedFileManifest = sharedFileManifest;
185+
return this;
186+
}
187+
156188
/**
157189
* Adds validation events to the result.
158190
*

smithy-build/src/main/java/software/amazon/smithy/build/SmithyBuildImpl.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ final class SmithyBuildImpl {
4949
private static final Pattern PLUGIN_PATTERN = Pattern
5050
.compile("^" + PATTERN_PART + "(::" + PATTERN_PART + ")?$");
5151

52+
private static final String SHARED_MANIFEST_NAME = "shared";
53+
5254
private final SmithyBuildConfig config;
5355
private final Function<Path, FileManifest> fileManifestFactory;
5456
private final Supplier<ModelAssembler> modelAssemblerSupplier;
@@ -357,12 +359,17 @@ private ProjectionResult applyProjection(
357359
LOGGER.fine(() -> String.format("No transforms to apply for projection %s", projectionName));
358360
}
359361

362+
// Create the manifest where shared artifacts are stored.
363+
Path sharedPluginDir = baseProjectionDir.resolve(SHARED_MANIFEST_NAME);
364+
FileManifest sharedManifest = fileManifestFactory.apply(sharedPluginDir);
365+
360366
// Keep track of the first error created by plugins to fail the build after all plugins have run.
361367
Throwable firstPluginError = null;
362368
ProjectionResult.Builder resultBuilder = ProjectionResult.builder()
363369
.projectionName(projectionName)
364370
.model(projectedModel)
365-
.events(modelResult.getValidationEvents());
371+
.events(modelResult.getValidationEvents())
372+
.sharedFileManifest(sharedManifest);
366373

367374
for (ResolvedPlugin resolvedPlugin : resolvedPlugins) {
368375
if (pluginFilter.test(resolvedPlugin.id.getArtifactName())) {
@@ -374,7 +381,8 @@ private ProjectionResult applyProjection(
374381
projectedModel,
375382
resolvedModel,
376383
modelResult,
377-
resultBuilder);
384+
resultBuilder,
385+
sharedManifest);
378386
} catch (Throwable e) {
379387
if (firstPluginError == null) {
380388
firstPluginError = e;
@@ -427,7 +435,8 @@ private void applyPlugin(
427435
Model projectedModel,
428436
Model resolvedModel,
429437
ValidatedResult<Model> modelResult,
430-
ProjectionResult.Builder resultBuilder
438+
ProjectionResult.Builder resultBuilder,
439+
FileManifest sharedManifest
431440
) {
432441
PluginId id = resolvedPlugin.id;
433442

@@ -449,6 +458,7 @@ private void applyPlugin(
449458
.events(modelResult.getValidationEvents())
450459
.settings(resolvedPlugin.config)
451460
.fileManifest(manifest)
461+
.sharedFileManifest(sharedManifest)
452462
.pluginClassLoader(pluginClassLoader)
453463
.sources(sources)
454464
.artifactName(id.hasArtifactName() ? id.getArtifactName() : null)

smithy-build/src/test/java/software/amazon/smithy/build/PluginContextTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ public void convertsToBuilder() {
7272
PluginContext context = PluginContext.builder()
7373
.projection("foo", ProjectionConfig.builder().build())
7474
.fileManifest(new MockManifest())
75+
.sharedFileManifest(new MockManifest())
7576
.model(Model.builder().build())
7677
.originalModel(Model.builder().build())
7778
.settings(Node.objectNode().withMember("foo", "bar"))
@@ -83,6 +84,7 @@ public void convertsToBuilder() {
8384
assertThat(context.getModel(), equalTo(context2.getModel()));
8485
assertThat(context.getOriginalModel(), equalTo(context2.getOriginalModel()));
8586
assertThat(context.getFileManifest(), is(context2.getFileManifest()));
87+
assertThat(context.getSharedFileManifest(), is(context2.getSharedFileManifest()));
8688
assertThat(context.getSources(), equalTo(context2.getSources()));
8789
assertThat(context.getEvents(), equalTo(context2.getEvents()));
8890
}

smithy-build/src/test/java/software/amazon/smithy/build/SmithyBuildTest.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
import static org.hamcrest.Matchers.hasItem;
1313
import static org.hamcrest.Matchers.is;
1414
import static org.hamcrest.Matchers.not;
15+
import static org.junit.jupiter.api.Assertions.assertEquals;
1516
import static org.junit.jupiter.api.Assertions.assertFalse;
17+
import static org.junit.jupiter.api.Assertions.assertNotNull;
1618
import static org.junit.jupiter.api.Assertions.assertTrue;
1719

1820
import java.io.File;
@@ -459,6 +461,47 @@ public void appliesPlugins() throws Exception {
459461
assertTrue(b.getPluginManifest("test2").get().hasFile("hello2"));
460462
}
461463

464+
@Test
465+
public void appliesPluginsWithSharedSpace() throws Exception {
466+
Map<String, SmithyBuildPlugin> plugins = MapUtils.of(
467+
"testSharing1",
468+
new TestSharingPlugin1(),
469+
"testSharing2",
470+
new TestSharingPlugin2());
471+
Function<String, Optional<SmithyBuildPlugin>> factory = SmithyBuildPlugin.createServiceFactory();
472+
Function<String, Optional<SmithyBuildPlugin>> composed = name -> OptionalUtils.or(
473+
Optional.ofNullable(plugins.get(name)),
474+
() -> factory.apply(name));
475+
476+
SmithyBuild builder = new SmithyBuild().pluginFactory(composed);
477+
builder.fileManifestFactory(MockManifest::new);
478+
builder.config(SmithyBuildConfig.builder()
479+
.load(Paths.get(getClass().getResource("applies-plugins-with-shared-space.json").toURI()))
480+
.outputDirectory("/foo")
481+
.build());
482+
483+
SmithyBuildResult results = builder.build();
484+
ProjectionResult source = results.getProjectionResult("source").get();
485+
ProjectionResult projection = results.getProjectionResult("testProjection").get();
486+
487+
assertNotNull(source.getSharedManifest());
488+
assertNotNull(projection.getSharedManifest());
489+
490+
MockManifest sourceManifest = (MockManifest) source.getSharedManifest();
491+
MockManifest projectionManifest = (MockManifest) projection.getSharedManifest();
492+
493+
assertTrue(sourceManifest.hasFile("helloShare1"));
494+
assertEquals("1", sourceManifest.getFileString("helloShare1").get());
495+
496+
assertTrue(projectionManifest.hasFile("helloShare1"));
497+
assertEquals("2", projectionManifest.getFileString("helloShare1").get());
498+
499+
assertFalse(sourceManifest.hasFile("helloShare2"));
500+
501+
assertTrue(projectionManifest.hasFile("helloShare2"));
502+
assertEquals("2", projectionManifest.getFileString("helloShare2").get());
503+
}
504+
462505
@Test
463506
public void appliesSerialPlugins() throws Exception {
464507
Map<String, SmithyBuildPlugin> plugins = new LinkedHashMap<>();
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
package software.amazon.smithy.build;
6+
7+
public class TestSharingPlugin1 implements SmithyBuildPlugin {
8+
@Override
9+
public String getName() {
10+
return "testSharing1";
11+
}
12+
13+
@Override
14+
public void execute(PluginContext context) {
15+
FileManifest manifest = context.getSharedFileManifest();
16+
String count = String.valueOf(manifest.getFiles().size() + 1);
17+
manifest.getFiles().forEach(file -> {
18+
manifest.writeFile(file, count);
19+
});
20+
manifest.writeFile("helloShare1", count);
21+
}
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
package software.amazon.smithy.build;
6+
7+
public class TestSharingPlugin2 implements SmithyBuildPlugin {
8+
@Override
9+
public String getName() {
10+
return "testSharing2";
11+
}
12+
13+
@Override
14+
public void execute(PluginContext context) {
15+
FileManifest manifest = context.getSharedFileManifest();
16+
String count = String.valueOf(manifest.getFiles().size() + 1);
17+
manifest.getFiles().forEach(file -> {
18+
manifest.writeFile(file, count);
19+
});
20+
manifest.writeFile("helloShare2", count);
21+
}
22+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"version": "2.0",
3+
"plugins": {
4+
"testSharing1": {}
5+
},
6+
"projections": {
7+
"testProjection": {
8+
"plugins": {
9+
"testSharing2": {}
10+
}
11+
}
12+
}
13+
}

0 commit comments

Comments
 (0)