Skip to content

Commit db7c741

Browse files
committed
Add APIs to load a model from a build config
Tools that consume Smithy models via smithy-build have to either shell out to CLI or reimplement `SmithyBuildImpl#createBaseModel`. To improve this, `SmithyBuildConfig` gets `toModelAssembler` that prepares a `ModelAssembler` with the config's sources and imports. `SmithyBuild` gets `toProjectedModel` that assembles and projects the model in one step, returning the requested projection. Plugins are skipped and the run config is so that unknown transforms or missing plugins in unrelated projections do not block the call.
1 parent a2f149b commit db7c741

7 files changed

Lines changed: 409 additions & 0 deletions

File tree

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

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,13 @@
2222
import java.util.function.Predicate;
2323
import java.util.function.Supplier;
2424
import java.util.logging.Logger;
25+
import software.amazon.smithy.build.model.ProjectionConfig;
2526
import software.amazon.smithy.build.model.SmithyBuildConfig;
2627
import software.amazon.smithy.model.Model;
2728
import software.amazon.smithy.model.loader.ModelAssembler;
2829
import software.amazon.smithy.model.transform.ModelTransformer;
30+
import software.amazon.smithy.model.validation.ValidatedResult;
31+
import software.amazon.smithy.utils.FunctionalUtils;
2932

3033
/**
3134
* Runs the projections and plugins found in a {@link SmithyBuildConfig}
@@ -167,6 +170,102 @@ public void build(Consumer<ProjectionResult> resultCallback, BiConsumer<String,
167170
new SmithyBuildImpl(this).applyAllProjections(resultCallback, exceptionCallback);
168171
}
169172

173+
/**
174+
* Assembles the model from the given configuration and applies the transforms
175+
* of the named projection, returning the projected model and its validation
176+
* events without running any plugins or writing artifacts to disk. Both
177+
* {@link SmithyBuildConfig#getSources()} and {@link SmithyBuildConfig#getImports()}
178+
* are loaded into the model.
179+
*
180+
* <p>If the base-model assemble is broken, the base result is returned
181+
* unchanged so the caller can inspect its events.
182+
*
183+
* <p>The class loader used for service discovery is the
184+
* {@linkplain Thread#getContextClassLoader() current thread's context
185+
* class loader}, falling back to {@link SmithyBuild}'s class loader. Use
186+
* {@link #toProjectedModel(SmithyBuildConfig, String, ClassLoader)} when
187+
* the loader needs to be controlled explicitly (for example, in IDE or
188+
* application-server environments).
189+
*
190+
* @param config Configuration to load.
191+
* @param projectionName The projection to apply.
192+
* @return The validated projected model.
193+
* @throws UnknownProjectionException if the projection is not declared.
194+
* @throws SmithyBuildException if the projection is abstract or the build
195+
* fails.
196+
*/
197+
public static ValidatedResult<Model> toProjectedModel(SmithyBuildConfig config, String projectionName) {
198+
ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
199+
return toProjectedModel(config,
200+
projectionName,
201+
contextLoader != null ? contextLoader : SmithyBuild.class.getClassLoader());
202+
}
203+
204+
/**
205+
* Assembles the model from the given configuration and applies the transforms
206+
* of the named projection, using the given class loader for service
207+
* discovery of traits, validators, etc., returning the projected model and
208+
* its validation events without running any plugins. Both
209+
* {@link SmithyBuildConfig#getSources()} and {@link SmithyBuildConfig#getImports()}
210+
* are loaded into the model.
211+
*
212+
* <p>If the base-model assemble is broken, the base result is returned
213+
* unchanged so the caller can inspect its events.
214+
*
215+
* @param config Configuration to load.
216+
* @param projectionName The projection to apply.
217+
* @param classLoader Class loader used for service discovery.
218+
* @return The validated projected model.
219+
* @throws UnknownProjectionException if the projection is not declared.
220+
* @throws SmithyBuildException if the projection is abstract or the build
221+
* fails.
222+
*/
223+
public static ValidatedResult<Model> toProjectedModel(
224+
SmithyBuildConfig config,
225+
String projectionName,
226+
ClassLoader classLoader
227+
) {
228+
ProjectionConfig projection = config.getProjections().get(projectionName);
229+
if (projection == null) {
230+
throw new UnknownProjectionException("Unknown projection: " + projectionName);
231+
}
232+
if (projection.isAbstract()) {
233+
throw new SmithyBuildException("Cannot apply abstract projection: " + projectionName);
234+
}
235+
236+
ValidatedResult<Model> baseResult = config.toModelAssembler(classLoader).assemble();
237+
if (baseResult.isBroken()) {
238+
// The inner pipeline unwraps the base model and would throw on ERROR/DANGER events,
239+
// so short-circuit and hand the broken result to the caller intact.
240+
return baseResult;
241+
}
242+
243+
// Restrict the run config to the targeted projection so SmithyBuildImpl does not eagerly
244+
// resolve transformers for unrelated projections (which would throw on configs that mention
245+
// transforms not on this classloader).
246+
SmithyBuildConfig runConfig = config.toBuilder()
247+
.projections(Collections.singletonMap(projectionName, projection))
248+
// Strip sources and imports because the pre-assembled base model is passed via
249+
// .model() below, leaving them would re-parse the same files
250+
.sources(Collections.emptyList())
251+
.imports(Collections.emptyList())
252+
// Set ignoreMissingPlugins because plugin resolution still walks declared plugins
253+
// even though the pluginFilter rejects them all.
254+
.ignoreMissingPlugins(true)
255+
.build();
256+
257+
ProjectionResult result = SmithyBuild.create(classLoader)
258+
.config(runConfig)
259+
.model(baseResult.getResult().get())
260+
.pluginFilter(FunctionalUtils.alwaysFalse())
261+
.build()
262+
.getProjectionResult(projectionName)
263+
.orElseThrow(() -> new IllegalStateException(
264+
"Projection result not found: " + projectionName));
265+
266+
return new ValidatedResult<>(result.getModel(), result.getEvents());
267+
}
268+
170269
/**
171270
* Sets the <strong>required</strong> configuration object used to
172271
* build the model.

smithy-build/src/main/java/software/amazon/smithy/build/model/SmithyBuildConfig.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
import java.util.Optional;
1313
import java.util.Set;
1414
import software.amazon.smithy.build.SmithyBuildException;
15+
import software.amazon.smithy.model.Model;
16+
import software.amazon.smithy.model.loader.ModelAssembler;
1517
import software.amazon.smithy.model.loader.ModelSyntaxException;
1618
import software.amazon.smithy.model.node.Node;
1719
import software.amazon.smithy.model.node.ObjectNode;
@@ -226,6 +228,44 @@ public long getLastModifiedInMillis() {
226228
return lastModifiedInMillis;
227229
}
228230

231+
/**
232+
* Creates a {@link ModelAssembler} populated with the sources and imports
233+
* defined in this configuration. Both {@link #getSources()} and
234+
* {@link #getImports()} are added to the assembler via
235+
* {@link ModelAssembler#addImport(String)}.
236+
*
237+
* <p>Note: Maven dependencies declared in the configuration are not
238+
* resolved by this method. Dependency resolution is the responsibility
239+
* of the caller (for example, the Smithy CLI).
240+
*
241+
* @return Returns a pre-configured {@link ModelAssembler}.
242+
* @see software.amazon.smithy.build.SmithyBuild#toProjectedModel(SmithyBuildConfig, String)
243+
*/
244+
public ModelAssembler toModelAssembler() {
245+
return toModelAssembler(getClass().getClassLoader());
246+
}
247+
248+
/**
249+
* Creates a {@link ModelAssembler} populated with the sources and imports
250+
* defined in this configuration, using the given {@code ClassLoader} for
251+
* service discovery of traits, validators, and other providers. Both
252+
* {@link #getSources()} and {@link #getImports()} are added to the assembler
253+
* via {@link ModelAssembler#addImport(String)}.
254+
*
255+
* <p>Note: Maven dependencies declared in the configuration are not
256+
* resolved by this method. Dependency resolution is the responsibility
257+
* of the caller (for example, the Smithy CLI).
258+
*
259+
* @param classLoader Class loader used to discover traits and validators.
260+
* @return Returns a pre-configured {@link ModelAssembler}.
261+
*/
262+
public ModelAssembler toModelAssembler(ClassLoader classLoader) {
263+
ModelAssembler assembler = Model.assembler(classLoader);
264+
sources.forEach(assembler::addImport);
265+
imports.forEach(assembler::addImport);
266+
return assembler;
267+
}
268+
229269
/**
230270
* Builder used to create a {@link SmithyBuildConfig}.
231271
*/

0 commit comments

Comments
 (0)