diff --git a/devtools/cli/src/main/java/io/quarkus/cli/ProjectExtensionsList.java b/devtools/cli/src/main/java/io/quarkus/cli/ProjectExtensionsList.java index 81c08e7fde9690..c59bb18afdd7b6 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/ProjectExtensionsList.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/ProjectExtensionsList.java @@ -8,7 +8,12 @@ import io.quarkus.cli.build.BuildSystemRunner; import io.quarkus.cli.common.ListFormatOptions; import io.quarkus.cli.common.RunModeOption; +import io.quarkus.cli.create.TargetQuarkusVersionGroup; +import io.quarkus.devtools.commands.ListExtensions; +import io.quarkus.devtools.commands.data.QuarkusCommandException; +import io.quarkus.devtools.commands.data.QuarkusCommandOutcome; import io.quarkus.devtools.project.BuildTool; +import io.quarkus.devtools.project.QuarkusProject; import picocli.CommandLine; import picocli.CommandLine.Mixin; @@ -18,12 +23,15 @@ public class ProjectExtensionsList extends BaseBuildCommand implements Callable< @Mixin RunModeOption runMode; + @CommandLine.ArgGroup(order = 2, heading = "%nQuarkus version%n") + TargetQuarkusVersionGroup targetQuarkusVersion = new TargetQuarkusVersionGroup(); + @CommandLine.Option(names = { "-i", "--installable" }, defaultValue = "false", order = 2, description = "Display installable extensions.") boolean installable = false; @CommandLine.Option(names = { "-s", - "--search" }, defaultValue = "*", paramLabel = "PATTERN", order = 3, description = "Search filter on extension list. The format is based on Java Pattern.") + "--search" }, defaultValue = "*", paramLabel = "PATTERN", order = 3, description = "Search filter on extension list (Java Pattern syntax).") String searchPattern; @CommandLine.ArgGroup(heading = "%nOutput format%n") @@ -35,33 +43,67 @@ public Integer call() { output.debug("List extensions with initial parameters: %s", this); output.throwIfUnmatchedArguments(spec.commandLine()); - BuildSystemRunner runner = getRunner(); - if (runMode.isDryRun()) { - dryRunList(spec.commandLine().getHelp(), runner.getBuildTool()); - return CommandLine.ExitCode.OK; - } + if (targetQuarkusVersion.isPlatformSpecified() || targetQuarkusVersion.isStreamSpecified()) { + // do not evaluate installables for list of arbitrary version (project-agnostic) + installable = false; + // show origins by default (as it could be different than current project) + format.useOriginsUnlessSpecified(); + + if (runMode.isDryRun()) { + return dryRunList(spec.commandLine().getHelp(), null); + } + return listPlatformExtensions(); + } else { + BuildSystemRunner runner = getRunner(); - return runner.listExtensions(runMode, format, installable, searchPattern); + if (runMode.isDryRun()) { + return dryRunList(spec.commandLine().getHelp(), runner.getBuildTool()); + } + return runner.listExtensions(runMode, format, installable, searchPattern); + } } catch (Exception e) { return output.handleCommandException(e, "Unable to list extensions: " + e.getMessage()); } } - void dryRunList(CommandLine.Help help, BuildTool buildTool) { - output.printText(new String[] { - "\nList extensions for current project\n", - "\t" + projectRoot().toString() - }); + Integer dryRunList(CommandLine.Help help, BuildTool buildTool) { Map dryRunOutput = new TreeMap<>(); - dryRunOutput.put("Build tool", buildTool.name()); + if (buildTool == null) { + output.printText(new String[] { + "\nList extensions for specified platform\n", + "\t" + targetQuarkusVersion.dryRun() + }); + } else { + output.printText(new String[] { + "\nList extensions for current project\n", + "\t" + projectRoot().toString() + }); + dryRunOutput.put("Build tool", buildTool.name()); + } + dryRunOutput.put("Batch (non-interactive mode)", Boolean.toString(runMode.isBatchMode())); dryRunOutput.put("List format", format.getFormatString()); dryRunOutput.put("List installable extensions", Boolean.toString(installable)); dryRunOutput.put("Search pattern", searchPattern); output.info(help.createTextTable(dryRunOutput).toString()); + return CommandLine.ExitCode.OK; + } + + Integer listPlatformExtensions() throws QuarkusCommandException { + QuarkusProject qp = registryClient.createQuarkusProject(projectRoot(), targetQuarkusVersion, + BuildTool.MAVEN, output); + + QuarkusCommandOutcome outcome = new ListExtensions(qp, output) + .fromCli(true) + .all(true) + .format(format.getFormatString()) + .search(searchPattern) + .execute(); + + return outcome.isSuccess() ? CommandLine.ExitCode.OK : CommandLine.ExitCode.SOFTWARE; } @Override diff --git a/devtools/cli/src/main/java/io/quarkus/cli/common/ListFormatOptions.java b/devtools/cli/src/main/java/io/quarkus/cli/common/ListFormatOptions.java index 6fd62d319d402d..a61235279ff313 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/common/ListFormatOptions.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/common/ListFormatOptions.java @@ -17,6 +17,17 @@ public class ListFormatOptions { "--origins" }, order = 7, description = "Display extensions including their platform origins.") boolean origins = false; + /** + * If a value was not specified via options (all false), + * make origins true. Used with specific platform list. + */ + public void useOriginsUnlessSpecified() { + if (name || concise || full || origins) { + return; + } + origins = true; + } + public String getFormatString() { String formatString = "name"; if (concise) diff --git a/devtools/cli/src/main/java/io/quarkus/cli/common/RegistryClientMixin.java b/devtools/cli/src/main/java/io/quarkus/cli/common/RegistryClientMixin.java index 273b1d614fff74..ffd94619b24abf 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/common/RegistryClientMixin.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/common/RegistryClientMixin.java @@ -1,9 +1,14 @@ package io.quarkus.cli.common; +import java.nio.file.Path; + import io.quarkus.cli.Version; import io.quarkus.cli.create.TargetQuarkusVersionGroup; +import io.quarkus.devtools.project.BuildTool; +import io.quarkus.devtools.project.QuarkusProject; import io.quarkus.devtools.project.QuarkusProjectHelper; import io.quarkus.maven.ArtifactCoords; +import io.quarkus.maven.StreamCoords; import io.quarkus.platform.tools.ToolsUtils; import io.quarkus.registry.ExtensionCatalogResolver; import io.quarkus.registry.catalog.ExtensionCatalog; @@ -18,7 +23,13 @@ public boolean enabled() { return enableRegistryClient; } - public ExtensionCatalog getExtensionCatalog(TargetQuarkusVersionGroup targetVersion, OutputOptionMixin log) { + public QuarkusProject createQuarkusProject(Path projectRoot, TargetQuarkusVersionGroup targetVersion, BuildTool buildTool, + OutputOptionMixin log) { + ExtensionCatalog catalog = getExtensionCatalog(targetVersion, log); + return QuarkusProjectHelper.getProject(projectRoot, catalog, buildTool, log); + } + + ExtensionCatalog getExtensionCatalog(TargetQuarkusVersionGroup targetVersion, OutputOptionMixin log) { log.debug("Resolving Quarkus extension catalog for " + targetVersion); QuarkusProjectHelper.setMessageWriter(log); @@ -38,12 +49,9 @@ public ExtensionCatalog getExtensionCatalog(TargetQuarkusVersionGroup targetVers QuarkusProjectHelper.artifactResolver(), log); } - if (targetVersion.isStream()) { - final String stream = targetVersion.getStream(); - final int colon = stream.indexOf(':'); - final String platformKey = colon <= 0 ? null : stream.substring(0, colon); - final String streamId = colon < 0 ? stream : stream.substring(colon + 1); - return catalogResolver.resolveExtensionCatalog(platformKey, streamId); + if (targetVersion.isStreamSpecified()) { + final StreamCoords stream = targetVersion.getStream(); + return catalogResolver.resolveExtensionCatalog(stream.getPlatformKey()); } return catalogResolver.resolveExtensionCatalog(); diff --git a/devtools/cli/src/main/java/io/quarkus/cli/common/RunModeOption.java b/devtools/cli/src/main/java/io/quarkus/cli/common/RunModeOption.java index d86f6a7da99128..9fdb34fa8b6fac 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/common/RunModeOption.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/common/RunModeOption.java @@ -8,7 +8,11 @@ public class RunModeOption { "--batch-mode" }, description = "Run in non-interactive (batch) mode.") boolean batchMode; - @CommandLine.Option(names = { "--dryrun" }, description = "Show actions that would be taken.") + // Allow the option variant, but don't crowd help + @CommandLine.Option(names = { "--dryrun" }, hidden = true) + boolean dryRun2 = false; + + @CommandLine.Option(names = { "--dry-run" }, description = "Show actions that would be taken.") boolean dryRun = false; public boolean isBatchMode() { @@ -16,12 +20,12 @@ public boolean isBatchMode() { } public boolean isDryRun() { - return dryRun; + return dryRun || dryRun2; } @Override public String toString() { - return "RunModeOption [batchMode=" + batchMode + ", dryRun=" + dryRun + "]"; + return "RunModeOption [batchMode=" + batchMode + ", dryRun=" + isDryRun() + "]"; } } diff --git a/devtools/cli/src/main/java/io/quarkus/cli/create/CreateProjectMixin.java b/devtools/cli/src/main/java/io/quarkus/cli/create/CreateProjectMixin.java index 4f3f349d295b8c..57b0eb7abd5f6f 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/create/CreateProjectMixin.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/create/CreateProjectMixin.java @@ -14,12 +14,10 @@ import io.quarkus.devtools.commands.data.QuarkusCommandInvocation; import io.quarkus.devtools.project.BuildTool; import io.quarkus.devtools.project.QuarkusProject; -import io.quarkus.devtools.project.QuarkusProjectHelper; import io.quarkus.devtools.project.codegen.CreateProjectHelper; import io.quarkus.devtools.project.codegen.ProjectGenerator; import io.quarkus.devtools.project.codegen.SourceType; import io.quarkus.registry.RegistryResolutionException; -import io.quarkus.registry.catalog.ExtensionCatalog; import picocli.CommandLine.Help; import picocli.CommandLine.Mixin; import picocli.CommandLine.Model.CommandSpec; @@ -105,8 +103,7 @@ public QuarkusCommandInvocation build(BuildTool buildTool, TargetQuarkusVersionG buildTool = BuildTool.MAVEN; } - ExtensionCatalog catalog = registryClient.getExtensionCatalog(targetVersion, log); - QuarkusProject qp = QuarkusProjectHelper.getProject(projectRoot(), catalog, buildTool, log); + QuarkusProject qp = registryClient.createQuarkusProject(projectRoot(), targetVersion, buildTool, log); return new QuarkusCommandInvocation(qp, values); } diff --git a/devtools/cli/src/main/java/io/quarkus/cli/create/TargetQuarkusVersionGroup.java b/devtools/cli/src/main/java/io/quarkus/cli/create/TargetQuarkusVersionGroup.java index 8194bcee32418d..1e1617daeab38e 100644 --- a/devtools/cli/src/main/java/io/quarkus/cli/create/TargetQuarkusVersionGroup.java +++ b/devtools/cli/src/main/java/io/quarkus/cli/create/TargetQuarkusVersionGroup.java @@ -1,26 +1,44 @@ package io.quarkus.cli.create; import io.quarkus.maven.ArtifactCoords; +import io.quarkus.maven.StreamCoords; import picocli.CommandLine; import picocli.CommandLine.Model.CommandSpec; public class TargetQuarkusVersionGroup { + StreamCoords streamCoords = null; + String validStream = null; + ArtifactCoords platformBom = null; + String validPlatformBom = null; @CommandLine.Spec CommandSpec spec; - @CommandLine.Option(paramLabel = "STREAM", names = { "-S", - "--stream" }, description = "A target stream, e.g. default, snapshot", hidden = true) - String stream; + @CommandLine.Option(paramLabel = "platformKey:streamId", names = { "-S", + "--stream" }, description = "A target stream, for example:%n io.quarkus.platform:999-SNAPSHOT%n io.quarkus:1.13") + void setStream(String stream) { + stream = stream.trim(); + if (!stream.isEmpty()) { + try { + streamCoords = StreamCoords.fromString(stream); + validStream = stream; + } catch (IllegalArgumentException iex) { + throw new CommandLine.ParameterException(spec.commandLine(), + String.format("Invalid value '%s' for option '--stream'. " + + "Value should be specified as 'platformKey:streamId'. %s", iex.getMessage())); + } + } + } @CommandLine.Option(paramLabel = "groupId:artifactId:version", names = { "-p", - "--platform-bom" }, description = "A specific Quarkus platform BOM,%ne.g. io.quarkus:quarkus-bom:1.13.4.Final") + "--platform-bom" }, description = "A specific Quarkus platform BOM, for example:%n io.quarkus:quarkus-bom:1.13.4.Final") void setPlatformBom(String bom) { bom = bom.trim(); if (!bom.isEmpty()) { try { platformBom = ArtifactCoords.fromString(bom); + validPlatformBom = bom; // keep original (valid) string (dryrun) } catch (IllegalArgumentException iex) { throw new CommandLine.ParameterException(spec.commandLine(), String.format("Invalid value '%s' for option '--platform-bom'. " + @@ -37,18 +55,28 @@ public ArtifactCoords getPlatformBom() { return platformBom; } - public boolean isStream() { - return stream != null; + public boolean isStreamSpecified() { + return streamCoords != null; } - public String getStream() { - return stream; + public StreamCoords getStream() { + return streamCoords; + } + + public String dryRun() { + if (streamCoords != null) { + return "stream " + validStream; + } else if (platformBom != null) { + return "platform " + validPlatformBom; + } else { + return "same as project"; + } } @Override public String toString() { return "TargetQuarkusVersionGroup{" - + "stream=" + stream + + "stream=" + streamCoords + ", platformBom=" + platformBom + '}'; } diff --git a/devtools/cli/src/test/java/io/quarkus/cli/CliNonProjectTest.java b/devtools/cli/src/test/java/io/quarkus/cli/CliNonProjectTest.java index 5f7c2041617413..f9d585337ebff6 100644 --- a/devtools/cli/src/test/java/io/quarkus/cli/CliNonProjectTest.java +++ b/devtools/cli/src/test/java/io/quarkus/cli/CliNonProjectTest.java @@ -49,6 +49,18 @@ public void testListOutsideOfProject() throws Exception { System.out.println(result); } + @Test + public void testListPlatformExtensions() throws Exception { + // List extensions of a specified platform version + CliDriver.Result result = CliDriver.execute("ext", "list", "-p=io.quarkus:quarkus-bom:2.0.0.CR3", "-e"); + Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, + "Expected OK return code." + result); + Assertions.assertTrue(result.stdout.contains("Jackson"), + "Should contain 'Jackson' in the list of extensions, found: " + result.stdout); + Assertions.assertTrue(result.stdout.contains("2.0.0.CR3"), + "Should contain '2.0.0.CR3' in the list of extensions (origin), found: " + result.stdout); + } + @Test public void testBuildOutsideOfProject() throws Exception { CliDriver.Result result = CliDriver.execute("build", "-e"); @@ -68,28 +80,14 @@ public void testDevOutsideOfProject() throws Exception { @Test public void testCreateAppDryRun() throws Exception { // A dry run of create should not create any files or directories - CliDriver.Result result = CliDriver.execute("create", "--dryrun", "-e"); + CliDriver.Result result = CliDriver.execute("create", "--dry-run", "-e"); Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, "Expected OK return code." + result); Assertions.assertTrue(result.stdout.contains("project would have been created"), "Should contain 'project would have been created', found: " + result.stdout); - } - - @Test - public void testCreateAppDryRunMistype() throws Exception { - // A dry run of create should not create any files or directories - CliDriver.Result result = CliDriver.execute("create", "--dry-run", "-e", "--verbose"); - Assertions.assertEquals(CommandLine.ExitCode.USAGE, result.exitCode, - "Expected OK return code." + result); - } - @Test - public void testCreateCliDryRun() throws Exception { - // A dry run of create should not create any files or directories - CliDriver.Result result = CliDriver.execute("create", "cli", "--dryrun", "-e"); - Assertions.assertEquals(CommandLine.ExitCode.OK, result.exitCode, - "Expected OK return code." + result); - Assertions.assertTrue(result.stdout.contains("project would have been created"), - "Should contain 'project would have been created', found: " + result.stdout); + CliDriver.Result result2 = CliDriver.execute("create", "--dryrun", "-e"); + Assertions.assertEquals(result.stdout, result2.stdout, + "Invoking the command with --dryrun should produce the same result"); } } diff --git a/independent-projects/tools/artifact-api/src/main/java/io/quarkus/maven/StreamCoords.java b/independent-projects/tools/artifact-api/src/main/java/io/quarkus/maven/StreamCoords.java new file mode 100644 index 00000000000000..147ce225506010 --- /dev/null +++ b/independent-projects/tools/artifact-api/src/main/java/io/quarkus/maven/StreamCoords.java @@ -0,0 +1,34 @@ +package io.quarkus.maven; + +public class StreamCoords { + final String platformKey; + final String streamId; + + public static StreamCoords fromString(String stream) { + final int colon = stream.indexOf(':'); + String platformKey = colon <= 0 ? null : stream.substring(0, colon); + String streamId = colon < 0 ? stream : stream.substring(colon + 1); + return new StreamCoords(platformKey, streamId); + } + + public StreamCoords(String platformKey, String streamId) { + this.platformKey = platformKey; + this.streamId = streamId; + } + + public String getPlatformKey() { + return platformKey; + } + + public String getStreamId() { + return streamId; + } + + @Override + public String toString() { + return "StreamCoords{" + + "platformKey='" + platformKey + '\'' + + ", streamId='" + streamId + '\'' + + '}'; + } +} diff --git a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/ExtensionCatalogResolver.java b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/ExtensionCatalogResolver.java index c1296a1c882b68..13d628a5f1b746 100644 --- a/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/ExtensionCatalogResolver.java +++ b/independent-projects/tools/registry-client/src/main/java/io/quarkus/registry/ExtensionCatalogResolver.java @@ -4,6 +4,7 @@ import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver; import io.quarkus.devtools.messagewriter.MessageWriter; import io.quarkus.maven.ArtifactCoords; +import io.quarkus.maven.StreamCoords; import io.quarkus.registry.catalog.ExtensionCatalog; import io.quarkus.registry.catalog.Platform; import io.quarkus.registry.catalog.PlatformCatalog; @@ -388,7 +389,7 @@ public ExtensionCatalog resolveExtensionCatalog(String quarkusCoreVersion) throw return JsonCatalogMerger.merge(extensionCatalogs); } - public ExtensionCatalog resolveExtensionCatalog(String platformKey, String streamId) throws RegistryResolutionException { + public ExtensionCatalog resolveExtensionCatalog(StreamCoords streamCoords) throws RegistryResolutionException { final int registriesTotal = registries.size(); if (registriesTotal == 0) { @@ -398,6 +399,9 @@ public ExtensionCatalog resolveExtensionCatalog(String platformKey, String strea final List catalogs = new ArrayList<>(); final ElementCatalogBuilder catalogBuilder = ElementCatalogBuilder.newInstance(); + String platformKey = streamCoords.getPlatformKey(); + String streamId = streamCoords.getStreamId(); + PlatformStream stream = null; RegistryExtensionResolver registry = null; for (RegistryExtensionResolver qer : registries) {