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"),