diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/AddExtensionsCommandHandler.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/AddExtensionsCommandHandler.java index 732161a32671bc..67d6964e4227f6 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/AddExtensionsCommandHandler.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/AddExtensionsCommandHandler.java @@ -1,6 +1,7 @@ package io.quarkus.devtools.commands.handlers; import static io.quarkus.devtools.commands.AddExtensions.EXTENSION_MANAGER; +import static io.quarkus.devtools.messagewriter.MessageIcons.ERROR_ICON; import static io.quarkus.devtools.messagewriter.MessageIcons.NOK_ICON; import io.quarkus.devtools.commands.AddExtensions; @@ -42,17 +43,38 @@ public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws invocation.getQuarkusProject().getExtensionManager()); try { ExtensionInstallPlan extensionInstallPlan = planInstallation(invocation, extensionsQuery); - if (extensionInstallPlan.isNotEmpty()) { + if (extensionInstallPlan.isInstallable()) { final InstallResult result = extensionManager.install(extensionInstallPlan); - result.getInstalled() + result.getInstalledPlatforms() + .forEach(a -> invocation.log() + .info(MessageIcons.OK_ICON + " Platform " + a.getGroupId() + ":" + a.getArtifactId() + + " has been installed")); + result.getInstalledManagedExtensions() .forEach(a -> invocation.log() .info(MessageIcons.OK_ICON + " Extension " + a.getGroupId() + ":" + a.getArtifactId() + " has been installed")); + result.getInstalledIndependentExtensions() + .forEach(a -> invocation.log() + .info(MessageIcons.OK_ICON + " Extension " + a.getGroupId() + ":" + a.getArtifactId() + ":" + + a.getVersion() + + " has been installed")); + result.getAlreadyInstalled() + .forEach(a -> invocation.log() + .info(MessageIcons.NOOP_ICON + " Extension " + a.getGroupId() + ":" + a.getArtifactId() + + " was already installed")); return new QuarkusCommandOutcome(true).setValue(AddExtensions.OUTCOME_UPDATED, result.isSourceUpdated()); + } else if (!extensionInstallPlan.getUnmatchedKeywords().isEmpty()) { + invocation.log() + .info(ERROR_ICON + " Nothing installed because keyword(s) '" + + String.join("', '", extensionInstallPlan.getUnmatchedKeywords()) + + "' were not matched in the catalog."); + } else { + invocation.log() + .info(NOK_ICON + " The provided keyword(s) did not match any extension from the catalog."); } } catch (MultipleExtensionsFoundException m) { StringBuilder sb = new StringBuilder(); - sb.append(NOK_ICON + " Multiple extensions matching '").append(m.getKeyword()).append("'"); + sb.append(ERROR_ICON + " Multiple extensions matching '").append(m.getKeyword()).append("'"); m.getExtensions() .forEach(extension -> sb.append(System.lineSeparator()).append(" * ") .append(extension.managementKey())); @@ -68,12 +90,14 @@ public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws public ExtensionInstallPlan planInstallation(QuarkusCommandInvocation invocation, Collection keywords) throws IOException { + if (keywords.isEmpty()) { + return ExtensionInstallPlan.EMPTY; + } final ExtensionCatalog catalog = invocation.getExtensionsCatalog(); final String quarkusCore = catalog.getQuarkusCoreVersion(); final Collection importedPlatforms = invocation.getQuarkusProject().getExtensionManager() .getInstalledPlatforms(); ExtensionInstallPlan.Builder builder = ExtensionInstallPlan.builder(); - boolean multipleKeywords = keywords.size() > 1; for (String keyword : keywords) { int countColons = StringUtils.countMatches(keyword, ":"); // Check if it's just groupId:artifactId @@ -87,9 +111,9 @@ public ExtensionInstallPlan planInstallation(QuarkusCommandInvocation invocation continue; } List listed = listInternalExtensions(quarkusCore, keyword, catalog.getExtensions()); - if (listed.size() != 1 && multipleKeywords) { - // No extension found for this keyword. Return empty immediately - return ExtensionInstallPlan.EMPTY; + if (listed.isEmpty()) { + // No extension found for this keyword. + builder.addUnmatchedKeyword(keyword); } // If it's a pattern allow multiple results // See https://github.com/quarkusio/quarkus/issues/11086#issuecomment-666360783 diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/BuildFile.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/BuildFile.java index 0e352cef901bcf..019692b474be76 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/BuildFile.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/BuildFile.java @@ -32,33 +32,44 @@ public BuildFile(final Path projectDirPath, ExtensionCatalog catalog) { @Override public final InstallResult install(Collection coords) throws IOException { - this.refreshData(); - final Collection installed = withoutAlreadyInstalled(coords); - installed.forEach(e -> addDependency(e, e.getVersion() == null)); - this.writeToDisk(); - return new InstallResult(installed); + final ExtensionInstallPlan.Builder builder = ExtensionInstallPlan.builder(); + for (ArtifactCoords coord : coords) { + if ("pom".equals(coord.getType())) { + builder.addPlatform(coord); + } else if (coord.getVersion() == null) { + builder.addManagedExtension(coord); + } else { + builder.addIndependentExtension(coord); + } + } + return install(builder.build()); } @Override public InstallResult install(ExtensionInstallPlan plan) throws IOException { - List installed = new ArrayList<>(); - for (ArtifactCoords platform : withoutAlreadyInstalled(plan.getPlatforms())) { + this.refreshData(); + List installedManagedExtensions = new ArrayList<>(); + List installedIndependentExtensions = new ArrayList<>(); + List installedPlatforms = new ArrayList<>(); + final Set alreadyInstalled = alreadyInstalled(plan.toCollection()); + for (ArtifactCoords platform : withoutAlreadyInstalled(alreadyInstalled, plan.getPlatforms())) { if (addDependency(platform, false)) { - installed.add(platform); + installedPlatforms.add(platform); } } - for (ArtifactCoords managedExtension : withoutAlreadyInstalled(plan.getManagedExtensions())) { + for (ArtifactCoords managedExtension : withoutAlreadyInstalled(alreadyInstalled, plan.getManagedExtensions())) { if (addDependency(managedExtension, true)) { - installed.add(managedExtension); + installedManagedExtensions.add(managedExtension); } } - for (ArtifactCoords independentExtension : withoutAlreadyInstalled(plan.getIndependentExtensions())) { + for (ArtifactCoords independentExtension : withoutAlreadyInstalled(alreadyInstalled, plan.getIndependentExtensions())) { if (addDependency(independentExtension, false)) { - installed.add(independentExtension); + installedIndependentExtensions.add(independentExtension); } } writeToDisk(); - return new InstallResult(installed); + return new InstallResult(installedPlatforms, installedManagedExtensions, installedIndependentExtensions, + alreadyInstalled); } @Override @@ -88,8 +99,17 @@ public final UninstallResult uninstall(Collection keys) throws IOEx return new UninstallResult(uninstalled); } - private Collection withoutAlreadyInstalled(Collection extensions) throws IOException { + private Set alreadyInstalled(Collection extensions) throws IOException { final Set existingKeys = getDependenciesKeys(); + return extensions.stream() + .distinct() + .filter(a -> existingKeys.contains(a.getKey())) + .map(ArtifactCoords::getKey) + .collect(Collectors.toSet()); + } + + private Collection withoutAlreadyInstalled(Set existingKeys, + Collection extensions) { return extensions.stream() .distinct() .filter(a -> !existingKeys.contains(a.getKey())) diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/MavenBuildFile.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/MavenBuildFile.java index 6686625bc3d776..6c5e33ac9ac824 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/MavenBuildFile.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/MavenBuildFile.java @@ -81,7 +81,13 @@ protected boolean addDependency(ArtifactCoords coords, boolean managed) { if (model.getDependencies() .stream() .noneMatch(thisDep -> d.getManagementKey().equals(thisDep.getManagementKey()))) { - model.addDependency(d); + final int index = getIndexToAddExtension(); + if (index >= 0) { + model.getDependencies().add(index, d); + } else { + model.getDependencies().add(d); + } + return true; } } @@ -152,6 +158,16 @@ private Model getModel() { }); } + private int getIndexToAddExtension() { + final List dependencies = getModel().getDependencies(); + for (int i = 0; i < dependencies.size(); i++) { + if ("test".equals(dependencies.get(i).getScope())) { + return i; + } + } + return -1; + } + private Model initModel() throws IOException { if (!hasProjectFile(BuildTool.MAVEN.getDependenciesFile())) { return null; diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/MavenProjectBuildFile.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/MavenProjectBuildFile.java index fa28530883175c..66383ce637f218 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/MavenProjectBuildFile.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/buildfile/MavenProjectBuildFile.java @@ -221,7 +221,13 @@ protected boolean addDependency(ArtifactCoords coords, boolean managed) { } else if (model().getDependencies() .stream() .noneMatch(thisDep -> d.getManagementKey().equals(thisDep.getManagementKey()))) { - model().addDependency(d); + final int index = getIndexToAddExtension(); + if (index >= 0) { + model().getDependencies().add(index, d); + } else { + model().getDependencies().add(d); + } + // it could still be a transitive dependency or inherited from the parent if (!getDependencies().contains(coords)) { getDependencies().add(coords); @@ -241,8 +247,8 @@ protected void removeDependency(ArtifactKey key) throws IOException { i.remove(); break; } - model().getDependencies().removeIf(d -> Objects.equals(toKey(d), key)); } + model().getDependencies().removeIf(d -> Objects.equals(toKey(d), key)); } } @@ -293,6 +299,16 @@ protected String getProperty(String propertyName) { protected void refreshData() { } + private int getIndexToAddExtension() { + final List dependencies = model().getDependencies(); + for (int i = 0; i < dependencies.size(); i++) { + if ("test".equals(dependencies.get(i).getScope())) { + return i; + } + } + return -1; + } + private Model model() { return model; } diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/extensions/ExtensionInstallPlan.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/extensions/ExtensionInstallPlan.java index aefc0da8ac59eb..ef8ed352e86e3f 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/extensions/ExtensionInstallPlan.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/extensions/ExtensionInstallPlan.java @@ -1,6 +1,7 @@ package io.quarkus.devtools.project.extensions; import io.quarkus.maven.ArtifactCoords; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashSet; @@ -9,6 +10,7 @@ public class ExtensionInstallPlan { public static final ExtensionInstallPlan EMPTY = new ExtensionInstallPlan( + Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), Collections.emptySet()); @@ -16,13 +18,16 @@ public class ExtensionInstallPlan { private final Set platforms; private final Set managedExtensions; private final Set independentExtensions; + private final Collection unmatchedKeywords; private ExtensionInstallPlan(Set platforms, Set managedExtensions, - Set independentExtensions) { + Set independentExtensions, + Collection unmatchedKeywords) { this.platforms = platforms; this.managedExtensions = managedExtensions; this.independentExtensions = independentExtensions; + this.unmatchedKeywords = unmatchedKeywords; } public boolean isNotEmpty() { @@ -30,6 +35,10 @@ public boolean isNotEmpty() { || !this.independentExtensions.isEmpty(); } + public boolean isInstallable() { + return isNotEmpty() && unmatchedKeywords.isEmpty(); + } + /** * @return a {@link Collection} of all extensions contained in this object */ @@ -63,12 +72,17 @@ public Collection getIndependentExtensions() { return independentExtensions; } + public Collection getUnmatchedKeywords() { + return unmatchedKeywords; + } + @Override public String toString() { return "InstallRequest{" + "platforms=" + platforms + ", managedExtensions=" + managedExtensions + ", independentExtensions=" + independentExtensions + + ", unmatchedKeywords=" + unmatchedKeywords + '}'; } @@ -81,9 +95,10 @@ public static class Builder { private final Set platforms = new LinkedHashSet<>(); private final Set extensionsInPlatforms = new LinkedHashSet<>(); private final Set independentExtensions = new LinkedHashSet<>(); + private final Collection unmatchedKeywords = new ArrayList<>(); public ExtensionInstallPlan build() { - return new ExtensionInstallPlan(platforms, extensionsInPlatforms, independentExtensions); + return new ExtensionInstallPlan(platforms, extensionsInPlatforms, independentExtensions, unmatchedKeywords); } public Builder addIndependentExtension(ArtifactCoords artifactCoords) { @@ -101,6 +116,11 @@ public Builder addPlatform(ArtifactCoords artifactCoords) { return this; } + public Builder addUnmatchedKeyword(String unmatchedKeyword) { + this.unmatchedKeywords.add(unmatchedKeyword); + return this; + } + public boolean hasExtensionInPlatform() { return !this.extensionsInPlatforms.isEmpty(); } diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/extensions/ExtensionManager.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/extensions/ExtensionManager.java index bd717483fee473..08d9a5fbf918ff 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/extensions/ExtensionManager.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/extensions/ExtensionManager.java @@ -76,7 +76,7 @@ default boolean isInstalled(ArtifactKey key) throws IOException { /** * This is going to uninstall/remove all the specified extensions from the project build file(s). * - * This is ignoring the {@link Extension} version + * This is ignoring the version * * @param keys the set of {@link ArtifactKey} for the extensions to uninstall * @return the {@link InstallResult} @@ -85,18 +85,39 @@ default boolean isInstalled(ArtifactKey key) throws IOException { UninstallResult uninstall(Collection keys) throws IOException; class InstallResult { - private final Collection installed; + private final Collection installedPlatforms; + private final Collection installedManagedExtensions; + private final Collection installedIndependentExtensions; + private final Collection alreadyInstalled; + + public InstallResult(Collection installedPlatforms, + Collection installedManagedExtensions, + Collection installedIndependentExtensions, Collection alreadyInstalled) { + this.installedPlatforms = installedPlatforms; + this.installedManagedExtensions = installedManagedExtensions; + this.installedIndependentExtensions = installedIndependentExtensions; + this.alreadyInstalled = alreadyInstalled; + } + + public Collection getInstalledManagedExtensions() { + return installedManagedExtensions; + } + + public Collection getInstalledIndependentExtensions() { + return installedIndependentExtensions; + } - public InstallResult(Collection installed) { - this.installed = installed; + public Collection getInstalledPlatforms() { + return installedPlatforms; } - public Collection getInstalled() { - return installed; + public Collection getAlreadyInstalled() { + return alreadyInstalled; } public boolean isSourceUpdated() { - return installed.size() > 0; + return !installedPlatforms.isEmpty() || !installedManagedExtensions.isEmpty() + || !installedIndependentExtensions.isEmpty(); } }