Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "feature",
"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.",
"pull_requests": [
"[#2764](https://github.com/awslabs/smithy/pull/2764)"
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public final class PluginContext implements ToSmithyBuilder<PluginContext> {
private final List<ValidationEvent> events;
private final ObjectNode settings;
private final FileManifest fileManifest;
private final FileManifest sharedFileManifest;
private final ClassLoader pluginClassLoader;
private final Set<Path> sources;
private final String artifactName;
Expand All @@ -42,6 +43,7 @@ public final class PluginContext implements ToSmithyBuilder<PluginContext> {
private PluginContext(Builder builder) {
model = SmithyBuilder.requiredState("model", builder.model);
fileManifest = SmithyBuilder.requiredState("fileManifest", builder.fileManifest);
sharedFileManifest = builder.sharedFileManifest;
artifactName = builder.artifactName;
projection = builder.projection;
projectionName = builder.projectionName;
Expand Down Expand Up @@ -133,14 +135,49 @@ public ObjectNode getSettings() {
* Gets the FileManifest used to create files in the projection.
*
* <p>All files written by a plugin should either be written using this
* manifest or added to the manifest via {@link FileManifest#addFile}.
* manifest, the shared manifest ({@link #getSharedFileManifest()}), or
* added to the manifest via {@link FileManifest#addFile}.
*
* <p>Files written to this manifest are specific to this plugin and cannot
* be read or modified by other plugins. To write files that should be
* shared with other plugins, use the shared manifest from
* {@link #getSharedFileManifest()}.
*
* @return Returns the file manifest.
*/
public FileManifest getFileManifest() {
return fileManifest;
}

/**
* Gets the FileManifest used to create files in the projection's shared
* file space.
*
* <p>All files written by a plugin should either be written using this
* manifest, the plugin's isolated manifest ({@link #getFileManifest()}),
* or added to the manifest via {@link FileManifest#addFile}.
*
* <p>Files written to this manifest may be read or modified by other
* plugins. Plugins SHOULD NOT write files to this manifest unless they
* specifically intend for them to be consumed by other plugins. Files
* that are not intended to be shared should be written to the manifest
* from {@link #getFileManifest()}.
*
* @return Returns the file manifest.
*/
public FileManifest getSharedFileManifest() {
// This will always be set in actual Smithy builds, as it is set by
// SmithyBuildImpl. We therefore don't want the return type to be
// optional, since in real usage it isn't. This was introduced after
// the class was made public, however, and this class is likely being
// manually constructed in tests. So instead of checking if it's set
// in the builder, we check when it's actually used.
if (sharedFileManifest == null) {
SmithyBuilder.requiredState("sharedFileManifest", sharedFileManifest);
}
return sharedFileManifest;
}

/**
* Gets the ClassLoader that should be used in build plugins to load
* services.
Expand Down Expand Up @@ -248,6 +285,7 @@ public Builder toBuilder() {
.events(events)
.settings(settings)
.fileManifest(fileManifest)
.sharedFileManifest(sharedFileManifest)
.pluginClassLoader(pluginClassLoader)
.sources(sources)
.artifactName(artifactName);
Expand All @@ -267,6 +305,7 @@ public static final class Builder implements SmithyBuilder<PluginContext> {
private List<ValidationEvent> events = Collections.emptyList();
private ObjectNode settings = Node.objectNode();
private FileManifest fileManifest;
private FileManifest sharedFileManifest;
private ClassLoader pluginClassLoader;
private final BuilderRef<Set<Path>> sources = BuilderRef.forOrderedSet();
private String artifactName;
Expand All @@ -290,6 +329,18 @@ public Builder fileManifest(FileManifest fileManifest) {
return this;
}

/**
* Sets the <strong>required</strong> shared space {@link FileManifest} to use
* in the plugin.
*
* @param sharedFileManifest FileManifest to use for shared space.
* @return Returns the builder.
*/
public Builder sharedFileManifest(FileManifest sharedFileManifest) {
this.sharedFileManifest = sharedFileManifest;
return this;
}

/**
* Sets the <strong>required</strong> model that is being built.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@ public final class ProjectionResult {
private final String projectionName;
private final Model model;
private final Map<String, FileManifest> pluginManifests;
private final FileManifest sharedFileManifest;
private final List<ValidationEvent> events;

private ProjectionResult(Builder builder) {
this.projectionName = SmithyBuilder.requiredState("projectionName", builder.projectionName);
this.model = SmithyBuilder.requiredState("model", builder.model);
this.events = builder.events.copy();
this.pluginManifests = builder.pluginManifests.copy();
this.sharedFileManifest = builder.sharedFileManifest;
}

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

/**
* Gets the shared result manifest.
*
* @return Returns files created by plugins in shared space.
*/
public FileManifest getSharedManifest() {
// This will always be set in actual Smithy builds, as it is set by
// SmithyBuildImpl. We therefore don't want the return type to be
// optional, since in real usage it isn't. This was introduced after
// the class was made public, however, and this class is likely being
// manually constructed in tests. So instead of checking if it's set
// in the builder, we check when it's actually used.
if (sharedFileManifest == null) {
SmithyBuilder.requiredState("sharedFileManifest", sharedFileManifest);
}
return sharedFileManifest;
}

/**
* Builds up a {@link ProjectionResult}.
*/
public static final class Builder implements SmithyBuilder<ProjectionResult> {
private String projectionName;
private Model model;
private final BuilderRef<Map<String, FileManifest>> pluginManifests = BuilderRef.forUnorderedMap();
private FileManifest sharedFileManifest;
private final BuilderRef<List<ValidationEvent>> events = BuilderRef.forList();

@Override
Expand Down Expand Up @@ -153,6 +174,17 @@ public Builder addPluginManifest(String artifactName, FileManifest manifest) {
return this;
}

/**
* Sets the shared result manifest.
*
* @param sharedFileManifest File manifest shared by all plugins.
* @return Returns the builder.
*/
public Builder sharedFileManifest(FileManifest sharedFileManifest) {
this.sharedFileManifest = sharedFileManifest;
return this;
}

/**
* Adds validation events to the result.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ final class SmithyBuildImpl {
private static final Pattern PLUGIN_PATTERN = Pattern
.compile("^" + PATTERN_PART + "(::" + PATTERN_PART + ")?$");

private static final String SHARED_MANIFEST_NAME = "shared";

private final SmithyBuildConfig config;
private final Function<Path, FileManifest> fileManifestFactory;
private final Supplier<ModelAssembler> modelAssemblerSupplier;
Expand Down Expand Up @@ -357,12 +359,17 @@ private ProjectionResult applyProjection(
LOGGER.fine(() -> String.format("No transforms to apply for projection %s", projectionName));
}

// Create the manifest where shared artifacts are stored.
Path sharedPluginDir = baseProjectionDir.resolve(SHARED_MANIFEST_NAME);
FileManifest sharedManifest = fileManifestFactory.apply(sharedPluginDir);

// Keep track of the first error created by plugins to fail the build after all plugins have run.
Throwable firstPluginError = null;
ProjectionResult.Builder resultBuilder = ProjectionResult.builder()
.projectionName(projectionName)
.model(projectedModel)
.events(modelResult.getValidationEvents());
.events(modelResult.getValidationEvents())
.sharedFileManifest(sharedManifest);

for (ResolvedPlugin resolvedPlugin : resolvedPlugins) {
if (pluginFilter.test(resolvedPlugin.id.getArtifactName())) {
Expand All @@ -374,7 +381,8 @@ private ProjectionResult applyProjection(
projectedModel,
resolvedModel,
modelResult,
resultBuilder);
resultBuilder,
sharedManifest);
} catch (Throwable e) {
if (firstPluginError == null) {
firstPluginError = e;
Expand Down Expand Up @@ -427,7 +435,8 @@ private void applyPlugin(
Model projectedModel,
Model resolvedModel,
ValidatedResult<Model> modelResult,
ProjectionResult.Builder resultBuilder
ProjectionResult.Builder resultBuilder,
FileManifest sharedManifest
) {
PluginId id = resolvedPlugin.id;

Expand All @@ -449,6 +458,7 @@ private void applyPlugin(
.events(modelResult.getValidationEvents())
.settings(resolvedPlugin.config)
.fileManifest(manifest)
.sharedFileManifest(sharedManifest)
.pluginClassLoader(pluginClassLoader)
.sources(sources)
.artifactName(id.hasArtifactName() ? id.getArtifactName() : null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ public void convertsToBuilder() {
PluginContext context = PluginContext.builder()
.projection("foo", ProjectionConfig.builder().build())
.fileManifest(new MockManifest())
.sharedFileManifest(new MockManifest())
.model(Model.builder().build())
.originalModel(Model.builder().build())
.settings(Node.objectNode().withMember("foo", "bar"))
Expand All @@ -83,6 +84,7 @@ public void convertsToBuilder() {
assertThat(context.getModel(), equalTo(context2.getModel()));
assertThat(context.getOriginalModel(), equalTo(context2.getOriginalModel()));
assertThat(context.getFileManifest(), is(context2.getFileManifest()));
assertThat(context.getSharedFileManifest(), is(context2.getSharedFileManifest()));
assertThat(context.getSources(), equalTo(context2.getSources()));
assertThat(context.getEvents(), equalTo(context2.getEvents()));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

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

@Test
public void appliesPluginsWithSharedSpace() throws Exception {
Map<String, SmithyBuildPlugin> plugins = MapUtils.of(
"testSharing1",
new TestSharingPlugin1(),
"testSharing2",
new TestSharingPlugin2());
Function<String, Optional<SmithyBuildPlugin>> factory = SmithyBuildPlugin.createServiceFactory();
Function<String, Optional<SmithyBuildPlugin>> composed = name -> OptionalUtils.or(
Optional.ofNullable(plugins.get(name)),
() -> factory.apply(name));

SmithyBuild builder = new SmithyBuild().pluginFactory(composed);
builder.fileManifestFactory(MockManifest::new);
builder.config(SmithyBuildConfig.builder()
.load(Paths.get(getClass().getResource("applies-plugins-with-shared-space.json").toURI()))
.outputDirectory("/foo")
.build());

SmithyBuildResult results = builder.build();
ProjectionResult source = results.getProjectionResult("source").get();
ProjectionResult projection = results.getProjectionResult("testProjection").get();

assertNotNull(source.getSharedManifest());
assertNotNull(projection.getSharedManifest());

MockManifest sourceManifest = (MockManifest) source.getSharedManifest();
MockManifest projectionManifest = (MockManifest) projection.getSharedManifest();

assertTrue(sourceManifest.hasFile("helloShare1"));
assertEquals("1", sourceManifest.getFileString("helloShare1").get());

assertTrue(projectionManifest.hasFile("helloShare1"));
assertEquals("2", projectionManifest.getFileString("helloShare1").get());

assertFalse(sourceManifest.hasFile("helloShare2"));

assertTrue(projectionManifest.hasFile("helloShare2"));
assertEquals("2", projectionManifest.getFileString("helloShare2").get());
}

@Test
public void appliesSerialPlugins() throws Exception {
Map<String, SmithyBuildPlugin> plugins = new LinkedHashMap<>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package software.amazon.smithy.build;

public class TestSharingPlugin1 implements SmithyBuildPlugin {
@Override
public String getName() {
return "testSharing1";
}

@Override
public void execute(PluginContext context) {
FileManifest manifest = context.getSharedFileManifest();
String count = String.valueOf(manifest.getFiles().size() + 1);
manifest.getFiles().forEach(file -> {
manifest.writeFile(file, count);
});
manifest.writeFile("helloShare1", count);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package software.amazon.smithy.build;

public class TestSharingPlugin2 implements SmithyBuildPlugin {
@Override
public String getName() {
return "testSharing2";
}

@Override
public void execute(PluginContext context) {
FileManifest manifest = context.getSharedFileManifest();
String count = String.valueOf(manifest.getFiles().size() + 1);
manifest.getFiles().forEach(file -> {
manifest.writeFile(file, count);
});
manifest.writeFile("helloShare2", count);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"version": "2.0",
"plugins": {
"testSharing1": {}
},
"projections": {
"testProjection": {
"plugins": {
"testSharing2": {}
}
}
}
}
Loading