diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/model/AppArtifactKey.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/model/AppArtifactKey.java index ad82ec0400e62..a9c1a96ef67b8 100644 --- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/model/AppArtifactKey.java +++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/model/AppArtifactKey.java @@ -73,12 +73,12 @@ protected static String[] split(String str, String[] parts, int fromIndex) { protected AppArtifactKey(String[] parts) { this.groupId = parts[0]; this.artifactId = parts[1]; - if (parts.length == 2) { + if (parts.length == 2 || parts[2] == null) { this.classifier = ""; } else { this.classifier = parts[2]; } - if (parts.length <= 3) { + if (parts.length <= 3 || parts[3] == null) { this.type = "jar"; } else { this.type = parts[3]; diff --git a/independent-projects/tools/common/src/main/java/io/quarkus/devtools/commands/handlers/AddExtensionsCommandHandler.java b/independent-projects/tools/common/src/main/java/io/quarkus/devtools/commands/handlers/AddExtensionsCommandHandler.java index 78d8571238b71..3e3452a59499e 100644 --- a/independent-projects/tools/common/src/main/java/io/quarkus/devtools/commands/handlers/AddExtensionsCommandHandler.java +++ b/independent-projects/tools/common/src/main/java/io/quarkus/devtools/commands/handlers/AddExtensionsCommandHandler.java @@ -1,14 +1,15 @@ package io.quarkus.devtools.commands.handlers; import static io.quarkus.devtools.commands.AddExtensions.EXTENSIONS_MANAGER; -import static io.quarkus.devtools.commands.handlers.QuarkusCommandHandlers.computeExtensionsFromQuery; +import static io.quarkus.devtools.commands.handlers.QuarkusCommandHandlers.computeCoordsFromQuery; -import io.quarkus.dependencies.Extension; +import io.quarkus.bootstrap.model.AppArtifactCoords; import io.quarkus.devtools.commands.AddExtensions; import io.quarkus.devtools.commands.data.QuarkusCommandException; import io.quarkus.devtools.commands.data.QuarkusCommandInvocation; import io.quarkus.devtools.commands.data.QuarkusCommandOutcome; import io.quarkus.devtools.project.extensions.ExtensionsManager; +import io.quarkus.devtools.project.extensions.ExtensionsManager.InstallResult; import java.io.IOException; import java.util.Collections; import java.util.List; @@ -27,13 +28,13 @@ public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws return QuarkusCommandOutcome.success().setValue(AddExtensions.OUTCOME_UPDATED, false); } - final List extensionsToAdd = computeExtensionsFromQuery(invocation, extensionsQuery); + final List extensionsToAdd = computeCoordsFromQuery(invocation, extensionsQuery); final ExtensionsManager extensionsManager = invocation.getValue(EXTENSIONS_MANAGER, invocation.getQuarkusProject().getExtensionsManager()); try { if (extensionsToAdd != null) { - final int added = extensionsManager.install(extensionsToAdd); - return new QuarkusCommandOutcome(true).setValue(AddExtensions.OUTCOME_UPDATED, added > 0); + final InstallResult result = extensionsManager.install(extensionsToAdd); + return new QuarkusCommandOutcome(true).setValue(AddExtensions.OUTCOME_UPDATED, result.isSourceUpdated()); } } catch (IOException e) { diff --git a/independent-projects/tools/common/src/main/java/io/quarkus/devtools/commands/handlers/CreateProjectCommandHandler.java b/independent-projects/tools/common/src/main/java/io/quarkus/devtools/commands/handlers/CreateProjectCommandHandler.java index 442195e11f848..ccf13b687bc15 100644 --- a/independent-projects/tools/common/src/main/java/io/quarkus/devtools/commands/handlers/CreateProjectCommandHandler.java +++ b/independent-projects/tools/common/src/main/java/io/quarkus/devtools/commands/handlers/CreateProjectCommandHandler.java @@ -1,9 +1,9 @@ package io.quarkus.devtools.commands.handlers; -import static io.quarkus.devtools.commands.handlers.QuarkusCommandHandlers.computeExtensionsFromQuery; +import static io.quarkus.devtools.commands.handlers.QuarkusCommandHandlers.computeCoordsFromQuery; import static io.quarkus.devtools.project.codegen.ProjectGenerator.*; -import io.quarkus.dependencies.Extension; +import io.quarkus.bootstrap.model.AppArtifactCoords; import io.quarkus.devtools.commands.data.QuarkusCommandException; import io.quarkus.devtools.commands.data.QuarkusCommandInvocation; import io.quarkus.devtools.commands.data.QuarkusCommandOutcome; @@ -54,7 +54,7 @@ public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws invocation.setValue(CLASS_NAME, className); } - final List extensionsToAdd = computeExtensionsFromQuery(invocation, extensionsQuery); + final List extensionsToAdd = computeCoordsFromQuery(invocation, extensionsQuery); // extensionsToAdd is null when an error occurred while matching extensions if (extensionsToAdd != null) { diff --git a/independent-projects/tools/common/src/main/java/io/quarkus/devtools/commands/handlers/ListExtensionsCommandHandler.java b/independent-projects/tools/common/src/main/java/io/quarkus/devtools/commands/handlers/ListExtensionsCommandHandler.java index 6b6b2c1b4707a..91b517cb55ee6 100644 --- a/independent-projects/tools/common/src/main/java/io/quarkus/devtools/commands/handlers/ListExtensionsCommandHandler.java +++ b/independent-projects/tools/common/src/main/java/io/quarkus/devtools/commands/handlers/ListExtensionsCommandHandler.java @@ -1,7 +1,10 @@ package io.quarkus.devtools.commands.handlers; +import static io.quarkus.devtools.project.extensions.Extensions.toKey; import static java.util.stream.Collectors.toMap; +import io.quarkus.bootstrap.model.AppArtifactCoords; +import io.quarkus.bootstrap.model.AppArtifactKey; import io.quarkus.dependencies.Extension; import io.quarkus.devtools.commands.ListExtensions; import io.quarkus.devtools.commands.data.QuarkusCommandException; @@ -39,10 +42,10 @@ public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws final ExtensionsManager extensionsManager = invocation.getValue(ListExtensions.EXTENSIONS_MANAGER, invocation.getQuarkusProject().getExtensionsManager()); - Map installedByGA; + Map installedByKey; try { - installedByGA = extensionsManager.getInstalled().stream() - .collect(toMap(Extension::managementKey, Function.identity())); + installedByKey = extensionsManager.getInstalled().stream() + .collect(toMap(AppArtifactCoords::getKey, Function.identity())); } catch (IOException e) { throw new QuarkusCommandException("Failed to determine the list of installed extensions", e); } @@ -77,7 +80,7 @@ public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws } platformExtensions.forEach(platformExtension -> display(invocation.log(), platformExtension, - installedByGA.get(platformExtension.managementKey()), all, currentFormatter)); + installedByKey.get(toKey(platformExtension)), all, currentFormatter)); final BuildTool buildTool = invocation.getQuarkusProject().getBuildTool(); if ("concise".equalsIgnoreCase(format)) { if (BuildTool.GRADLE.equals(buildTool)) { @@ -121,7 +124,8 @@ private void nameFormatter(MessageWriter writer, String[] cols) { writer.info(String.format(NAME_FORMAT, cols[2])); } - private void display(MessageWriter messageWriter, final Extension platformExtension, final Extension installed, boolean all, + private void display(MessageWriter messageWriter, final Extension platformExtension, final AppArtifactCoords installed, + boolean all, BiConsumer formatter) { if (!all && installed != null) { return; @@ -130,13 +134,16 @@ private void display(MessageWriter messageWriter, final Extension platformExtens String label = ""; String version = ""; - final String installedVersion = installed != null ? installed.getVersion() : null; - if (installedVersion != null) { - if (installedVersion.equalsIgnoreCase(platformExtension.getVersion())) { - label = "current"; + if (installed != null) { + final String installedVersion = installed.getVersion(); + if (installedVersion == null) { + label = "managed"; + version = String.format("%s", platformExtension.getVersion()); + } else if (installedVersion.equalsIgnoreCase(platformExtension.getVersion())) { + label = "overridden"; version = String.format("%s", installedVersion); } else { - label = "update"; + label = "overridden"; version = String.format("%s <> %s", installedVersion, platformExtension.getVersion()); } } diff --git a/independent-projects/tools/common/src/main/java/io/quarkus/devtools/commands/handlers/QuarkusCommandHandlers.java b/independent-projects/tools/common/src/main/java/io/quarkus/devtools/commands/handlers/QuarkusCommandHandlers.java index 8ea62d9ea5b62..4effcd2d654ce 100644 --- a/independent-projects/tools/common/src/main/java/io/quarkus/devtools/commands/handlers/QuarkusCommandHandlers.java +++ b/independent-projects/tools/common/src/main/java/io/quarkus/devtools/commands/handlers/QuarkusCommandHandlers.java @@ -4,6 +4,7 @@ import static io.quarkus.platform.tools.ConsoleMessageFormat.nok; import com.google.common.collect.ImmutableList; +import io.quarkus.bootstrap.model.AppArtifactCoords; import io.quarkus.dependencies.Extension; import io.quarkus.devtools.commands.data.QuarkusCommandInvocation; import io.quarkus.devtools.commands.data.SelectionResult; @@ -21,15 +22,20 @@ final class QuarkusCommandHandlers { private QuarkusCommandHandlers() { } - static List computeExtensionsFromQuery(final QuarkusCommandInvocation invocation, + static List computeCoordsFromQuery(final QuarkusCommandInvocation invocation, final Set extensionsQuery) { - final ImmutableList.Builder builder = ImmutableList. builder(); + final ImmutableList.Builder builder = ImmutableList.builder(); for (String query : extensionsQuery) { if (query.contains(":")) { - builder.add(Extensions.parse(query)); + builder.add(AppArtifactCoords.fromString(query)); } else { - SelectionResult result = select(invocation.getPlatformDescriptor().getExtensions(), query, false); - if (!result.matches()) { + SelectionResult result = select(query, invocation.getPlatformDescriptor().getExtensions(), false); + if (result.matches()) { + final Set withStrippedVersion = result.getExtensions().stream().map(Extensions::toCoords) + .map(Extensions::stripVersion).collect(Collectors.toSet()); + // We strip the version because those extensions are managed + builder.addAll(withStrippedVersion); + } else { StringBuilder sb = new StringBuilder(); // We have 3 cases, we can still have a single candidate, but the match is on label // or we have several candidates, or none @@ -48,11 +54,6 @@ static List computeExtensionsFromQuery(final QuarkusCommandInvocation invocation.log().info(sb.toString()); return null; } - } else { // Matches. - for (Extension extension : result) { - // Don't set success to false even if the dependency is not added; as it's should be idempotent. - builder.add(extension); - } } } } @@ -62,13 +63,13 @@ static List computeExtensionsFromQuery(final QuarkusCommandInvocation /** * Selection algorithm. * - * @param allPlatformExtensions the list of all platform extensions * @param query the query + * @param allPlatformExtensions the list of all platform extensions * @param labelLookup whether or not the query must be tested against the labels of the extensions. Should * be {@code false} by default. * @return the list of matching candidates and whether or not a match has been found. */ - static SelectionResult select(final List allPlatformExtensions, final String query, final boolean labelLookup) { + static SelectionResult select(final String query, final List allPlatformExtensions, final boolean labelLookup) { String q = query.trim().toLowerCase(); // Try exact matches diff --git a/independent-projects/tools/common/src/main/java/io/quarkus/devtools/commands/handlers/RemoveExtensionsCommandHandler.java b/independent-projects/tools/common/src/main/java/io/quarkus/devtools/commands/handlers/RemoveExtensionsCommandHandler.java index 1b283af880bd5..fb8a889cb1fed 100644 --- a/independent-projects/tools/common/src/main/java/io/quarkus/devtools/commands/handlers/RemoveExtensionsCommandHandler.java +++ b/independent-projects/tools/common/src/main/java/io/quarkus/devtools/commands/handlers/RemoveExtensionsCommandHandler.java @@ -1,19 +1,21 @@ package io.quarkus.devtools.commands.handlers; import static io.quarkus.devtools.commands.RemoveExtensions.EXTENSIONS_MANAGER; -import static io.quarkus.devtools.commands.handlers.QuarkusCommandHandlers.computeExtensionsFromQuery; +import static io.quarkus.devtools.commands.handlers.QuarkusCommandHandlers.computeCoordsFromQuery; -import com.google.common.collect.ImmutableSet; -import io.quarkus.dependencies.Extension; +import io.quarkus.bootstrap.model.AppArtifactCoords; +import io.quarkus.bootstrap.model.AppArtifactKey; import io.quarkus.devtools.commands.RemoveExtensions; import io.quarkus.devtools.commands.data.QuarkusCommandException; import io.quarkus.devtools.commands.data.QuarkusCommandInvocation; import io.quarkus.devtools.commands.data.QuarkusCommandOutcome; import io.quarkus.devtools.project.extensions.ExtensionsManager; +import io.quarkus.devtools.project.extensions.ExtensionsManager.UninstallResult; import java.io.IOException; import java.util.Collections; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; /** * This class is thread-safe. It extracts extensions to be removed from the project from an instance of @@ -28,13 +30,15 @@ public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws return QuarkusCommandOutcome.success().setValue(RemoveExtensions.OUTCOME_UPDATED, false); } - final List extensionsToRemove = computeExtensionsFromQuery(invocation, extensionsQuery); + final List extensionsToRemove = computeCoordsFromQuery(invocation, extensionsQuery); final ExtensionsManager extensionsManager = invocation.getValue(EXTENSIONS_MANAGER, invocation.getQuarkusProject().getExtensionsManager()); try { if (extensionsToRemove != null) { - final int removed = extensionsManager.uninstall(ImmutableSet.copyOf(extensionsToRemove)); - return new QuarkusCommandOutcome(true).setValue(RemoveExtensions.OUTCOME_UPDATED, removed > 0); + final Set keys = extensionsToRemove.stream().map(AppArtifactCoords::getKey) + .collect(Collectors.toSet()); + final UninstallResult result = extensionsManager.uninstall(keys); + return new QuarkusCommandOutcome(true).setValue(RemoveExtensions.OUTCOME_UPDATED, result.isSourceUpdated()); } } catch (IOException e) { throw new QuarkusCommandException("Failed to add extensions", e); diff --git a/independent-projects/tools/common/src/main/java/io/quarkus/devtools/project/buildfile/AbstractGradleBuildFile.java b/independent-projects/tools/common/src/main/java/io/quarkus/devtools/project/buildfile/AbstractGradleBuildFile.java index bc5b04f511bdd..7633ed2605662 100644 --- a/independent-projects/tools/common/src/main/java/io/quarkus/devtools/project/buildfile/AbstractGradleBuildFile.java +++ b/independent-projects/tools/common/src/main/java/io/quarkus/devtools/project/buildfile/AbstractGradleBuildFile.java @@ -1,5 +1,7 @@ package io.quarkus.devtools.project.buildfile; +import io.quarkus.bootstrap.model.AppArtifactCoords; +import io.quarkus.bootstrap.model.AppArtifactKey; import io.quarkus.devtools.project.BuildTool; import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor; import java.io.ByteArrayInputStream; @@ -14,7 +16,6 @@ import java.util.Scanner; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; -import org.apache.maven.model.Dependency; // We keep it here to take advantage of the abstract tests public abstract class AbstractGradleBuildFile extends BuildFile { @@ -58,35 +59,37 @@ public void writeToDisk() throws IOException { } @Override - protected void addDependencyInBuildFile(Dependency dependency) throws IOException { + protected void addDependencyInBuildFile(AppArtifactCoords coords) throws IOException { + addDependencyInModel(getModel(), coords); + } + + static void addDependencyInModel(Model model, AppArtifactCoords coords) throws IOException { StringBuilder newBuildContent = new StringBuilder(); - readLineByLine(getModel().getBuildContent(), currentLine -> { + readLineByLine(model.getBuildContent(), currentLine -> { newBuildContent.append(currentLine).append(System.lineSeparator()); if (currentLine.startsWith("dependencies {")) { newBuildContent.append(" implementation '") - .append(dependency.getGroupId()) + .append(coords.getGroupId()) .append(":") - .append(dependency.getArtifactId()); - if (dependency.getVersion() != null && !dependency.getVersion().isEmpty()) { + .append(coords.getArtifactId()); + if (coords.getVersion() != null && !coords.getVersion().isEmpty()) { newBuildContent.append(":") - .append(dependency.getVersion()); + .append(coords.getVersion()); } newBuildContent.append("'") .append(System.lineSeparator()); } }); - getModel().setBuildContent(newBuildContent.toString()); + model.setBuildContent(newBuildContent.toString()); } @Override - protected void removeDependencyFromBuildFile(Dependency dependency) throws IOException { - String depString = new StringBuilder("'").append(dependency.getGroupId()).append(":") - .append(dependency.getArtifactId()).toString(); + protected void removeDependencyFromBuildFile(AppArtifactKey key) throws IOException { StringBuilder newBuildContent = new StringBuilder(); Scanner scanner = new Scanner(getModel().getBuildContent()); while (scanner.hasNextLine()) { String line = scanner.nextLine(); - if (!line.contains(depString)) { + if (!line.contains(key.getGroupId() + ":" + key.getArtifactId())) { newBuildContent.append(line).append(System.lineSeparator()); } } @@ -115,16 +118,6 @@ public BuildTool getBuildTool() { return BuildTool.GRADLE; } - private void readLineByLine(String content, Consumer lineConsumer) { - try (Scanner scanner = new Scanner(new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)), - StandardCharsets.UTF_8.name())) { - while (scanner.hasNextLine()) { - String currentLine = scanner.nextLine(); - lineConsumer.accept(currentLine); - } - } - } - private Model getModel() throws IOException { return modelReference.updateAndGet(model -> { if (model == null) { @@ -192,7 +185,17 @@ protected String getBuildContent() throws IOException { return getModel().getBuildContent(); } - private static class Model { + private static void readLineByLine(String content, Consumer lineConsumer) { + try (Scanner scanner = new Scanner(new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)), + StandardCharsets.UTF_8.name())) { + while (scanner.hasNextLine()) { + String currentLine = scanner.nextLine(); + lineConsumer.accept(currentLine); + } + } + } + + static class Model { private String settingsContent; private String buildContent; private Properties propertiesContent; diff --git a/independent-projects/tools/common/src/main/java/io/quarkus/devtools/project/buildfile/BuildFile.java b/independent-projects/tools/common/src/main/java/io/quarkus/devtools/project/buildfile/BuildFile.java index b26e7f01c832f..8136477eb4ba6 100644 --- a/independent-projects/tools/common/src/main/java/io/quarkus/devtools/project/buildfile/BuildFile.java +++ b/independent-projects/tools/common/src/main/java/io/quarkus/devtools/project/buildfile/BuildFile.java @@ -1,9 +1,13 @@ package io.quarkus.devtools.project.buildfile; import static com.google.common.base.Preconditions.checkNotNull; -import static io.quarkus.devtools.project.extensions.Extensions.key; +import static io.quarkus.devtools.project.extensions.Extensions.findInRegistry; +import static io.quarkus.devtools.project.extensions.Extensions.toCoords; +import static io.quarkus.devtools.project.extensions.Extensions.toKey; import static java.util.stream.Collectors.toList; +import io.quarkus.bootstrap.model.AppArtifactCoords; +import io.quarkus.bootstrap.model.AppArtifactKey; import io.quarkus.dependencies.Extension; import io.quarkus.devtools.project.extensions.Extensions; import io.quarkus.devtools.project.extensions.ExtensionsManager; @@ -13,8 +17,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.List; -import java.util.Objects; -import java.util.Optional; import java.util.Set; import java.util.concurrent.atomic.LongAdder; import java.util.stream.Collectors; @@ -36,60 +38,58 @@ public final boolean hasQuarkusPlatformBom() throws IOException { } @Override - public final int install(List extensions) throws IOException { + public final InstallResult install(List coords) throws IOException { if (!hasQuarkusPlatformBom()) { throw new IllegalStateException("The Quarkus BOM is required to add a Quarkus extension"); } this.refreshData(); - final Set existingKeys = getDependenciesManagementKeys(); + final Set existingKeys = getDependenciesKeys(); final LongAdder counter = new LongAdder(); - extensions.stream() - .filter(a -> !existingKeys.contains(a.managementKey())) + coords.stream() + .filter(a -> !existingKeys.contains(a.getKey())) .forEach(e -> { try { - final boolean isInPlatformBom = isDefinedInRegistry(platformDescriptor.getExtensions(), - e.managementKey()); - // We hardcode the version when it is already defined in the platform bom - addDependencyInBuildFile(e.toDependency(isInPlatformBom)); + addDependencyInBuildFile(e); counter.increment(); } catch (IOException ex) { throw new UncheckedIOException(ex); } }); this.writeToDisk(); - return counter.intValue(); + return new InstallResult(counter.intValue()); } @Override - public final List getInstalled() throws IOException { + public final List getInstalled() throws IOException { this.refreshData(); - return this.getDependencies().stream().filter(d -> this.isRegisteredQuarkusExtension(key(d))) - .map(d -> new Extension(d.getGroupId(), d.getArtifactId(), extractVersion(d))) + return this.getDependencies().stream() + .filter(d -> this.isQuarkusExtension(toKey(d))) + .map(d -> toCoords(d, extractVersion(d))) .collect(toList()); } @Override - public final int uninstall(Set extensions) throws IOException { + public final UninstallResult uninstall(Set keys) throws IOException { this.refreshData(); - final Set existingKeys = getDependenciesManagementKeys(); + final Set existingKeys = getDependenciesKeys(); final LongAdder counter = new LongAdder(); - extensions.stream() - .filter(a -> existingKeys.contains(a.managementKey())) - .forEach(e -> { + keys.stream() + .filter(existingKeys::contains) + .forEach(k -> { try { - removeDependencyFromBuildFile(e.toDependency(true)); + removeDependencyFromBuildFile(k); counter.increment(); } catch (IOException ex) { throw new UncheckedIOException(ex); } }); this.writeToDisk(); - return counter.intValue(); + return new UninstallResult(counter.intValue()); } - protected abstract void addDependencyInBuildFile(Dependency dependency) throws IOException; + protected abstract void addDependencyInBuildFile(AppArtifactCoords coords) throws IOException; - protected abstract void removeDependencyFromBuildFile(Dependency dependency) throws IOException; + protected abstract void removeDependencyFromBuildFile(AppArtifactKey key) throws IOException; protected abstract List getDependencies() throws IOException; @@ -119,13 +119,13 @@ protected void writeToProjectFile(final String fileName, final byte[] content) t Files.write(projectFolderPath.resolve(fileName), content); } - private boolean isRegisteredQuarkusExtension(final String managementKey) { + private boolean isQuarkusExtension(final AppArtifactKey key) { // This will not always be true as the platform descriptor does not contain the list of all available extensions - return isDefinedInRegistry(platformDescriptor.getExtensions(), managementKey); + return isDefinedInRegistry(platformDescriptor.getExtensions(), key); } - private Set getDependenciesManagementKeys() throws IOException { - return getDependencies().stream().map(Extensions::key).collect(Collectors.toSet()); + private Set getDependenciesKeys() throws IOException { + return getDependencies().stream().map(Extensions::toKey).collect(Collectors.toSet()); } private String extractVersion(final Dependency d) { @@ -144,20 +144,15 @@ private String extractVersion(final Dependency d) { if (version != null) { return version; } - return mapToExtensionFromRegistry(platformDescriptor.getExtensions(), key(d)) - .map(Extension::getVersion) - .orElse(null); + return null; } private String propertyName(final String variable) { return variable.substring(2, variable.length() - 1); } - public static boolean isDefinedInRegistry(List registry, final String managementKey) { - return mapToExtensionFromRegistry(registry, managementKey).isPresent(); + public static boolean isDefinedInRegistry(List registry, final AppArtifactKey key) { + return findInRegistry(registry, key).isPresent(); } - public static Optional mapToExtensionFromRegistry(List registry, final String managementKey) { - return registry.stream().filter(k -> Objects.equals(k.managementKey(), managementKey)).findFirst(); - } } diff --git a/independent-projects/tools/common/src/main/java/io/quarkus/devtools/project/buildfile/GenericGradleBuildFile.java b/independent-projects/tools/common/src/main/java/io/quarkus/devtools/project/buildfile/GenericGradleBuildFile.java index 6e9bae027ef0c..da7bb74d1709c 100644 --- a/independent-projects/tools/common/src/main/java/io/quarkus/devtools/project/buildfile/GenericGradleBuildFile.java +++ b/independent-projects/tools/common/src/main/java/io/quarkus/devtools/project/buildfile/GenericGradleBuildFile.java @@ -1,6 +1,7 @@ package io.quarkus.devtools.project.buildfile; -import io.quarkus.dependencies.Extension; +import io.quarkus.bootstrap.model.AppArtifactCoords; +import io.quarkus.bootstrap.model.AppArtifactKey; import io.quarkus.devtools.project.BuildTool; import io.quarkus.devtools.project.extensions.ExtensionsManager; import java.io.IOException; @@ -18,7 +19,7 @@ public BuildTool getBuildTool() { } @Override - public List getInstalled() throws IOException { + public List getInstalled() throws IOException { throw new IllegalStateException("This feature is not yet implemented outside of the Gradle Plugin."); } @@ -28,12 +29,13 @@ public boolean hasQuarkusPlatformBom() throws IOException { } @Override - public int install(List extensions) throws IOException { + public InstallResult install(List coords) throws IOException { throw new IllegalStateException("This feature is not yet implemented outside of the Gradle Plugin."); } @Override - public int uninstall(Set extensions) throws IOException { + public UninstallResult uninstall(Set keys) throws IOException { throw new IllegalStateException("This feature is not yet implemented outside of the Gradle Plugin."); } + } diff --git a/independent-projects/tools/common/src/main/java/io/quarkus/devtools/project/buildfile/GradleBuildFilesCreator.java b/independent-projects/tools/common/src/main/java/io/quarkus/devtools/project/buildfile/GradleBuildFilesCreator.java index aaf18e4b402ab..735adff8eaeea 100644 --- a/independent-projects/tools/common/src/main/java/io/quarkus/devtools/project/buildfile/GradleBuildFilesCreator.java +++ b/independent-projects/tools/common/src/main/java/io/quarkus/devtools/project/buildfile/GradleBuildFilesCreator.java @@ -1,9 +1,10 @@ package io.quarkus.devtools.project.buildfile; -import static io.quarkus.devtools.project.buildfile.BuildFile.isDefinedInRegistry; +import static io.quarkus.devtools.project.buildfile.AbstractGradleBuildFile.addDependencyInModel; -import io.quarkus.dependencies.Extension; +import io.quarkus.bootstrap.model.AppArtifactCoords; import io.quarkus.devtools.project.QuarkusProject; +import io.quarkus.devtools.project.buildfile.AbstractGradleBuildFile.Model; import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor; import io.quarkus.platform.tools.ToolsUtils; import java.io.ByteArrayInputStream; @@ -18,7 +19,6 @@ import java.util.Scanner; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; -import org.apache.maven.model.Dependency; public final class GradleBuildFilesCreator { @@ -34,17 +34,14 @@ public GradleBuildFilesCreator(QuarkusProject quarkusProject) { } public void create(String groupId, String artifactId, String version, - Properties properties, List extensions) throws IOException { + Properties properties, List extensions) throws IOException { createSettingsContent(artifactId); createBuildContent(groupId, version); createProperties(); extensions.stream() .forEach(e -> { try { - final boolean isInPlatformBom = isDefinedInRegistry( - quarkusProject.getPlatformDescriptor().getExtensions(), e.managementKey()); - // We hardcode the version when it is already defined in the platform bom - addDependencyInBuildFile(e.toDependency(isInPlatformBom)); + addDependencyInBuildFile(e); } catch (IOException ex) { throw new UncheckedIOException(ex); } @@ -61,24 +58,8 @@ private void writeToDisk() throws IOException { } } - private void addDependencyInBuildFile(Dependency dependency) throws IOException { - StringBuilder newBuildContent = new StringBuilder(); - readLineByLine(getModel().getBuildContent(), currentLine -> { - newBuildContent.append(currentLine).append(System.lineSeparator()); - if (currentLine.startsWith("dependencies {")) { - newBuildContent.append(" implementation '") - .append(dependency.getGroupId()) - .append(":") - .append(dependency.getArtifactId()); - if (dependency.getVersion() != null && !dependency.getVersion().isEmpty()) { - newBuildContent.append(":") - .append(dependency.getVersion()); - } - newBuildContent.append("'") - .append(System.lineSeparator()); - } - }); - getModel().setBuildContent(newBuildContent.toString()); + private void addDependencyInBuildFile(AppArtifactCoords coords) throws IOException { + addDependencyInModel(getModel(), coords); } protected boolean containsBOM(String groupId, String artifactId) throws IOException { @@ -130,7 +111,7 @@ private Model readModel() throws IOException { final byte[] properties = readProjectFile(GRADLE_PROPERTIES_PATH); propertiesContent.load(new ByteArrayInputStream(properties)); } - return new Model(settingsContent, buildContent, propertiesContent); + return new Model(settingsContent, buildContent, propertiesContent, null, null); } protected boolean hasProjectFile(final String fileName) throws IOException { @@ -231,36 +212,4 @@ private void createProperties() throws IOException { } } - private static class Model { - private String settingsContent; - private String buildContent; - private Properties propertiesContent; - - public Model(String settingsContent, String buildContent, Properties propertiesContent) { - this.settingsContent = settingsContent; - this.buildContent = buildContent; - this.propertiesContent = propertiesContent; - } - - public String getSettingsContent() { - return settingsContent; - } - - public String getBuildContent() { - return buildContent; - } - - public Properties getPropertiesContent() { - return propertiesContent; - } - - public void setSettingsContent(String settingsContent) { - this.settingsContent = settingsContent; - } - - public void setBuildContent(String buildContent) { - this.buildContent = buildContent; - } - - } } diff --git a/independent-projects/tools/common/src/main/java/io/quarkus/devtools/project/buildfile/MavenBuildFile.java b/independent-projects/tools/common/src/main/java/io/quarkus/devtools/project/buildfile/MavenBuildFile.java index 9a79a6017094e..8295257e8e30c 100644 --- a/independent-projects/tools/common/src/main/java/io/quarkus/devtools/project/buildfile/MavenBuildFile.java +++ b/independent-projects/tools/common/src/main/java/io/quarkus/devtools/project/buildfile/MavenBuildFile.java @@ -1,5 +1,9 @@ package io.quarkus.devtools.project.buildfile; +import static io.quarkus.devtools.project.extensions.Extensions.toKey; + +import io.quarkus.bootstrap.model.AppArtifactCoords; +import io.quarkus.bootstrap.model.AppArtifactKey; import io.quarkus.devtools.project.BuildTool; import io.quarkus.maven.utilities.MojoUtils; import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor; @@ -10,6 +14,7 @@ import java.nio.file.Path; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.concurrent.atomic.AtomicReference; import org.apache.maven.model.Dependency; import org.apache.maven.model.Model; @@ -34,18 +39,23 @@ public void writeToDisk() throws IOException { } @Override - protected void addDependencyInBuildFile(Dependency dependency) throws IOException { + protected void addDependencyInBuildFile(AppArtifactCoords coords) throws IOException { if (getModel() != null) { - getModel().addDependency(dependency); + final Dependency d = new Dependency(); + d.setGroupId(coords.getGroupId()); + d.setArtifactId(coords.getArtifactId()); + d.setVersion(coords.getVersion()); + d.setClassifier(coords.getClassifier()); + d.setType(coords.getType()); + getModel().addDependency(d); } } @Override - protected void removeDependencyFromBuildFile(Dependency dependency) throws IOException { + protected void removeDependencyFromBuildFile(AppArtifactKey key) throws IOException { if (getModel() != null) { getModel().getDependencies() - .removeIf(d -> d.getGroupId().equals(dependency.getGroupId()) - && d.getArtifactId().equals(dependency.getArtifactId())); + .removeIf(d -> Objects.equals(toKey(d), key)); } } diff --git a/independent-projects/tools/common/src/main/java/io/quarkus/devtools/project/extensions/Extensions.java b/independent-projects/tools/common/src/main/java/io/quarkus/devtools/project/extensions/Extensions.java index 34af541b140b2..09968d311b441 100644 --- a/independent-projects/tools/common/src/main/java/io/quarkus/devtools/project/extensions/Extensions.java +++ b/independent-projects/tools/common/src/main/java/io/quarkus/devtools/project/extensions/Extensions.java @@ -1,40 +1,49 @@ package io.quarkus.devtools.project.extensions; +import io.quarkus.bootstrap.model.AppArtifactCoords; +import io.quarkus.bootstrap.model.AppArtifactKey; import io.quarkus.dependencies.Extension; +import java.util.List; import java.util.Objects; +import java.util.Optional; import org.apache.maven.model.Dependency; public final class Extensions { private Extensions() { } - public static String key(final Extension extension) { - return extension.managementKey(); + public static AppArtifactKey toKey(final Extension extension) { + return toKey(extension.toDependency(false)); } - public static String key(final Dependency dependency) { - return dependency.getGroupId() + ":" + dependency.getArtifactId(); + public static AppArtifactKey toKey(final Dependency dependency) { + return new AppArtifactKey(dependency.getGroupId(), dependency.getArtifactId(), dependency.getClassifier(), + dependency.getType()); } - public static boolean equalsIgnoringVersions(final Extension a, final Extension b) { - return Objects.equals(a.getGroupId(), b.getGroupId()) && Objects.equals(a.getArtifactId(), b.getArtifactId()); + public static Optional findInRegistry(List registry, final AppArtifactKey key) { + return registry.stream().filter(e -> Objects.equals(toCoords(e).getKey(), key)).findFirst(); } - public static Extension parse(String gav) { - final Extension res = new Extension(); - String[] segments = gav.split(":"); - if (segments.length >= 2) { - res.setGroupId(segments[0].toLowerCase()); - res.setArtifactId(segments[1].toLowerCase()); - if (segments.length >= 3 && !segments[2].isEmpty()) { - res.setVersion(segments[2]); - } - if (segments.length >= 4) { - res.setClassifier(segments[3].toLowerCase()); - } - return res; - } else { - throw new IllegalArgumentException("Invalid GAV '" + gav + "'"); - } + public static AppArtifactCoords toCoords(final Extension e) { + return toCoords(e.toDependency(false)); } + + public static AppArtifactCoords toCoords(final Dependency d, final String overrideVersion) { + return overrideVersion(toCoords(d), overrideVersion); + } + + public static AppArtifactCoords stripVersion(final AppArtifactCoords coords) { + return overrideVersion(coords, null); + } + + public static AppArtifactCoords overrideVersion(final AppArtifactCoords coords, final String overrideVersion) { + return new AppArtifactCoords(coords.getGroupId(), coords.getArtifactId(), coords.getClassifier(), coords.getType(), + overrideVersion); + } + + public static AppArtifactCoords toCoords(final Dependency d) { + return new AppArtifactCoords(d.getGroupId(), d.getArtifactId(), d.getClassifier(), d.getType(), d.getVersion()); + } + } diff --git a/independent-projects/tools/common/src/main/java/io/quarkus/devtools/project/extensions/ExtensionsManager.java b/independent-projects/tools/common/src/main/java/io/quarkus/devtools/project/extensions/ExtensionsManager.java index 06900c1a92451..48df9e0d3a679 100644 --- a/independent-projects/tools/common/src/main/java/io/quarkus/devtools/project/extensions/ExtensionsManager.java +++ b/independent-projects/tools/common/src/main/java/io/quarkus/devtools/project/extensions/ExtensionsManager.java @@ -1,11 +1,12 @@ package io.quarkus.devtools.project.extensions; -import static io.quarkus.devtools.project.extensions.Extensions.equalsIgnoringVersions; - +import io.quarkus.bootstrap.model.AppArtifactCoords; +import io.quarkus.bootstrap.model.AppArtifactKey; import io.quarkus.dependencies.Extension; import io.quarkus.devtools.project.BuildTool; import java.io.IOException; import java.util.List; +import java.util.Objects; import java.util.Set; /** @@ -21,20 +22,20 @@ public interface ExtensionsManager { /** * Read the build file(s) to get the list of installed extensions in this Quarkus project. * - * @return The list of extensions installed in the project build file(s). + * @return The list of {@link AppArtifactCoords} installed in the project build file(s). * @throws IOException if a problem occurs while reading the project build file(s) */ - List getInstalled() throws IOException; + List getInstalled() throws IOException; /** * Read build file(s) to check if an extension is installed in this Quarkus project. * - * @param e the extensions to check + * @param key the {@link AppArtifactKey} of the extension to check * @return true if it's installed * @throws IOException if a problem occurs while reading the project build file(s) */ - default boolean isInstalled(Extension e) throws IOException { - return getInstalled().stream().anyMatch(i -> equalsIgnoringVersions(i, e)); + default boolean isInstalled(AppArtifactKey key) throws IOException { + return getInstalled().stream().anyMatch(i -> Objects.equals(i.getKey(), key)); } /** @@ -49,23 +50,60 @@ default boolean isInstalled(Extension e) throws IOException { /** * This is going to install/add all the specified extensions to the project build file(s). * - * Extensions which are already installed are ignored. + *
+     *   - If the project Quarkus platform bom is not defined, an {@link IllegalStateException} will be thrown
+     *   - Extensions which are already installed will ALWAYS be skipped whatever the specified version
+     *   - The provided version will be used wasn't already installed
+     * 
* - * @param extensions the list of extensions to add - * @return the number of added extensions (excluding already installed) + * @param coords the list of {@link AppArtifactCoords} for the extensions to install + * @return the {@link InstallResult} * @throws IOException if a problem occurs while reading/writing the project build file(s) */ - int install(List extensions) throws IOException; + InstallResult install(List coords) 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 * - * @param extensions the set of extensions to remove - * @return the number of removed extensions (excluding already not installed) + * @param keys the set of {@link AppArtifactKey} for the extensions to uninstall + * @return the {@link InstallResult} * @throws IOException if a problem occurs while reading/writing the project build file(s) */ - int uninstall(Set extensions) throws IOException; + UninstallResult uninstall(Set keys) throws IOException; + + class InstallResult { + private final int installed; + + public InstallResult(int installed) { + this.installed = installed; + } + + public int getInstalled() { + return installed; + } + + public boolean isSourceUpdated() { + return installed > 0; + } + } + + class UninstallResult { + private final int uninstalled; + + public UninstallResult(int uninstalled) { + this.uninstalled = uninstalled; + } + + public int getUninstalled() { + return uninstalled; + } + + public boolean isSourceUpdated() { + return uninstalled > 0; + } + + } } diff --git a/independent-projects/tools/common/src/test/java/io/quarkus/devtools/commands/ListExtensionsTest.java b/independent-projects/tools/common/src/test/java/io/quarkus/devtools/commands/ListExtensionsTest.java index cfc4228ce3cd9..6c5cb97f08fe0 100644 --- a/independent-projects/tools/common/src/test/java/io/quarkus/devtools/commands/ListExtensionsTest.java +++ b/independent-projects/tools/common/src/test/java/io/quarkus/devtools/commands/ListExtensionsTest.java @@ -6,7 +6,8 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.junit.jupiter.api.Assertions.assertTrue; -import io.quarkus.dependencies.Extension; +import io.quarkus.bootstrap.model.AppArtifactCoords; +import io.quarkus.bootstrap.model.AppArtifactKey; import io.quarkus.devtools.commands.data.QuarkusCommandException; import io.quarkus.devtools.project.QuarkusProject; import io.quarkus.maven.utilities.MojoUtils; @@ -35,9 +36,9 @@ public void listWithBom() throws Exception { final ListExtensions listExtensions = new ListExtensions(project); - final Map installed = readByManagementKey(project); + final Map installed = readByKey(project); - Assertions.assertNotNull(installed.get(getPluginGroupId() + ":quarkus-agroal")); + Assertions.assertNotNull(installed.get(AppArtifactKey.fromString(getPluginGroupId() + ":quarkus-agroal"))); } /** @@ -53,10 +54,10 @@ public void listWithBomExtensionWithSpaces() throws Exception { final ListExtensions listExtensions = new ListExtensions(quarkusProject); - final Map installed = readByManagementKey(quarkusProject); + final Map installed = readByKey(quarkusProject); - Assertions.assertNotNull(installed.get(getPluginGroupId() + ":quarkus-resteasy")); - Assertions.assertNotNull(installed.get(getPluginGroupId() + ":quarkus-hibernate-validator")); + Assertions.assertNotNull(installed.get(AppArtifactKey.fromString(getPluginGroupId() + ":quarkus-resteasy"))); + Assertions.assertNotNull(installed.get(AppArtifactKey.fromString(getPluginGroupId() + ":quarkus-hibernate-validator"))); } @Test @@ -93,14 +94,14 @@ public void listWithoutBom() throws Exception { boolean checkGuideInLineAfter = false; for (String line : output.split("\r?\n")) { if (line.contains(" Agroal ")) { - assertTrue(line.startsWith("current"), "Agroal should list as current: " + line); + assertTrue(line.startsWith("managed"), "Agroal should list as managed: " + line); agroal = true; } else if (line.contains(" RESTEasy ")) { - assertTrue(line.startsWith("update"), "RESTEasy should list as having an update: " + line); + assertTrue(line.startsWith("overridden"), "RESTEasy should list as having an overridden: " + line); assertTrue( line.endsWith( String.format("%-16s", getPluginVersion())), - "RESTEasy should list as having an update: " + line); + "RESTEasy should list as having an overridden: " + line); resteasy = true; checkGuideInLineAfter = true; } else if (line.contains(" Hibernate Validator ")) { @@ -165,7 +166,7 @@ public void searchRest() throws Exception { void testListExtensionsWithoutAPomFile() throws IOException { final Path tempDirectory = Files.createTempDirectory("proj"); final QuarkusProject project = QuarkusProject.maven(tempDirectory, getPlatformDescriptor()); - assertThatExceptionOfType(UncheckedIOException.class).isThrownBy(() -> readByManagementKey(project)) + assertThatExceptionOfType(UncheckedIOException.class).isThrownBy(() -> readByKey(project)) .withRootCauseInstanceOf(NoSuchFileException.class); } @@ -186,8 +187,8 @@ private QuarkusProject createNewProject(final File pom) throws IOException, Quar return QuarkusProject.maven(projectFolderPath, getPlatformDescriptor()); } - private static Map readByManagementKey(QuarkusProject project) throws IOException { + private static Map readByKey(QuarkusProject project) throws IOException { return project.getExtensionsManager().getInstalled().stream() - .collect(toMap(Extension::managementKey, Function.identity())); + .collect(toMap(AppArtifactCoords::getKey, Function.identity())); } } diff --git a/independent-projects/tools/common/src/test/java/io/quarkus/devtools/commands/handlers/QuarkusCommandHandlersTest.java b/independent-projects/tools/common/src/test/java/io/quarkus/devtools/commands/handlers/QuarkusCommandHandlersTest.java index e87d46a1e4ef2..ab9135375aa1c 100644 --- a/independent-projects/tools/common/src/test/java/io/quarkus/devtools/commands/handlers/QuarkusCommandHandlersTest.java +++ b/independent-projects/tools/common/src/test/java/io/quarkus/devtools/commands/handlers/QuarkusCommandHandlersTest.java @@ -26,11 +26,11 @@ void testMultiMatchByLabels() { List extensions = asList(e1, e2, e3); Collections.shuffle(extensions); - SelectionResult matches = select(extensions, "foo", true); + SelectionResult matches = select("foo", extensions, true); Assertions.assertFalse(matches.matches()); Assertions.assertEquals(2, matches.getExtensions().size()); - matches = select(extensions, "foo", false); + matches = select("foo", extensions, false); Assertions.assertFalse(matches.matches()); Assertions.assertEquals(0, matches.getExtensions().size()); } @@ -46,7 +46,7 @@ void testThatSingleLabelMatchIsNotAMatch() { List extensions = asList(e1, e2); Collections.shuffle(extensions); - SelectionResult matches = select(extensions, "foo", true); + SelectionResult matches = select("foo", extensions, true); Assertions.assertFalse(matches.matches()); Assertions.assertEquals(1, matches.getExtensions().size()); } @@ -65,11 +65,11 @@ void testMultiMatchByArtifactIdsAndNames() { List extensions = asList(e1, e2, e3); Collections.shuffle(extensions); - SelectionResult matches = select(extensions, "foo", false); + SelectionResult matches = select("foo", extensions, false); Assertions.assertFalse(matches.matches()); Assertions.assertEquals(2, matches.getExtensions().size()); - matches = select(extensions, "foo", true); + matches = select("foo", extensions, true); Assertions.assertFalse(matches.matches()); Assertions.assertEquals(3, matches.getExtensions().size()); @@ -90,7 +90,7 @@ void testShortNameSelection() { List extensions = asList(e1, e2, e3); Collections.shuffle(extensions); - SelectionResult matches = select(extensions, "foo", false); + SelectionResult matches = select("foo", extensions, false); Assertions.assertTrue(matches.matches()); Assertions.assertEquals(1, matches.getExtensions().size()); Assertions.assertTrue(matches.iterator().hasNext()); @@ -113,7 +113,7 @@ void testArtifactIdSelectionWithQuarkusPrefix() { List extensions = asList(e1, e2, e3); Collections.shuffle(extensions); - SelectionResult matches = select(extensions, "foo", false); + SelectionResult matches = select("foo", extensions, false); Assertions.assertEquals(1, matches.getExtensions().size()); Assertions.assertTrue(matches.iterator().hasNext()); Assertions.assertTrue(matches.iterator().next().getArtifactId().equalsIgnoreCase("quarkus-foo")); @@ -135,10 +135,10 @@ void testListedVsUnlisted() { List extensions = asList(e1, e2, e3); Collections.shuffle(extensions); - SelectionResult matches = select(extensions, "quarkus-foo", true); + SelectionResult matches = select("quarkus-foo", extensions, true); Assertions.assertEquals(2, matches.getExtensions().size()); - matches = select(extensions, "quarkus-foo-unlisted", true); + matches = select("quarkus-foo-unlisted", extensions, true); Assertions.assertEquals(1, matches.getExtensions().size()); } diff --git a/independent-projects/tools/platform-descriptor-api/src/main/java/io/quarkus/dependencies/Extension.java b/independent-projects/tools/platform-descriptor-api/src/main/java/io/quarkus/dependencies/Extension.java index 7ea3f369ac47c..1c3eda6389a5c 100644 --- a/independent-projects/tools/platform-descriptor-api/src/main/java/io/quarkus/dependencies/Extension.java +++ b/independent-projects/tools/platform-descriptor-api/src/main/java/io/quarkus/dependencies/Extension.java @@ -195,6 +195,9 @@ public Dependency toDependency(boolean stripVersion) { if (version != null && !version.isEmpty() && !stripVersion) { dependency.setVersion(version); } + if (type != null && !type.isEmpty()) { + dependency.setType(type); + } return dependency; }