diff --git a/config/spotbugs/filter.xml b/config/spotbugs/filter.xml
index 24d313006d0..23aacb80283 100644
--- a/config/spotbugs/filter.xml
+++ b/config/spotbugs/filter.xml
@@ -158,6 +158,10 @@
+
+
+
+
diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/ClasspathAction.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/ClasspathAction.java
index 75bf97a0ff7..92a69df8a27 100644
--- a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/ClasspathAction.java
+++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/ClasspathAction.java
@@ -135,7 +135,7 @@ private List resolveDependencies(
DependencyResolver resolver = new FileCacheResolver(getCacheFile(buildOptions, smithyBuildConfig),
lastModified,
delegate);
- addConfiguredMavenRepos(smithyBuildConfig, resolver);
+ DependencyHelper.addConfiguredMavenRepos(smithyBuildConfig, resolver);
maven.getDependencies().forEach(resolver::addDependency);
List artifacts = resolver.resolve();
LOGGER.fine(() -> "Classpath resolved with Maven: " + artifacts);
@@ -148,29 +148,6 @@ private List resolveDependencies(
return result;
}
- private static void addConfiguredMavenRepos(SmithyBuildConfig config, DependencyResolver resolver) {
- // Environment variables take precedence over config files.
- String envRepos = EnvironmentVariable.SMITHY_MAVEN_REPOS.get();
- if (envRepos != null) {
- for (String repo : envRepos.split("\\|")) {
- resolver.addRepository(MavenRepository.builder().url(repo.trim()).build());
- }
- }
-
- Set configuredRepos = config.getMaven()
- .map(MavenConfig::getRepositories)
- .orElse(Collections.emptySet());
-
- if (!configuredRepos.isEmpty()) {
- configuredRepos.forEach(resolver::addRepository);
- } else if (envRepos == null) {
- LOGGER.finest(() -> String.format("maven.repositories is not defined in smithy-build.json and the %s "
- + "environment variable is not set. Defaulting to Maven Central.",
- EnvironmentVariable.SMITHY_MAVEN_REPOS));
- resolver.addRepository(CENTRAL);
- }
- }
-
private File getCacheFile(BuildOptions buildOptions, SmithyBuildConfig config) {
return buildOptions.resolveOutput(config).resolve("classpath.json").toFile();
}
diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/DependencyHelper.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/DependencyHelper.java
new file mode 100644
index 00000000000..393cb6cfe79
--- /dev/null
+++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/DependencyHelper.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.smithy.cli.commands;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.logging.Logger;
+import software.amazon.smithy.build.model.MavenConfig;
+import software.amazon.smithy.build.model.MavenRepository;
+import software.amazon.smithy.build.model.SmithyBuildConfig;
+import software.amazon.smithy.cli.EnvironmentVariable;
+import software.amazon.smithy.cli.dependencies.DependencyResolver;
+
+final class DependencyHelper {
+
+ private static final Logger LOGGER = Logger.getLogger(DependencyHelper.class.getName());
+ private static final MavenRepository CENTRAL = MavenRepository.builder()
+ .url("https://repo.maven.apache.org/maven2")
+ .build();
+
+ private DependencyHelper() { }
+
+ static void addConfiguredMavenRepos(SmithyBuildConfig config, DependencyResolver resolver) {
+ // Environment variables take precedence over config files.
+ String envRepos = EnvironmentVariable.SMITHY_MAVEN_REPOS.get();
+ if (envRepos != null) {
+ for (String repo : envRepos.split("\\|")) {
+ resolver.addRepository(MavenRepository.builder().url(repo.trim()).build());
+ }
+ }
+
+ Set configuredRepos = config.getMaven()
+ .map(MavenConfig::getRepositories)
+ .orElse(Collections.emptySet());
+
+ if (!configuredRepos.isEmpty()) {
+ configuredRepos.forEach(resolver::addRepository);
+ } else if (envRepos == null) {
+ LOGGER.finest(() -> String.format("maven.repositories is not defined in smithy-build.json and the %s "
+ + "environment variable is not set. Defaulting to Maven Central.",
+ EnvironmentVariable.SMITHY_MAVEN_REPOS));
+ resolver.addRepository(CENTRAL);
+ }
+ }
+}
diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/FormatCommand.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/FormatCommand.java
new file mode 100644
index 00000000000..dc832384636
--- /dev/null
+++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/FormatCommand.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.smithy.cli.commands;
+
+import java.io.FileWriter;
+import java.io.PrintWriter;
+import java.lang.reflect.Method;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import software.amazon.smithy.build.model.SmithyBuildConfig;
+import software.amazon.smithy.cli.Arguments;
+import software.amazon.smithy.cli.CliError;
+import software.amazon.smithy.cli.Command;
+import software.amazon.smithy.cli.StandardOptions;
+import software.amazon.smithy.cli.dependencies.DependencyResolver;
+import software.amazon.smithy.cli.dependencies.ResolvedArtifact;
+import software.amazon.smithy.utils.IoUtils;
+
+final class FormatCommand implements Command {
+
+ private final String parentCommandName;
+ private final DependencyResolver.Factory dependencyResolverFactory;
+
+ FormatCommand(String parentCommandName, DependencyResolver.Factory dependencyResolverFactory) {
+ this.parentCommandName = parentCommandName;
+ this.dependencyResolverFactory = dependencyResolverFactory;
+ }
+
+ @Override
+ public String getName() {
+ return "format";
+ }
+
+ @Override
+ public String getSummary() {
+ return "Formats Smithy IDL models in place.";
+ }
+
+ @Override
+ public int execute(Arguments arguments, Env env) {
+ arguments.addReceiver(new ConfigOptions());
+
+ CommandAction action = HelpActionWrapper.fromCommand(
+ this, parentCommandName, this::runFormatter);
+
+ return action.apply(arguments, env);
+ }
+
+ private int runFormatter(Arguments arguments, Env env) {
+ StandardOptions standardOptions = arguments.getReceiver(StandardOptions.class);
+ ConfigOptions configOptions = arguments.getReceiver(ConfigOptions.class);
+ SmithyBuildConfig config = configOptions.createSmithyBuildConfig();
+ DependencyResolver resolver = dependencyResolverFactory.create(config, env);
+
+ if (!standardOptions.quiet()) {
+ env.stderr().append("Checking for Smithy formatter...").flush();
+ }
+
+ DependencyHelper.addConfiguredMavenRepos(config, resolver);
+ resolver.addDependency("software.amazon.smithy:smithy-language-server:LATEST");
+ List resolvedArtifacts = resolver.resolve();
+ List dependencies = new ArrayList<>(resolvedArtifacts.size());
+ for (ResolvedArtifact artifact : resolvedArtifacts) {
+ dependencies.add(artifact.getPath());
+ }
+
+ if (env.colors().isColorEnabled()) {
+ env.stderr().append("\r").append(StyleHelper.CLEAR_LINE_ESCAPE).flush();
+ }
+
+ List positional = arguments.getPositional();
+ if (positional.isEmpty()) {
+ throw new CliError("Missing required positional argument pointing to a file to format");
+ } else if (positional.size() > 1) {
+ throw new CliError("Can only format a single model file");
+ }
+
+ String file = positional.get(0);
+ String contents = IoUtils.readUtf8File(file);
+
+ new IsolatedRunnable(dependencies, getClass().getClassLoader().getParent(), loader -> {
+ try {
+ Class> main = loader.loadClass("smithyfmt.Formatter");
+ Method method = main.getMethod("format", String.class);
+ Object response = method.invoke(null, contents);
+ Method isSuccess = response.getClass().getMethod("isSuccess");
+ if ((boolean) isSuccess.invoke(response)) {
+ Method valueMethod = response.getClass().getMethod("getValue");
+ String formatted = (String) valueMethod.invoke(response);
+ FileWriter fileWriter = new FileWriter(file);
+ PrintWriter printWriter = new PrintWriter(fileWriter);
+ printWriter.print(formatted);
+ printWriter.close();
+ } else {
+ Method errorMessage = response.getClass().getMethod("getError");
+ throw new CliError("Error formatting model: " + errorMessage.invoke(response), 1);
+ }
+ } catch (CliError e) {
+ throw e;
+ } catch (Exception e) {
+ throw new CliError("Error formatting model", 1, e);
+ }
+ }).run();
+
+ return 0;
+ }
+}
diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/JavaHelper.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/JavaHelper.java
new file mode 100644
index 00000000000..42262daaa61
--- /dev/null
+++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/JavaHelper.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.smithy.cli.commands;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import software.amazon.smithy.cli.CliError;
+import software.amazon.smithy.utils.StringUtils;
+
+final class JavaHelper {
+
+ private JavaHelper() {}
+
+ static Path getJavaHome() {
+ return Paths.get(getOrThrowIfUndefinedProperty("java.home"));
+ }
+
+ static Path getJavaBinary() {
+ Path javaHome = getJavaHome();
+ Path bin = javaHome.resolve("bin");
+ Path windowsBinary = bin.resolve("java.exe");
+ Path posixBinary = bin.resolve("java");
+
+ if (!Files.isDirectory(bin)) {
+ throw new CliError("$JAVA_HOME/bin directory not found: " + bin);
+ } else if (Files.exists(windowsBinary)) {
+ return windowsBinary;
+ } else if (Files.exists(posixBinary)) {
+ return posixBinary;
+ } else {
+ throw new CliError("No java binary found in " + bin);
+ }
+ }
+
+ private static String getOrThrowIfUndefinedProperty(String property) {
+ String result = System.getProperty(property);
+ if (StringUtils.isEmpty(result)) {
+ throw new CliError(result + " system property is not defined");
+ }
+ return result;
+ }
+}
diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/LspCommand.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/LspCommand.java
new file mode 100644
index 00000000000..b5b0495f790
--- /dev/null
+++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/LspCommand.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.smithy.cli.commands;
+
+import java.lang.reflect.Method;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+import software.amazon.smithy.build.model.SmithyBuildConfig;
+import software.amazon.smithy.cli.ArgumentReceiver;
+import software.amazon.smithy.cli.Arguments;
+import software.amazon.smithy.cli.CliError;
+import software.amazon.smithy.cli.Command;
+import software.amazon.smithy.cli.HelpPrinter;
+import software.amazon.smithy.cli.StandardOptions;
+import software.amazon.smithy.cli.dependencies.DependencyResolver;
+import software.amazon.smithy.cli.dependencies.ResolvedArtifact;
+
+final class LspCommand implements Command {
+
+ private final String parentCommandName;
+ private final DependencyResolver.Factory dependencyResolverFactory;
+
+ LspCommand(String parentCommandName, DependencyResolver.Factory dependencyResolverFactory) {
+ this.parentCommandName = parentCommandName;
+ this.dependencyResolverFactory = dependencyResolverFactory;
+ }
+
+ private static final class Options implements ArgumentReceiver {
+ private String version = "LATEST";
+
+ @Override
+ public Consumer testParameter(String name) {
+ if ("--version".equals(name)) {
+ return v -> version = v;
+ }
+ return null;
+ }
+
+ @Override
+ public void registerHelp(HelpPrinter printer) {
+ printer.param("--version", null, "LSP_VERSION",
+ "Provides a custom LSP version. Uses the latest version by default.");
+ }
+ }
+
+ @Override
+ public String getName() {
+ return "lsp";
+ }
+
+ @Override
+ public String getSummary() {
+ return "Downloads, starts, and stops the Smithy Language Server Protocol (LSP).";
+ }
+
+ @Override
+ public int execute(Arguments arguments, Env env) {
+ arguments.addReceiver(new ConfigOptions());
+ arguments.addReceiver(new Options());
+
+ CommandAction action = HelpActionWrapper.fromCommand(
+ this, parentCommandName, this::runLsp);
+
+ return action.apply(arguments, env);
+ }
+
+ private int runLsp(Arguments arguments, Env env) {
+ StandardOptions standardOptions = arguments.getReceiver(StandardOptions.class);
+ ConfigOptions configOptions = arguments.getReceiver(ConfigOptions.class);
+ SmithyBuildConfig config = configOptions.createSmithyBuildConfig();
+ DependencyResolver resolver = dependencyResolverFactory.create(config, env);
+ Options options = arguments.getReceiver(Options.class);
+
+ if (!standardOptions.quiet()) {
+ env.stderr().println("Checking for Smithy LSP...").flush();
+ }
+
+ DependencyHelper.addConfiguredMavenRepos(config, resolver);
+ resolver.addDependency("software.amazon.smithy:smithy-language-server:" + options.version);
+ List resolvedArtifacts = resolver.resolve();
+ List dependencies = new ArrayList<>(resolvedArtifacts.size());
+ for (ResolvedArtifact artifact : resolvedArtifacts) {
+ dependencies.add(artifact.getPath());
+ }
+
+ if (!standardOptions.quiet()) {
+ env.stderr().println("Starting LSP at version: " + resolvedArtifacts.get(0).getVersion()).flush();
+ }
+
+ new IsolatedRunnable(dependencies, getClass().getClassLoader().getParent(), loader -> {
+ try {
+ Class> main = loader.loadClass("software.amazon.smithy.lsp.Main");
+ Method method = main.getMethod("main", String[].class);
+ method.invoke(null, (Object) new String[]{"0"});
+ } catch (Exception e) {
+ throw new CliError("Error running LSP", 1, e);
+ }
+ }).run();
+
+ return 0;
+ }
+}
diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/ModelBuilder.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/ModelBuilder.java
index b9020bee8c9..bec6fe203a0 100644
--- a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/ModelBuilder.java
+++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/ModelBuilder.java
@@ -48,7 +48,6 @@
final class ModelBuilder {
private static final Logger LOGGER = Logger.getLogger(ModelBuilder.class.getName());
- private static final String CLEAR_LINE_ESCAPE = "\033[2K\r";
private static final int DEFAULT_CODE_LINES = 6;
private Validator.Mode validationMode;
@@ -217,8 +216,7 @@ static Consumer createStatusUpdater(
// If a status update was printed, then clear it out.
static void clearStatusUpdateIfPresent(AtomicInteger issueCount, CliPrinter stderr) {
if (issueCount.get() > 0) {
- stderr.append(CLEAR_LINE_ESCAPE);
- stderr.flush();
+ stderr.append(StyleHelper.CLEAR_LINE_ESCAPE).flush();
}
}
diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/SmithyCommand.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/SmithyCommand.java
index 167615aa1e4..927586f39c9 100644
--- a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/SmithyCommand.java
+++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/SmithyCommand.java
@@ -47,6 +47,8 @@ public SmithyCommand(DependencyResolver.Factory dependencyResolverFactory) {
new AstCommand(getName(), dependencyResolverFactory),
new SelectCommand(getName(), dependencyResolverFactory),
new CleanCommand(getName()),
+ new LspCommand(getName(), dependencyResolverFactory),
+ new FormatCommand(getName(), dependencyResolverFactory),
migrateCommand,
deprecated1To2Command,
new WarmupCommand(getName())
diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/StyleHelper.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/StyleHelper.java
index 727d014ec59..c618f3a4607 100644
--- a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/StyleHelper.java
+++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/StyleHelper.java
@@ -22,6 +22,7 @@
final class StyleHelper {
+ static final String CLEAR_LINE_ESCAPE = "\033[2K\r";
private static final Pattern TICK_PATTERN = Pattern.compile("`(.*?)`");
private StyleHelper() {}
diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/WarmupCommand.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/WarmupCommand.java
index 4923e0c29d4..87de28ca073 100644
--- a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/WarmupCommand.java
+++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/WarmupCommand.java
@@ -20,7 +20,6 @@
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -105,13 +104,10 @@ private int run(Arguments arguments, Env env) {
}
private int orchestrate(boolean isDebug, CliPrinter printer) {
- List baseArgs = new ArrayList<>();
String classpath = getOrThrowIfUndefinedProperty("java.class.path");
- Path javaHome = Paths.get(getOrThrowIfUndefinedProperty("java.home"));
+ Path javaHome = JavaHelper.getJavaHome();
+ Path javaBinary = JavaHelper.getJavaBinary();
Path lib = javaHome.resolve("lib");
- Path bin = javaHome.resolve("bin");
- Path windowsBinary = bin.resolve("java.exe");
- Path posixBinary = bin.resolve("java");
Path jsaFile = lib.resolve("smithy.jsa");
Path classListFile = lib.resolve("classlist");
@@ -119,16 +115,8 @@ private int orchestrate(boolean isDebug, CliPrinter printer) {
classListFile.toFile().delete();
jsaFile.toFile().delete();
- if (!Files.isDirectory(bin)) {
- throw new CliError("$JAVA_HOME/bin directory not found: " + bin);
- } else if (Files.exists(windowsBinary)) {
- baseArgs.add(windowsBinary.toString());
- } else if (Files.exists(posixBinary)) {
- baseArgs.add(posixBinary.toString());
- } else {
- throw new CliError("No java binary found in " + bin);
- }
-
+ List baseArgs = new ArrayList<>();
+ baseArgs.add(javaBinary.toString());
baseArgs.add("-classpath");
baseArgs.add(classpath);
diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/dependencies/MavenDependencyResolver.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/dependencies/MavenDependencyResolver.java
index 95c49e07739..a0aabc2920c 100644
--- a/smithy-cli/src/main/java/software/amazon/smithy/cli/dependencies/MavenDependencyResolver.java
+++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/dependencies/MavenDependencyResolver.java
@@ -161,10 +161,11 @@ private static Dependency createDependency(String coordinates, String scope) {
private static void validateDependencyVersion(Artifact artifact) {
String version = artifact.getVersion();
- if (version.equals("LATEST")) {
- throw new DependencyResolverException("LATEST dependencies are not supported: " + artifact);
- } else if (version.equals("latest-status") || version.startsWith("latest.")) {
- throw new DependencyResolverException("Gradle style latest dependencies are not supported: " + artifact);
+ if (version.equals("latest.release")) {
+ // Change latest.release to LATEST.
+ artifact.setVersion("LATEST");
+ } else if (version.equals("latest-status")) {
+ throw new DependencyResolverException("latest-status is not supported: " + artifact);
} else if (version.equals("RELEASE")) {
throw new DependencyResolverException("RELEASE dependencies are not supported: " + artifact);
} else if (version.contains("+")) {
diff --git a/smithy-cli/src/test/java/software/amazon/smithy/cli/dependencies/MavenDependencyResolverTest.java b/smithy-cli/src/test/java/software/amazon/smithy/cli/dependencies/MavenDependencyResolverTest.java
index c618d1af2b4..eb3408c112b 100644
--- a/smithy-cli/src/test/java/software/amazon/smithy/cli/dependencies/MavenDependencyResolverTest.java
+++ b/smithy-cli/src/test/java/software/amazon/smithy/cli/dependencies/MavenDependencyResolverTest.java
@@ -39,7 +39,6 @@ public static Stream invalidDependencies() {
Arguments.of("smithy.foo:bar:1.25.0-SNAPSHOT"),
Arguments.of("smithy.foo:bar:RELEASE"),
Arguments.of("smithy.foo:bar:latest-status"),
- Arguments.of("smithy.foo:bar:LATEST"),
Arguments.of("smithy.foo:bar:1.25.0+"),
Arguments.of("a::1.2.0"),
Arguments.of(":b:1.2.0"),