Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update smithy-build to use NodeMapper #305

Merged
merged 2 commits into from
Mar 9, 2020
Merged
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
305 changes: 269 additions & 36 deletions docs/source/guides/building-models/build-config.rst

Large diffs are not rendered by default.

25 changes: 21 additions & 4 deletions docs/source/guides/building-models/gradle-plugin.rst
Original file line number Diff line number Diff line change
Expand Up @@ -239,10 +239,27 @@ projection. For example:
"projections": {
"external": {
"transforms": [
{"name": "excludeShapesByTag", "args": ["internal"]},
{"name": "excludeTraitsByTag", "args": ["internal"]},
{"name": "excludeMetadata", "args": ["suppressions", "validators"]},
{"name": "removeUnusedShapes", "args": []}
{
"name": "excludeShapesByTag",
"args": {
"tags": ["internal"]
}
},
{
"name": "excludeTraitsByTag",
"args": {
"tags": ["internal"]
}
},
{
"name": "excludeMetadata",
"args": {
"keys": ["suppressions", "validators"]
}
},
{
"name": "removeUnusedShapes"
}
]
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,11 @@

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.function.BiFunction;
import java.util.function.Function;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.transform.ModelTransformer;
import software.amazon.smithy.utils.ListUtils;

/**
Expand All @@ -48,14 +45,13 @@ default Collection<String> getAliases() {
}

/**
* Creates a function that transforms a model using the provided
* {@link ModelTransformer}.
* Transforms the given model using the provided {@link TransformContext}.
*
* @param arguments Arguments used to create the ModelTransformer.
* @param context Transformation context.
* @return Returns the created transformer.
* @throws IllegalArgumentException if the arguments are invalid.
*/
BiFunction<ModelTransformer, Model, Model> createTransformer(List<String> arguments);
Model transform(TransformContext context);

/**
* Creates a {@code ProjectionTransformer} factory function using SPI
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
* and writes the artifacts to a {@link FileManifest}.
*/
public final class SmithyBuild {
/** The version of Smithy build. */
public static final String VERSION = "1.0";

SmithyBuildConfig config;
Path importBasePath;
Path outputDirectory;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
Expand All @@ -31,34 +32,33 @@
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import software.amazon.smithy.build.model.ProjectionConfig;
import software.amazon.smithy.build.model.SmithyBuildConfig;
import software.amazon.smithy.build.model.TransformConfig;
import software.amazon.smithy.build.transforms.Apply;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.loader.ModelAssembler;
import software.amazon.smithy.model.node.ObjectNode;
import software.amazon.smithy.model.transform.ModelTransformer;
import software.amazon.smithy.model.validation.ValidatedResult;
import software.amazon.smithy.utils.Pair;
import software.amazon.smithy.utils.SmithyBuilder;

final class SmithyBuildImpl {
private static final Logger LOGGER = Logger.getLogger(SmithyBuild.class.getName());
private static final String APPLY_PROJECTIONS = "apply";
private static final Pattern PATTERN = Pattern.compile("^[A-Za-z0-9\\-_.]+$");

private final SmithyBuildConfig config;
private final Function<Path, FileManifest> fileManifestFactory;
private final Supplier<ModelAssembler> modelAssemblerSupplier;
private final Path outputDirectory;
private final Map<String, BiFunction<ModelTransformer, Model, Model>> transformers = new HashMap<>();
private final Map<String, List<Pair<ObjectNode, ProjectionTransformer>>> transformers = new HashMap<>();
private final ModelTransformer modelTransformer;
private final Function<String, Optional<ProjectionTransformer>> transformFactory;
private final Function<String, Optional<SmithyBuildPlugin>> pluginFactory;
Expand Down Expand Up @@ -106,7 +106,9 @@ final class SmithyBuildImpl {
: Paths.get(".").toAbsolutePath().normalize();

// Create the transformers for each projection.
config.getProjections().forEach((k, p) -> transformers.put(k, createTransformer(k, p, new LinkedHashSet<>())));
config.getProjections().forEach((projectionName, projectionConfig) -> {
transformers.put(projectionName, createTransformers(projectionName, projectionConfig));
});

pluginClassLoader = builder.pluginClassLoader;
projectionFilter = builder.projectionFilter;
Expand Down Expand Up @@ -305,10 +307,9 @@ private ProjectionResult applyProjection(String projectionName, ProjectionConfig
// Create the base directory where all projection artifacts are stored.
Path baseProjectionDir = outputDirectory.resolve(projectionName);

// Project the model and collect the results.
Model projectedModel = transformers
.get(projectionName)
.apply(modelTransformer, resolvedModel);
// Transform the model and collect the results.
Model projectedModel = applyProjectionTransforms(
resolvedModel, resolvedModel, projectionName, Collections.emptySet());

ValidatedResult<Model> modelResult = modelAssemblerSupplier.get().addModel(projectedModel).assemble();

Expand All @@ -327,6 +328,31 @@ private ProjectionResult applyProjection(String projectionName, ProjectionConfig
return resultBuilder.build();
}

private Model applyProjectionTransforms(
Model inputModel,
Model originalModel,
String projectionName,
Set<String> visited
) {
// Transform the model and collect the results.
Model projectedModel = inputModel;

for (Pair<ObjectNode, ProjectionTransformer> transformerBinding : transformers.get(projectionName)) {
TransformContext context = TransformContext.builder()
.model(projectedModel)
.originalModel(originalModel)
.transformer(modelTransformer)
.projectionName(projectionName)
.sources(sources)
.settings(transformerBinding.left)
.visited(visited)
.build();
projectedModel = transformerBinding.right.transform(context);
}

return projectedModel;
}

private void applyPlugin(
String projectionName,
ProjectionConfig projection,
Expand Down Expand Up @@ -377,54 +403,55 @@ private Map<String, ObjectNode> resolvePlugins(ProjectionConfig projection) {
return result;
}

private BiFunction<ModelTransformer, Model, Model> createTransformer(
// Creates pairs where the left value is the configuration arguments of the
// transformer, and the right value is the instantiated transformer.
private List<Pair<ObjectNode, ProjectionTransformer>> createTransformers(
String projectionName,
ProjectionConfig projection,
Set<String> visited
ProjectionConfig config
) {
if (visited.contains(projectionName)) {
visited.add(projectionName);
throw new SmithyBuildException(String.format("Cycle found in %s transforms: %s -> ...",
APPLY_PROJECTIONS, String.join(" -> ", visited)));
}
List<Pair<ObjectNode, ProjectionTransformer>> resolved = new ArrayList<>(config.getTransforms().size());

visited.add(projectionName);
for (TransformConfig transformConfig : config.getTransforms()) {
String name = transformConfig.getName();

// Create a composed transformer of each created transformer.
return projection.getTransforms().stream()
.flatMap(transform -> getTransform(projectionName, transform, visited))
.reduce((a, b) -> (transformer, model) -> b.apply(transformer, a.apply(transformer, model)))
.orElse(((transformer, model) -> model));
}
if (name.equals("apply")) {
resolved.add(createApplyTransformer(projectionName, transformConfig));
continue;
}

private Stream<BiFunction<ModelTransformer, Model, Model>> getTransform(
String projection,
TransformConfig config,
Set<String> visited
) {
String name = config.getName();

if (name.equals(APPLY_PROJECTIONS)) {
return config.getArgs().stream().map(arg -> {
// Copy the set of visited projections to a new set;
// visiting the same projection isn't a problem, it's
// cycles that's problematic.
ProjectionConfig targetProjection = findProjection(projection, arg);
return createTransformer(arg, targetProjection, new LinkedHashSet<>(visited));
});
ProjectionTransformer transformer = transformFactory.apply(name)
.orElseThrow(() -> new UnknownTransformException(String.format(
"Unable to find a transform for `%s` in the `%s` projection.", name, projectionName)));
resolved.add(Pair.of(transformConfig.getArgs(), transformer));
}

ProjectionTransformer transformer = transformFactory.apply(name)
.orElseThrow(() -> new UnknownTransformException("Unable to find a transform for `" + name + "`."));
return Stream.of(transformer.createTransformer(config.getArgs()));
return resolved;
}

private ProjectionConfig findProjection(String projection, String name) {
if (!config.getProjections().containsKey(name)) {
throw new UnknownProjectionException(String.format(
"Unable to find projection named `%s` referenced by `%s`", name, projection));
}
// This is a somewhat hacky special case that allows the "apply" transform to apply
// transformations defined on other projections.
private Pair<ObjectNode, ProjectionTransformer> createApplyTransformer(
String projectionName,
TransformConfig transformConfig
) {
Apply.ApplyCallback callback = (currentModel, projectionTarget, visited) -> {
if (projectionTarget.equals(projectionName)) {
throw new SmithyBuildException(
"Cannot recursively apply the same projection: " + projectionName);
} else if (visited.contains(projectionTarget)) {
visited.add(projectionTarget);
throw new SmithyBuildException(String.format(
"Cycle found in apply transforms: %s -> ...", String.join(" -> ", visited)));
} else if (!transformers.containsKey(projectionTarget)) {
throw new UnknownProjectionException(String.format(
"Unable to find projection named `%s` referenced by `%s`",
projectionTarget, projectionName));
}
Set<String> updatedVisited = new LinkedHashSet<>(visited);
updatedVisited.add(projectionTarget);
return applyProjectionTransforms(currentModel, model, projectionTarget, updatedVisited);
};

return config.getProjections().get(name);
return Pair.of(transformConfig.getArgs(), new Apply(callback));
}
}
Loading