From 18915747841500a6f7043778a715c269611015b5 Mon Sep 17 00:00:00 2001 From: Clement Escoffier Date: Mon, 10 Jun 2019 12:39:38 +0200 Subject: [PATCH 1/3] Implementation of the new extension selection algorithm as proposed in #2737. Impact of the new selection algorithm on the guides. Most of it is purely cosmetic and allow using shorter names. Some tweaks are required to avoid ambiguity. Do not use dash in bean validation. Remove the swagger-ui short name. Swagger may mean something else. --- .../cli/commands/AddExtensionCommand.java | 5 +- .../io/quarkus/dependencies/Extension.java | 10 + .../common/src/main/filtered/extensions.json | 8 + .../cli/commands/AddExtensionResult.java | 20 ++ .../quarkus/cli/commands/AddExtensions.java | 160 ++++++++---- .../quarkus/cli/commands/SelectionResult.java | 34 +++ .../cli/commands/AddExtensionsTest.java | 241 +++++++++++++++++- .../io/quarkus/maven/AddExtensionMojo.java | 6 +- .../io/quarkus/maven/CreateProjectMojo.java | 8 +- .../main/asciidoc/async-message-passing.adoc | 2 +- docs/src/main/asciidoc/health-guide.adoc | 4 +- docs/src/main/asciidoc/jwt-guide.adoc | 2 +- docs/src/main/asciidoc/metrics-guide.adoc | 5 +- .../asciidoc/openapi-swaggerui-guide.adoc | 4 +- docs/src/main/asciidoc/opentracing-guide.adoc | 2 +- .../asciidoc/reactive-postgres-client.adoc | 8 +- docs/src/main/asciidoc/rest-client-guide.adoc | 2 +- docs/src/main/asciidoc/using-vertx.adoc | 4 +- 18 files changed, 451 insertions(+), 74 deletions(-) create mode 100644 devtools/common/src/main/java/io/quarkus/cli/commands/AddExtensionResult.java create mode 100644 devtools/common/src/main/java/io/quarkus/cli/commands/SelectionResult.java diff --git a/devtools/aesh/src/main/java/io/quarkus/cli/commands/AddExtensionCommand.java b/devtools/aesh/src/main/java/io/quarkus/cli/commands/AddExtensionCommand.java index 67735d3839270..92085cb93fbe3 100644 --- a/devtools/aesh/src/main/java/io/quarkus/cli/commands/AddExtensionCommand.java +++ b/devtools/aesh/src/main/java/io/quarkus/cli/commands/AddExtensionCommand.java @@ -46,7 +46,10 @@ public CommandResult execute(CommandInvocation commandInvocation) throws Command File pomFile = new File(pom.getAbsolutePath()); AddExtensions project = new AddExtensions(new FileProjectWriter(pomFile.getParentFile()), pomFile.getName()); - project.addExtensions(Collections.singleton(extension)); + AddExtensionResult result = project.addExtensions(Collections.singleton(extension)); + if (!result.succeeded()) { + throw new CommandException("Unable to add an extension matching " + extension); + } } catch (IOException e) { e.printStackTrace(); } diff --git a/devtools/common-core/src/main/java/io/quarkus/dependencies/Extension.java b/devtools/common-core/src/main/java/io/quarkus/dependencies/Extension.java index 5eab82a4d33e2..2e76ca37c8827 100644 --- a/devtools/common-core/src/main/java/io/quarkus/dependencies/Extension.java +++ b/devtools/common-core/src/main/java/io/quarkus/dependencies/Extension.java @@ -30,6 +30,7 @@ public class Extension { private String simplifiedArtifactId; private static final Pattern QUARKUS_PREFIX = Pattern.compile("^quarkus-"); + private String shortName; public Extension() { // Use by mapper. @@ -230,4 +231,13 @@ public boolean equals(Object obj) { public String getGuide() { return guide; } + + public String getShortName() { + return shortName; + } + + public Extension setShortName(String shortName) { + this.shortName = shortName; + return this; + } } diff --git a/devtools/common/src/main/filtered/extensions.json b/devtools/common/src/main/filtered/extensions.json index d785e939bf651..5d3ce61300431 100644 --- a/devtools/common/src/main/filtered/extensions.json +++ b/devtools/common/src/main/filtered/extensions.json @@ -10,6 +10,7 @@ }, { "name": "Arc", + "shortName": "CDI", "labels": [ "arc", "cdi", @@ -69,6 +70,7 @@ }, { "name": "Hibernate ORM", + "shortName": "JPA", "labels": [ "hibernate-orm", "jpa", @@ -105,6 +107,7 @@ }, { "name": "Hibernate Validator", + "shortName": "bean validation", "labels": [ "hibernate-validator", "bean-validation", @@ -242,6 +245,7 @@ }, { "name": "RESTEasy", + "shortName": "jax-rs", "labels": [ "resteasy", "jaxrs", @@ -412,6 +416,7 @@ }, { "name": "SmallRye Reactive Messaging - Kafka Connector", + "shortName": "kafka", "labels": [ "kafka", "reactive-kafka" @@ -462,6 +467,7 @@ }, { "name": "Undertow", + "shortName": "servlet", "labels": [ "undertow", "servlet" @@ -471,6 +477,7 @@ }, { "name": "Undertow WebSockets", + "shortName": "websockets", "labels": [ "undertow-websockets", "undertow-websocket", @@ -485,6 +492,7 @@ }, { "name": "Eclipse Vert.x", + "shortName": "vert.x", "labels": [ "eclipse-vert.x", "vertx", diff --git a/devtools/common/src/main/java/io/quarkus/cli/commands/AddExtensionResult.java b/devtools/common/src/main/java/io/quarkus/cli/commands/AddExtensionResult.java new file mode 100644 index 0000000000000..324e1d7d739b4 --- /dev/null +++ b/devtools/common/src/main/java/io/quarkus/cli/commands/AddExtensionResult.java @@ -0,0 +1,20 @@ +package io.quarkus.cli.commands; + +public class AddExtensionResult { + + private final boolean updated; + private final boolean succeeded; + + public AddExtensionResult(boolean updated, boolean succeeded) { + this.updated = updated; + this.succeeded = succeeded; + } + + public boolean isUpdated() { + return updated; + } + + public boolean succeeded() { + return succeeded; + } +} diff --git a/devtools/common/src/main/java/io/quarkus/cli/commands/AddExtensions.java b/devtools/common/src/main/java/io/quarkus/cli/commands/AddExtensions.java index 9465292a200d2..368941b317102 100644 --- a/devtools/common/src/main/java/io/quarkus/cli/commands/AddExtensions.java +++ b/devtools/common/src/main/java/io/quarkus/cli/commands/AddExtensions.java @@ -6,6 +6,7 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -18,9 +19,10 @@ import io.quarkus.maven.utilities.MojoUtils; public class AddExtensions { - private static String OK = "\u2705"; - private static String NOK = "\u274c"; - private static String NOOP = "\uD83D\uDC4D"; + private static final String OK = "\u2705"; + private static final String NOK = "\u274c"; + private static final String NOOP = "\uD83D\uDC4D"; + private Model model; private String pom; private ProjectWriter writer; @@ -31,57 +33,96 @@ public AddExtensions(final ProjectWriter writer, final String pom) throws IOExce this.pom = pom; } - public boolean addExtensions(final Set extensions) throws IOException { + /** + * Selection algorithm. + * + * @param query the query + * @param extensions the extension list + * @return the list of matching candidates and whether or not a match has been found. + */ + static SelectionResult select(String query, List extensions) { + String q = query.trim().toLowerCase(); + + // Try exact matches + Set matchesNameOrArtifactId = extensions.stream().filter(extension -> extension.getName().equalsIgnoreCase(q) + || matchesArtifactId(extension.getArtifactId(), q)).collect(Collectors.toSet()); + + if (matchesNameOrArtifactId.size() == 1) { + return new SelectionResult(matchesNameOrArtifactId, true); + } + + // Try short names + Set matchesShortName = extensions.stream().filter(extension -> matchesShortName(extension, q)) + .collect(Collectors.toSet()); + + if (matchesShortName.size() == 1) { + return new SelectionResult(matchesShortName, true); + } + + // find by labels + List matchesLabels = extensions.stream() + .filter(extension -> extension.labels().contains(q)).collect(Collectors.toList()); + + Set candidates = new LinkedHashSet<>(); + candidates.addAll(matchesNameOrArtifactId); + candidates.addAll(matchesShortName); + candidates.addAll(matchesLabels); + return new SelectionResult(candidates, false); + } + + private static boolean matchesShortName(Extension extension, String q) { + return q.equalsIgnoreCase(extension.getShortName()); + } + + private static boolean matchesArtifactId(String artifactId, String q) { + return (artifactId.equalsIgnoreCase(q) || + artifactId.equalsIgnoreCase("quarkus-" + q) || + artifactId.equalsIgnoreCase("quarkus-smallrye-" + q)); + } + + public AddExtensionResult addExtensions(final Set extensions) throws IOException { if (extensions == null || extensions.isEmpty()) { - return false; + return new AddExtensionResult(false, true); } boolean updated = false; + boolean success = true; List dependenciesFromBom = getDependenciesFromBom(); - for (String dependency : extensions) { - List matches = MojoUtils.loadExtensions().stream() - .filter(d -> { - boolean hasTag = d.labels().contains(dependency.trim().toLowerCase()); - boolean machName = d.getName() - .toLowerCase() - .contains(dependency.trim().toLowerCase()); - boolean matchArtifactId = d.getArtifactId() - .toLowerCase() - .contains(dependency.trim().toLowerCase()); - return hasTag || machName || matchArtifactId; - }) - .collect(Collectors.toList()); - - if (matches.size() > 1) { + for (String query : extensions) { + List registry = MojoUtils.loadExtensions(); + + SelectionResult result = select(query, registry); + + if (!result.matches()) { StringBuilder sb = new StringBuilder(); - sb.append(NOK) - .append(" Multiple extensions matching '" + dependency + "'"); - - matches.stream() - .forEach(extension -> sb.append(System.lineSeparator()).append(" * ") - .append(extension.managementKey())); - sb.append(System.lineSeparator()).append(" Be more specific e.g using the exact name or the full gav."); - System.out.println(sb); - } else if (matches.size() == 1) { - final Extension extension = matches.get(0); - - if (!MojoUtils.hasDependency(model, extension.getGroupId(), extension.getArtifactId())) { - System.out.println(OK + " Adding extension " + extension.managementKey()); - model.addDependency(extension - .toDependency(containsBOM(model) && - isDefinedInBom(dependenciesFromBom, extension))); - updated = true; + // We have 3 cases, we can still have a single candidate, but the match is on label + // or we have several candidates, or none + Set candidates = result.getExtensions(); + if (candidates.isEmpty() && query.contains(":")) { + updated = addExtensionAsGAV(query) || updated; + } else if (candidates.isEmpty()) { + // No matches at all. + print(NOK + " Cannot find a dependency matching '" + query + "', maybe a typo?"); + success = false; + } else if (candidates.size() == 1) { + sb.append(NOK).append(" One extension matching '").append(query).append("'"); + sb.append(System.lineSeparator()).append(" * ").append(candidates.iterator().next().managementKey()); + sb.append(System.lineSeparator()).append(" Use the exact name or the full GAV to add the extension"); + print(sb.toString()); + success = false; } else { - System.out.println(NOOP + " Skipping extension " + extension.managementKey() + ": already present"); + sb.append(NOK).append(" Multiple extensions matching '").append(query).append("'"); + result.getExtensions() + .forEach(extension -> sb.append(System.lineSeparator()).append(" * ") + .append(extension.managementKey())); + sb.append(System.lineSeparator()).append(" Be more specific e.g using the exact name or the full GAV."); + print(sb.toString()); + success = false; } - } else if (dependency.contains(":")) { - Dependency parsed = MojoUtils.parse(dependency); - System.out.println(OK + " Adding dependency " + parsed.getManagementKey()); - model.addDependency(parsed); - updated = true; - } else { - System.out.println(NOK + " Cannot find a dependency matching '" + dependency + "', maybe a typo?"); + } else { // Matches. + final Extension extension = result.getMatch(); + updated = addDependency(dependenciesFromBom, extension) || updated; } } @@ -91,7 +132,32 @@ public boolean addExtensions(final Set extensions) throws IOException { writer.write(pom, pomOutputStream.toString("UTF-8")); } - return updated; + return new AddExtensionResult(updated, success); + } + + private boolean addDependency(List dependenciesFromBom, Extension extension) { + if (!MojoUtils.hasDependency(model, extension.getGroupId(), extension.getArtifactId())) { + print(OK + " Adding extension " + extension.managementKey()); + model.addDependency(extension + .toDependency(containsBOM(model) && + isDefinedInBom(dependenciesFromBom, extension))); + return true; + } else { + // Don't set success to false as it's should be idempotent. + print(NOOP + " Skipping extension " + extension.managementKey() + ": already present"); + return false; + } + } + + private boolean addExtensionAsGAV(String query) { + Dependency parsed = MojoUtils.parse(query.trim().toLowerCase()); + print(OK + " Adding dependency " + parsed.getManagementKey()); + model.addDependency(parsed); + return true; + } + + private void print(String message) { + System.out.println(message); } private List getDependenciesFromBom() { @@ -100,7 +166,7 @@ private List getDependenciesFromBom() { .getDependencyManagement() .getDependencies(); } catch (IOException e) { - throw new RuntimeException(e.getMessage(), e); + throw new IllegalStateException("Unable to read the BOM file: " + e.getMessage(), e); } } diff --git a/devtools/common/src/main/java/io/quarkus/cli/commands/SelectionResult.java b/devtools/common/src/main/java/io/quarkus/cli/commands/SelectionResult.java new file mode 100644 index 0000000000000..f46e5740684b4 --- /dev/null +++ b/devtools/common/src/main/java/io/quarkus/cli/commands/SelectionResult.java @@ -0,0 +1,34 @@ +package io.quarkus.cli.commands; + +import java.util.Set; + +import io.quarkus.dependencies.Extension; + +public class SelectionResult { + + private final Set extensions; + private final boolean matches; + + public SelectionResult(Set extensions, boolean matches) { + this.extensions = extensions; + this.matches = matches; + } + + public Set getExtensions() { + return extensions; + } + + public boolean matches() { + return matches; + } + + public Extension getMatch() { + if (matches) { + if (extensions.isEmpty() || extensions.size() > 1) { + throw new IllegalStateException("Invalid selection result"); + } + return extensions.iterator().next(); + } + return null; + } +} diff --git a/devtools/common/src/test/java/io/quarkus/cli/commands/AddExtensionsTest.java b/devtools/common/src/test/java/io/quarkus/cli/commands/AddExtensionsTest.java index decc0eda1af11..aa20d9013ae82 100644 --- a/devtools/common/src/test/java/io/quarkus/cli/commands/AddExtensionsTest.java +++ b/devtools/common/src/test/java/io/quarkus/cli/commands/AddExtensionsTest.java @@ -4,20 +4,20 @@ import java.io.File; import java.io.IOException; -import java.util.HashMap; -import java.util.HashSet; +import java.util.*; import org.apache.maven.model.Model; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import io.quarkus.cli.commands.writer.FileProjectWriter; +import io.quarkus.dependencies.Extension; import io.quarkus.maven.utilities.MojoUtils; -public class AddExtensionsTest { +class AddExtensionsTest { @Test - public void addExtension() throws IOException { + void addSomeValidExtensions() throws IOException { final File pom = new File("target/extensions-test", "pom.xml"); CreateProjectTest.delete(pom.getParentFile()); @@ -29,17 +29,236 @@ public void addExtension() throws IOException { File pomFile = new File(pom.getAbsolutePath()); new AddExtensions(new FileProjectWriter(pomFile.getParentFile()), pomFile.getName()) - .addExtensions(new HashSet<>(asList("jdbc-postgre", "agroal", "quarkus-arc", " hibernate-validator"))); + .addExtensions(new HashSet<>(asList("jdbc-postgre", "agroal", "quarkus-arc", " hibernate-validator", + "commons-io:commons-io:2.6"))); Model model = MojoUtils.readPom(pom); hasDependency(model, "quarkus-agroal"); hasDependency(model, "quarkus-arc"); hasDependency(model, "quarkus-hibernate-validator"); - hasDependency(model, "quarkus-jdbc-postgresql"); + hasDependency(model, "commons-io", "commons-io", "2.6"); + doesNotHaveDependency(model, "quarkus-jdbc-postgresql"); + } + + @Test + void addMissingExtension() throws IOException { + final File pom = new File("target/extensions-test", "pom.xml"); + + CreateProjectTest.delete(pom.getParentFile()); + new CreateProject(new FileProjectWriter(pom.getParentFile())) + .groupId("org.acme") + .artifactId("add-extension-test") + .version("0.0.1-SNAPSHOT") + .doCreateProject(new HashMap<>()); + + File pomFile = new File(pom.getAbsolutePath()); + AddExtensionResult result = new AddExtensions(new FileProjectWriter(pomFile.getParentFile()), pomFile.getName()) + .addExtensions(new HashSet<>(asList("missing"))); + + Model model = MojoUtils.readPom(pom); + doesNotHaveDependency(model, "quarkus-missing"); + Assertions.assertFalse(result.succeeded()); + Assertions.assertFalse(result.isUpdated()); + } + + @Test + void addExtensionTwiceInOneBatch() throws IOException { + final File pom = new File("target/extensions-test", "pom.xml"); + + CreateProjectTest.delete(pom.getParentFile()); + new CreateProject(new FileProjectWriter(pom.getParentFile())) + .groupId("org.acme") + .artifactId("add-extension-test") + .version("0.0.1-SNAPSHOT") + .doCreateProject(new HashMap<>()); + + File pomFile = new File(pom.getAbsolutePath()); + AddExtensionResult result = new AddExtensions(new FileProjectWriter(pomFile.getParentFile()), pomFile.getName()) + .addExtensions(new HashSet<>(asList("agroal", "agroal"))); + + Model model = MojoUtils.readPom(pom); + hasDependency(model, "quarkus-agroal"); + Assertions.assertEquals(1, + model.getDependencies().stream().filter(d -> d.getArtifactId().equals("quarkus-agroal")).count()); + Assertions.assertTrue(result.isUpdated()); + Assertions.assertTrue(result.succeeded()); } @Test - public void addDuplicatedExtension() throws IOException { + void addExtensionTwiceInTwoBatches() throws IOException { + final File pom = new File("target/extensions-test", "pom.xml"); + + CreateProjectTest.delete(pom.getParentFile()); + new CreateProject(new FileProjectWriter(pom.getParentFile())) + .groupId("org.acme") + .artifactId("add-extension-test") + .version("0.0.1-SNAPSHOT") + .doCreateProject(new HashMap<>()); + + File pomFile = new File(pom.getAbsolutePath()); + AddExtensionResult result = new AddExtensions(new FileProjectWriter(pomFile.getParentFile()), pomFile.getName()) + .addExtensions(new HashSet<>(Collections.singletonList("agroal"))); + + Model model = MojoUtils.readPom(pom); + hasDependency(model, "quarkus-agroal"); + Assertions.assertEquals(1, + model.getDependencies().stream().filter(d -> d.getArtifactId().equals("quarkus-agroal")).count()); + Assertions.assertTrue(result.isUpdated()); + Assertions.assertTrue(result.succeeded()); + + AddExtensionResult result2 = new AddExtensions(new FileProjectWriter(pomFile.getParentFile()), pomFile.getName()) + .addExtensions(new HashSet<>(Collections.singletonList("agroal"))); + model = MojoUtils.readPom(pom); + hasDependency(model, "quarkus-agroal"); + Assertions.assertEquals(1, + model.getDependencies().stream().filter(d -> d.getArtifactId().equals("quarkus-agroal")).count()); + Assertions.assertFalse(result2.isUpdated()); + Assertions.assertTrue(result2.succeeded()); + } + + @Test + void addExistingAndMissingExtensions() throws IOException { + final File pom = new File("target/extensions-test", "pom.xml"); + + CreateProjectTest.delete(pom.getParentFile()); + new CreateProject(new FileProjectWriter(pom.getParentFile())) + .groupId("org.acme") + .artifactId("add-extension-test") + .version("0.0.1-SNAPSHOT") + .doCreateProject(new HashMap<>()); + + File pomFile = new File(pom.getAbsolutePath()); + AddExtensionResult result = new AddExtensions(new FileProjectWriter(pomFile.getParentFile()), pomFile.getName()) + .addExtensions(new HashSet<>(asList("missing", "agroal"))); + + Model model = MojoUtils.readPom(pom); + doesNotHaveDependency(model, "quarkus-missing"); + hasDependency(model, "quarkus-agroal"); + Assertions.assertFalse(result.succeeded()); + Assertions.assertTrue(result.isUpdated()); + } + + @Test + void testMultiMatchByLabels() { + Extension e1 = new Extension("org.acme", "e1", "1.0") + .setName("some complex seo unaware name") + .setLabels(new String[] { "foo", "bar" }); + Extension e2 = new Extension("org.acme", "e2", "1.0") + .setName("some foo bar") + .setLabels(new String[] { "foo", "bar", "baz" }); + Extension e3 = new Extension("org.acme", "e3", "1.0") + .setName("unrelated") + .setLabels(new String[] { "bar" }); + + List extensions = asList(e1, e2, e3); + Collections.shuffle(extensions); + SelectionResult matches = AddExtensions.select("foo", extensions); + Assertions.assertFalse(matches.matches()); + Assertions.assertEquals(2, matches.getExtensions().size()); + } + + @Test + void testThatSingleLabelMatchIsNotAMatch() { + Extension e1 = new Extension("org.acme", "e1", "1.0") + .setName("e1") + .setLabels(new String[] { "foo", "bar" }); + Extension e2 = new Extension("org.acme", "e2", "1.0") + .setName("e2") + .setLabels(new String[] { "bar", "baz" }); + + List extensions = asList(e1, e2); + Collections.shuffle(extensions); + SelectionResult matches = AddExtensions.select("foo", extensions); + Assertions.assertFalse(matches.matches()); + Assertions.assertEquals(1, matches.getExtensions().size()); + } + + @Test + void testMultiMatchByArtifactIdsAndNames() { + Extension e1 = new Extension("org.acme", "e1", "1.0") + .setName("foo") + .setLabels(new String[] { "foo", "bar" }); + Extension e2 = new Extension("org.acme", "quarkus-foo", "1.0") + .setName("some foo bar") + .setLabels(new String[] { "foo", "bar", "baz" }); + Extension e3 = new Extension("org.acme", "e3", "1.0") + .setName("unrelated") + .setLabels(new String[] { "foo" }); + + List extensions = asList(e1, e2, e3); + Collections.shuffle(extensions); + SelectionResult matches = AddExtensions.select("foo", extensions); + Assertions.assertFalse(matches.matches()); + Assertions.assertEquals(3, matches.getExtensions().size()); + + } + + @Test + void testShortNameSelection() { + Extension e1 = new Extension("org.acme", "some-complex-seo-unaware-artifactid", "1.0") + .setName("some complex seo unaware name") + .setShortName("foo") + .setLabels(new String[] { "foo", "bar" }); + Extension e2 = new Extension("org.acme", "some-foo-bar", "1.0") + .setName("some foo bar") + .setLabels(new String[] { "foo", "bar", "baz" }); + Extension e3 = new Extension("org.acme", "unrelated", "1.0") + .setName("unrelated") + .setLabels(new String[] { "foo" }); + + List extensions = asList(e1, e2, e3); + Collections.shuffle(extensions); + SelectionResult matches = AddExtensions.select("foo", extensions); + Assertions.assertTrue(matches.matches()); + Assertions.assertEquals(1, matches.getExtensions().size()); + Assertions.assertNotNull(matches.getMatch()); + Assertions.assertTrue(matches.getMatch().getArtifactId().equalsIgnoreCase("some-complex-seo-unaware-artifactid")); + } + + @Test + void testArtifactIdSelectionWithQuarkusPrefix() { + Extension e1 = new Extension("org.acme", "quarkus-foo", "1.0") + .setName("some complex seo unaware name") + .setShortName("foo") + .setLabels(new String[] { "foo", "bar" }); + Extension e2 = new Extension("org.acme", "quarkus-foo-bar", "1.0") + .setName("some foo bar") + .setLabels(new String[] { "foo", "bar", "baz" }); + Extension e3 = new Extension("org.acme", "quarkus-unrelated", "1.0") + .setName("unrelated") + .setLabels(new String[] { "foo" }); + + List extensions = asList(e1, e2, e3); + Collections.shuffle(extensions); + SelectionResult matches = AddExtensions.select("foo", extensions); + Assertions.assertEquals(1, matches.getExtensions().size()); + Assertions.assertNotNull(matches.getMatch()); + Assertions.assertTrue(matches.getMatch().getArtifactId().equalsIgnoreCase("quarkus-foo")); + } + + @Test + void testArtifactIdSelectionWithQuarkusSmallRyePrefix() { + Extension e1 = new Extension("org.acme", "quarkus-smallrye-foo", "1.0") + .setName("some complex seo unaware name") + .setShortName("foo") + .setLabels(new String[] { "foo", "bar" }); + Extension e2 = new Extension("org.acme", "quarkus-foo-bar", "1.0") + .setName("some foo bar") + .setLabels(new String[] { "foo", "bar", "baz" }); + Extension e3 = new Extension("org.acme", "quarkus-unrelated", "1.0") + .setName("unrelated") + .setLabels(new String[] { "foo" }); + + List extensions = asList(e1, e2, e3); + Collections.shuffle(extensions); + SelectionResult matches = AddExtensions.select("foo", extensions); + Assertions.assertEquals(1, matches.getExtensions().size()); + Assertions.assertNotNull(matches.getMatch()); + Assertions.assertTrue(matches.getMatch().getArtifactId().equalsIgnoreCase("quarkus-smallrye-foo")); + } + + @Test + void addDuplicatedExtension() throws IOException { final File pom = new File("target/extensions-test", "pom.xml"); CreateProjectTest.delete(pom.getParentFile()); @@ -66,6 +285,14 @@ private void hasDependency(final Model model, final String artifactId) { d.getArtifactId().equals(artifactId))); } + private void hasDependency(final Model model, String groupId, String artifactId, String version) { + Assertions.assertTrue(model.getDependencies() + .stream() + .anyMatch(d -> d.getGroupId().equals(groupId) && + d.getArtifactId().equals(artifactId) && + d.getVersion().equals(version))); + } + private void doesNotHaveDependency(final Model model, final String artifactId) { Assertions.assertFalse(model.getDependencies() .stream() diff --git a/devtools/maven/src/main/java/io/quarkus/maven/AddExtensionMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/AddExtensionMojo.java index d4c2f6eb8a3e7..6b5c70cd4a785 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/AddExtensionMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/AddExtensionMojo.java @@ -15,6 +15,7 @@ import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProject; +import io.quarkus.cli.commands.AddExtensionResult; import io.quarkus.cli.commands.AddExtensions; import io.quarkus.cli.commands.writer.FileProjectWriter; @@ -64,8 +65,11 @@ public void execute() throws MojoExecutionException { try { Model model = project.getOriginalModel().clone(); File pomFile = new File(model.getPomFile().getAbsolutePath()); - new AddExtensions(new FileProjectWriter(pomFile.getParentFile()), pomFile.getName()) + AddExtensionResult result = new AddExtensions(new FileProjectWriter(pomFile.getParentFile()), pomFile.getName()) .addExtensions(ext.stream().map(String::trim).collect(Collectors.toSet())); + if (!result.succeeded()) { + throw new MojoExecutionException("Unable to add extensions"); + } } catch (IOException e) { throw new MojoExecutionException("Unable to update the pom.xml file", e); } diff --git a/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java index d3e1eb4ec6a1b..e223356db31e6 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java @@ -35,6 +35,7 @@ import org.apache.maven.project.ProjectBuilder; import org.fusesource.jansi.Ansi; +import io.quarkus.cli.commands.AddExtensionResult; import io.quarkus.cli.commands.AddExtensions; import io.quarkus.cli.commands.CreateProject; import io.quarkus.cli.commands.writer.FileProjectWriter; @@ -143,8 +144,11 @@ public void execute() throws MojoExecutionException { File createdPomFile = new File(projectRoot, "pom.xml"); if (success) { File pomFile = new File(createdPomFile.getAbsolutePath()); - new AddExtensions(new FileProjectWriter(pomFile.getParentFile()), pomFile.getName()) + AddExtensionResult result = new AddExtensions(new FileProjectWriter(pomFile.getParentFile()), pomFile.getName()) .addExtensions(extensions); + if (!result.succeeded()) { + success = false; + } } createMavenWrapper(createdPomFile); @@ -153,6 +157,8 @@ public void execute() throws MojoExecutionException { } if (success) { printUserInstructions(projectRoot); + } else { + throw new MojoExecutionException("the project was created but it was unable to add the extensions"); } } diff --git a/docs/src/main/asciidoc/async-message-passing.adoc b/docs/src/main/asciidoc/async-message-passing.adoc index e914f6a91497d..42dded69553db 100644 --- a/docs/src/main/asciidoc/async-message-passing.adoc +++ b/docs/src/main/asciidoc/async-message-passing.adoc @@ -219,7 +219,7 @@ First create a new project using: mvn io.quarkus:quarkus-maven-plugin:{quarkus-version}:create \ -DprojectGroupId=org.acme \ -DprojectArtifactId=vertx-quickstart \ - -Dextensions="vertx" + -Dextensions="vert.x" ---- You can already start the application in _dev mode_ using `mvn compile quarkus:dev`. diff --git a/docs/src/main/asciidoc/health-guide.adoc b/docs/src/main/asciidoc/health-guide.adoc index d67360369a7be..8eff03edf51b5 100644 --- a/docs/src/main/asciidoc/health-guide.adoc +++ b/docs/src/main/asciidoc/health-guide.adoc @@ -48,7 +48,7 @@ First, we need a new project. Create a new project with the following command: mvn io.quarkus:quarkus-maven-plugin:{quarkus-version}:create \ -DprojectGroupId=org.acme \ -DprojectArtifactId=microprofile-health \ - -Dextensions="smallrye-health" + -Dextensions="health" ---- This command generates a Maven project, importing the `smallrye-health` extension @@ -222,7 +222,7 @@ All that is needed to enable the MicroProfile Health features in {project-name} * adding the `smallrye-health` {project-name} extension to your project using the `quarkus-maven-plugin`: - mvn quarkus:add-extension -Dextensions="smallrye-health" + mvn quarkus:add-extension -Dextensions="health" * or simply adding the following Maven dependency: diff --git a/docs/src/main/asciidoc/jwt-guide.adoc b/docs/src/main/asciidoc/jwt-guide.adoc index 7a801f08a3391..6bb0ecc36c863 100644 --- a/docs/src/main/asciidoc/jwt-guide.adoc +++ b/docs/src/main/asciidoc/jwt-guide.adoc @@ -57,7 +57,7 @@ mvn io.quarkus:quarkus-maven-plugin:{quarkus-version}:create \ -DprojectArtifactId=using-jwt-rbac \ -DclassName="org.acme.jwt.TokenSecuredResource" \ -Dpath="/secured" \ - -Dextensions="resteasy-jsonb, smallrye-jwt" + -Dextensions="resteasy-jsonb, jwt" ---- This command generates the Maven project with a REST endpoint and imports the `smallrye-jwt` extension, which includes the {mp-jwt} support. diff --git a/docs/src/main/asciidoc/metrics-guide.adoc b/docs/src/main/asciidoc/metrics-guide.adoc index 543ef4b0612cc..89648a6be0e75 100644 --- a/docs/src/main/asciidoc/metrics-guide.adoc +++ b/docs/src/main/asciidoc/metrics-guide.adoc @@ -49,11 +49,10 @@ First, we need a new project. Create a new project with the following command: mvn io.quarkus:quarkus-maven-plugin:{quarkus-version}:create \ -DprojectGroupId=org.acme \ -DprojectArtifactId=microprofile-metrics \ - -Dextensions="smallrye-metrics, resteasy" + -Dextensions="metrics" ---- -This command generates a Maven project, importing the `smallrye-metrics` extension -which is an implementation of the MicroProfile Metrics specification used in {project-name}. +This command generates a Maven project, importing the `smallrye-metrics` extension which is an implementation of the MicroProfile Metrics specification used in {project-name}. == Writing the application diff --git a/docs/src/main/asciidoc/openapi-swaggerui-guide.adoc b/docs/src/main/asciidoc/openapi-swaggerui-guide.adoc index 10995622df75a..a12ccd0f4f5ae 100644 --- a/docs/src/main/asciidoc/openapi-swaggerui-guide.adoc +++ b/docs/src/main/asciidoc/openapi-swaggerui-guide.adoc @@ -210,11 +210,11 @@ public class FruitResourceTest { Quarkus proposes a `smallrye-openapi` extension compliant with the https://github.com/eclipse/microprofile-open-api/[Eclipse MicroProfile OpenAPI] specification in order to generate your API https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md[OpenAPI v3 specification]. -You just need to add the `smallrye-openapi` extension to your Quarkus application: +You just need to add the `openapi` extension to your Quarkus application: [source, shell] ---- -./mvnw quarkus:add-extension -Dextensions="smallrye-openapi" +./mvnw quarkus:add-extension -Dextensions="openapi" ---- Now, we are ready to run our application: diff --git a/docs/src/main/asciidoc/opentracing-guide.adoc b/docs/src/main/asciidoc/opentracing-guide.adoc index 843031db2cb4b..9b1bd054365e1 100644 --- a/docs/src/main/asciidoc/opentracing-guide.adoc +++ b/docs/src/main/asciidoc/opentracing-guide.adoc @@ -46,7 +46,7 @@ mvn io.quarkus:quarkus-maven-plugin:{quarkus-version}:create \ -DprojectArtifactId=using-opentracing \ -DclassName="org.acme.opentracing.TracedResource" \ -Dpath="/hello" \ - -Dextensions="smallrye-opentracing" + -Dextensions="opentracing" ---- This command generates the Maven project with a REST endpoint and imports the `smallrye-opentracing` extension, which diff --git a/docs/src/main/asciidoc/reactive-postgres-client.adoc b/docs/src/main/asciidoc/reactive-postgres-client.adoc index 0a4bb26cfe7a8..795b50f92e6c1 100644 --- a/docs/src/main/asciidoc/reactive-postgres-client.adoc +++ b/docs/src/main/asciidoc/reactive-postgres-client.adoc @@ -60,14 +60,14 @@ If you are creating a new project, set the `extensions` parameter as follows: mvn io.quarkus:quarkus-maven-plugin:{quarkus-version}:create \ -DprojectGroupId=org.acme \ -DprojectArtifactId=reactive-pg-client-quickstart \ - -Dextensions="quarkus-reactive-pg-client" + -Dextensions="reactive-pg-client" ---- -If you have an already created project, the `quarkus-reactive-pg-client` extension can be added to an existing {project-name} project with the `add-extension` command: +If you have an already created project, the `reactive-pg-client` extension can be added to an existing {project-name} project with the `add-extension` command: [source,shell] ---- -mvn quarkus:add-extension -Dextensions="quarkus-reactive-pg-client" +mvn quarkus:add-extension -Dextensions="reactive-pg-client" ---- Otherwise, you can manually add this to the dependencies section of your `pom.xml` file: @@ -90,7 +90,7 @@ Consequently, you also need to add the `quarkus-resteasy-jsonb` extension: [source,shell] ---- -mvn quarkus:add-extension -Dextensions="quarkus-resteasy-jsonb" +mvn quarkus:add-extension -Dextensions="resteasy-jsonb" ---- If you prefer not to use the command line, manually add this to the dependencies section of your `pom.xml` file: diff --git a/docs/src/main/asciidoc/rest-client-guide.adoc b/docs/src/main/asciidoc/rest-client-guide.adoc index a373d71471a4f..ac02c157eb800 100644 --- a/docs/src/main/asciidoc/rest-client-guide.adoc +++ b/docs/src/main/asciidoc/rest-client-guide.adoc @@ -39,7 +39,7 @@ mvn io.quarkus:quarkus-maven-plugin:{quarkus-version}:create \ -DprojectArtifactId=rest-client \ -DclassName="org.acme.restclient.CountriesResource" \ -Dpath="/country" \ - -Dextensions="smallrye-rest-client, resteasy-jsonb" + -Dextensions="rest-client, resteasy-jsonb" ---- This command generates the Maven project with a REST endpoint and imports the `smallrye-rest-client` and `resteasy-jsonb` extensions. diff --git a/docs/src/main/asciidoc/using-vertx.adoc b/docs/src/main/asciidoc/using-vertx.adoc index b78aef4f6b46d..f9516439b0248 100644 --- a/docs/src/main/asciidoc/using-vertx.adoc +++ b/docs/src/main/asciidoc/using-vertx.adoc @@ -25,7 +25,7 @@ If you are creating a new project, set the `extensions` parameter are follows: mvn io.quarkus:quarkus-maven-plugin:{quarkus-version}:create \ -DprojectGroupId=org.acme \ -DprojectArtifactId=vertx-quickstart \ - -Dextensions="vertx" + -Dextensions="vert.x" ---- If you have an already created project, the `vertx` extension can be added to an existing {project-name} project with @@ -33,7 +33,7 @@ the `add-extension` command: [source,shell] ---- -mvn quarkus:add-extension -Dextensions="vertx" +mvn quarkus:add-extension -Dextensions="vert.x" ---- Otherwise, you can manually add this to the dependencies section of your `pom.xml` file: From 55f73d616950514d178715062cce4fe11ee38534 Mon Sep 17 00:00:00 2001 From: Clement Escoffier Date: Wed, 12 Jun 2019 14:02:38 +0200 Subject: [PATCH 2/3] Support partial matches in the name, artifactId or shortName If a query is a GAV, insert the dependency as a GAV. Also, avoid reloading the extension list every time. When the query is containg in the artifact id or in the name of the extension. --- .../io/quarkus/dependencies/Extension.java | 3 + .../common/src/main/filtered/extensions.json | 1 - .../quarkus/cli/commands/AddExtensions.java | 103 +++++++++------ .../cli/commands/AddExtensionsTest.java | 123 +++++++++++++++++- .../main/asciidoc/async-message-passing.adoc | 2 +- docs/src/main/asciidoc/using-vertx.adoc | 4 +- 6 files changed, 187 insertions(+), 49 deletions(-) diff --git a/devtools/common-core/src/main/java/io/quarkus/dependencies/Extension.java b/devtools/common-core/src/main/java/io/quarkus/dependencies/Extension.java index 2e76ca37c8827..81cdffc9d9c78 100644 --- a/devtools/common-core/src/main/java/io/quarkus/dependencies/Extension.java +++ b/devtools/common-core/src/main/java/io/quarkus/dependencies/Extension.java @@ -233,6 +233,9 @@ public String getGuide() { } public String getShortName() { + if (shortName == null) { + return name; + } return shortName; } diff --git a/devtools/common/src/main/filtered/extensions.json b/devtools/common/src/main/filtered/extensions.json index 5d3ce61300431..dbe7b20068ec8 100644 --- a/devtools/common/src/main/filtered/extensions.json +++ b/devtools/common/src/main/filtered/extensions.json @@ -492,7 +492,6 @@ }, { "name": "Eclipse Vert.x", - "shortName": "vert.x", "labels": [ "eclipse-vert.x", "vertx", diff --git a/devtools/common/src/main/java/io/quarkus/cli/commands/AddExtensions.java b/devtools/common/src/main/java/io/quarkus/cli/commands/AddExtensions.java index 368941b317102..2f0b4e19b4fdb 100644 --- a/devtools/common/src/main/java/io/quarkus/cli/commands/AddExtensions.java +++ b/devtools/common/src/main/java/io/quarkus/cli/commands/AddExtensions.java @@ -44,9 +44,9 @@ static SelectionResult select(String query, List extensions) { String q = query.trim().toLowerCase(); // Try exact matches - Set matchesNameOrArtifactId = extensions.stream().filter(extension -> extension.getName().equalsIgnoreCase(q) - || matchesArtifactId(extension.getArtifactId(), q)).collect(Collectors.toSet()); - + Set matchesNameOrArtifactId = extensions.stream() + .filter(extension -> extension.getName().equalsIgnoreCase(q) || matchesArtifactId(extension.getArtifactId(), q)) + .collect(Collectors.toSet()); if (matchesNameOrArtifactId.size() == 1) { return new SelectionResult(matchesNameOrArtifactId, true); } @@ -55,10 +55,20 @@ static SelectionResult select(String query, List extensions) { Set matchesShortName = extensions.stream().filter(extension -> matchesShortName(extension, q)) .collect(Collectors.toSet()); - if (matchesShortName.size() == 1) { + if (matchesShortName.size() == 1 && matchesNameOrArtifactId.isEmpty()) { return new SelectionResult(matchesShortName, true); } + // Partial matches on name, artifactId and short names + Set partialMatches = extensions.stream().filter(extension -> extension.getName().toLowerCase().contains(q) + || extension.getArtifactId().toLowerCase().contains(q) + || extension.getShortName().toLowerCase().contains(q)).collect(Collectors.toSet()); + // Even if we have a single partial match, if the name, artifactId and short names are ambiguous, so not + // consider it as a match. + if (partialMatches.size() == 1 && matchesNameOrArtifactId.isEmpty() && matchesShortName.isEmpty()) { + return new SelectionResult(partialMatches, true); + } + // find by labels List matchesLabels = extensions.stream() .filter(extension -> extension.labels().contains(q)).collect(Collectors.toList()); @@ -66,6 +76,7 @@ static SelectionResult select(String query, List extensions) { Set candidates = new LinkedHashSet<>(); candidates.addAll(matchesNameOrArtifactId); candidates.addAll(matchesShortName); + candidates.addAll(partialMatches); candidates.addAll(matchesLabels); return new SelectionResult(candidates, false); } @@ -89,40 +100,46 @@ public AddExtensionResult addExtensions(final Set extensions) throws IOE boolean success = true; List dependenciesFromBom = getDependenciesFromBom(); + List registry = MojoUtils.loadExtensions(); for (String query : extensions) { - List registry = MojoUtils.loadExtensions(); - - SelectionResult result = select(query, registry); - - if (!result.matches()) { - 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 - Set candidates = result.getExtensions(); - if (candidates.isEmpty() && query.contains(":")) { - updated = addExtensionAsGAV(query) || updated; - } else if (candidates.isEmpty()) { - // No matches at all. - print(NOK + " Cannot find a dependency matching '" + query + "', maybe a typo?"); - success = false; - } else if (candidates.size() == 1) { - sb.append(NOK).append(" One extension matching '").append(query).append("'"); - sb.append(System.lineSeparator()).append(" * ").append(candidates.iterator().next().managementKey()); - sb.append(System.lineSeparator()).append(" Use the exact name or the full GAV to add the extension"); - print(sb.toString()); - success = false; - } else { - sb.append(NOK).append(" Multiple extensions matching '").append(query).append("'"); - result.getExtensions() - .forEach(extension -> sb.append(System.lineSeparator()).append(" * ") - .append(extension.managementKey())); - sb.append(System.lineSeparator()).append(" Be more specific e.g using the exact name or the full GAV."); - print(sb.toString()); - success = false; + + if (query.contains(":")) { + // GAV case. + updated = addExtensionAsGAV(query) || updated; + } else { + SelectionResult result = select(query, registry); + if (!result.matches()) { + 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 + Set candidates = result.getExtensions(); + if (candidates.isEmpty()) { + // No matches at all. + print(NOK + " Cannot find a dependency matching '" + query + "', maybe a typo?"); + success = false; + } else if (candidates.size() == 1) { + sb.append(NOK).append(" One extension matching '").append(query).append("'"); + sb.append(System.lineSeparator()).append(" * ") + .append(candidates.iterator().next().managementKey()); + sb.append(System.lineSeparator()) + .append(" Use the exact name or the full GAV to add the extension"); + print(sb.toString()); + success = false; + } else { + sb.append(NOK).append(" Multiple extensions matching '").append(query).append("'"); + result.getExtensions() + .forEach(extension -> sb.append(System.lineSeparator()).append(" * ") + .append(extension.managementKey())); + sb.append(System.lineSeparator()) + .append(" Be more specific e.g using the exact name or the full GAV."); + print(sb.toString()); + success = false; + } + } else { // Matches. + final Extension extension = result.getMatch(); + // Don't set success to false even if the dependency is not added; as it's should be idempotent. + updated = addDependency(dependenciesFromBom, extension) || updated; } - } else { // Matches. - final Extension extension = result.getMatch(); - updated = addDependency(dependenciesFromBom, extension) || updated; } } @@ -143,7 +160,6 @@ private boolean addDependency(List dependenciesFromBom, Extension ex isDefinedInBom(dependenciesFromBom, extension))); return true; } else { - // Don't set success to false as it's should be idempotent. print(NOOP + " Skipping extension " + extension.managementKey() + ": already present"); return false; } @@ -151,9 +167,16 @@ private boolean addDependency(List dependenciesFromBom, Extension ex private boolean addExtensionAsGAV(String query) { Dependency parsed = MojoUtils.parse(query.trim().toLowerCase()); - print(OK + " Adding dependency " + parsed.getManagementKey()); - model.addDependency(parsed); - return true; + boolean alreadyThere = model.getDependencies().stream() + .anyMatch(d -> d.getManagementKey().equalsIgnoreCase(parsed.getManagementKey())); + if (!alreadyThere) { + print(OK + " Adding dependency " + parsed.getManagementKey()); + model.addDependency(parsed); + return true; + } else { + print(NOOP + " Dependency " + parsed.getManagementKey() + " already in the pom.xml file - skipping"); + return false; + } } private void print(String message) { diff --git a/devtools/common/src/test/java/io/quarkus/cli/commands/AddExtensionsTest.java b/devtools/common/src/test/java/io/quarkus/cli/commands/AddExtensionsTest.java index aa20d9013ae82..202e4b7d1c81e 100644 --- a/devtools/common/src/test/java/io/quarkus/cli/commands/AddExtensionsTest.java +++ b/devtools/common/src/test/java/io/quarkus/cli/commands/AddExtensionsTest.java @@ -4,7 +4,10 @@ import java.io.File; import java.io.IOException; -import java.util.*; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; import org.apache.maven.model.Model; import org.junit.jupiter.api.Assertions; @@ -37,7 +40,28 @@ void addSomeValidExtensions() throws IOException { hasDependency(model, "quarkus-arc"); hasDependency(model, "quarkus-hibernate-validator"); hasDependency(model, "commons-io", "commons-io", "2.6"); - doesNotHaveDependency(model, "quarkus-jdbc-postgresql"); + hasDependency(model, "quarkus-jdbc-postgresql"); + } + + @Test + void testPartialMatches() throws IOException { + File pom = new File("target/extensions-test", "pom.xml"); + + CreateProjectTest.delete(pom.getParentFile()); + new CreateProject(new FileProjectWriter(pom.getParentFile())) + .groupId("org.acme") + .artifactId("add-extension-test") + .version("0.0.1-SNAPSHOT") + .doCreateProject(new HashMap<>()); + + File pomFile = new File(pom.getAbsolutePath()); + new AddExtensions(new FileProjectWriter(pomFile.getParentFile()), pomFile.getName()) + .addExtensions(new HashSet<>(asList("orm-pana", "jdbc-postgre", "arc"))); + + Model model = MojoUtils.readPom(pom); + hasDependency(model, "quarkus-arc"); + hasDependency(model, "quarkus-hibernate-orm-panache"); + hasDependency(model, "quarkus-jdbc-postgresql"); } @Test @@ -53,7 +77,7 @@ void addMissingExtension() throws IOException { File pomFile = new File(pom.getAbsolutePath()); AddExtensionResult result = new AddExtensions(new FileProjectWriter(pomFile.getParentFile()), pomFile.getName()) - .addExtensions(new HashSet<>(asList("missing"))); + .addExtensions(new HashSet<>(Collections.singletonList("missing"))); Model model = MojoUtils.readPom(pom); doesNotHaveDependency(model, "quarkus-missing"); @@ -116,6 +140,39 @@ void addExtensionTwiceInTwoBatches() throws IOException { Assertions.assertTrue(result2.succeeded()); } + /** + * This test reproduce the issue we had using the first selection algorithm. + * The `arc` query was matching ArC but also hibernate-search-elasticsearch. + */ + @Test + void testPartialMatchConflict() throws IOException { + final File pom = new File("target/extensions-test", "pom.xml"); + + CreateProjectTest.delete(pom.getParentFile()); + new CreateProject(new FileProjectWriter(pom.getParentFile())) + .groupId("org.acme") + .artifactId("add-extension-test") + .version("0.0.1-SNAPSHOT") + .doCreateProject(new HashMap<>()); + + File pomFile = new File(pom.getAbsolutePath()); + AddExtensionResult result = new AddExtensions(new FileProjectWriter(pomFile.getParentFile()), pomFile.getName()) + .addExtensions(new HashSet<>(Collections.singletonList("arc"))); + + Assertions.assertTrue(result.isUpdated()); + Assertions.assertTrue(result.succeeded()); + Model model = MojoUtils.readPom(pom); + hasDependency(model, "quarkus-arc"); + + result = new AddExtensions(new FileProjectWriter(pomFile.getParentFile()), pomFile.getName()) + .addExtensions(new HashSet<>(Collections.singletonList("elasticsearch"))); + + Assertions.assertTrue(result.isUpdated()); + Assertions.assertTrue(result.succeeded()); + model = MojoUtils.readPom(pom); + hasDependency(model, "quarkus-hibernate-search-elasticsearch"); + } + @Test void addExistingAndMissingExtensions() throws IOException { final File pom = new File("target/extensions-test", "pom.xml"); @@ -141,10 +198,10 @@ void addExistingAndMissingExtensions() throws IOException { @Test void testMultiMatchByLabels() { Extension e1 = new Extension("org.acme", "e1", "1.0") - .setName("some complex seo unaware name") + .setName("some extension 1") .setLabels(new String[] { "foo", "bar" }); Extension e2 = new Extension("org.acme", "e2", "1.0") - .setName("some foo bar") + .setName("some extension 2") .setLabels(new String[] { "foo", "bar", "baz" }); Extension e3 = new Extension("org.acme", "e3", "1.0") .setName("unrelated") @@ -278,6 +335,62 @@ void addDuplicatedExtension() throws IOException { doesNotHaveDependency(model, "quarkus-jdbc-h2"); } + @Test + void addDuplicatedExtensionUsingGAV() throws IOException { + final File pom = new File("target/extensions-test", "pom.xml"); + + CreateProjectTest.delete(pom.getParentFile()); + new CreateProject(new FileProjectWriter(pom.getParentFile())) + .groupId("org.acme") + .artifactId("add-extension-test") + .version("0.0.1-SNAPSHOT") + .doCreateProject(new HashMap<>()); + + File pomFile = new File(pom.getAbsolutePath()); + new AddExtensions(new FileProjectWriter(pomFile.getParentFile()), pomFile.getName()) + .addExtensions(new HashSet<>(asList("org.acme:acme:1", "org.acme:acme:1"))); + + Model model = MojoUtils.readPom(pom); + hasDependency(model, "org.acme", "acme", "1"); + Assertions.assertEquals(1, + model.getDependencies().stream().filter(d -> d.getArtifactId().equalsIgnoreCase("acme")).count()); + } + + @Test + void testVertxWithAndWithoutDot() throws IOException { + final File pom = new File("target/extensions-test", "pom.xml"); + + // Test with vertx + CreateProjectTest.delete(pom.getParentFile()); + new CreateProject(new FileProjectWriter(pom.getParentFile())) + .groupId("org.acme") + .artifactId("add-extension-test") + .version("0.0.1-SNAPSHOT") + .doCreateProject(new HashMap<>()); + + File pomFile = new File(pom.getAbsolutePath()); + new AddExtensions(new FileProjectWriter(pomFile.getParentFile()), pomFile.getName()) + .addExtensions(new HashSet<>(Collections.singletonList("vertx"))); + + Model model = MojoUtils.readPom(pom); + hasDependency(model, "quarkus-vertx"); + + // Test with vert.x (the official writing) + CreateProjectTest.delete(pom.getParentFile()); + new CreateProject(new FileProjectWriter(pom.getParentFile())) + .groupId("org.acme") + .artifactId("add-extension-test") + .version("0.0.1-SNAPSHOT") + .doCreateProject(new HashMap<>()); + + pomFile = new File(pom.getAbsolutePath()); + new AddExtensions(new FileProjectWriter(pomFile.getParentFile()), pomFile.getName()) + .addExtensions(new HashSet<>(Collections.singletonList("vert.x"))); + + model = MojoUtils.readPom(pom); + hasDependency(model, "quarkus-vertx"); + } + private void hasDependency(final Model model, final String artifactId) { Assertions.assertTrue(model.getDependencies() .stream() diff --git a/docs/src/main/asciidoc/async-message-passing.adoc b/docs/src/main/asciidoc/async-message-passing.adoc index 42dded69553db..e914f6a91497d 100644 --- a/docs/src/main/asciidoc/async-message-passing.adoc +++ b/docs/src/main/asciidoc/async-message-passing.adoc @@ -219,7 +219,7 @@ First create a new project using: mvn io.quarkus:quarkus-maven-plugin:{quarkus-version}:create \ -DprojectGroupId=org.acme \ -DprojectArtifactId=vertx-quickstart \ - -Dextensions="vert.x" + -Dextensions="vertx" ---- You can already start the application in _dev mode_ using `mvn compile quarkus:dev`. diff --git a/docs/src/main/asciidoc/using-vertx.adoc b/docs/src/main/asciidoc/using-vertx.adoc index f9516439b0248..b78aef4f6b46d 100644 --- a/docs/src/main/asciidoc/using-vertx.adoc +++ b/docs/src/main/asciidoc/using-vertx.adoc @@ -25,7 +25,7 @@ If you are creating a new project, set the `extensions` parameter are follows: mvn io.quarkus:quarkus-maven-plugin:{quarkus-version}:create \ -DprojectGroupId=org.acme \ -DprojectArtifactId=vertx-quickstart \ - -Dextensions="vert.x" + -Dextensions="vertx" ---- If you have an already created project, the `vertx` extension can be added to an existing {project-name} project with @@ -33,7 +33,7 @@ the `add-extension` command: [source,shell] ---- -mvn quarkus:add-extension -Dextensions="vert.x" +mvn quarkus:add-extension -Dextensions="vertx" ---- Otherwise, you can manually add this to the dependencies section of your `pom.xml` file: From cc50c1bea52693ec5b084964bddaed9244a77338 Mon Sep 17 00:00:00 2001 From: Clement Escoffier Date: Sun, 16 Jun 2019 15:56:29 +0200 Subject: [PATCH 3/3] Add short names for smallrye dependencies in the extension list Disable the label match by default --- .../common/src/main/filtered/extensions.json | 4 ++ .../quarkus/cli/commands/AddExtensions.java | 29 +++++++------- .../cli/commands/AddExtensionsTest.java | 39 +++++++------------ 3 files changed, 31 insertions(+), 41 deletions(-) diff --git a/devtools/common/src/main/filtered/extensions.json b/devtools/common/src/main/filtered/extensions.json index dbe7b20068ec8..bb16638d34bcf 100644 --- a/devtools/common/src/main/filtered/extensions.json +++ b/devtools/common/src/main/filtered/extensions.json @@ -293,6 +293,7 @@ }, { "name": "SmallRye Context Propagation", + "shortName": "context propagation", "labels": [ "smallrye-context-propagation", "microprofile-context-propagation", @@ -317,6 +318,7 @@ }, { "name": "SmallRye Health", + "shortName": "health", "labels": [ "smallrye-health", "health-check", @@ -342,6 +344,7 @@ }, { "name": "SmallRye Metrics", + "shortName": "metrics", "labels": [ "smallrye-metrics", "metrics", @@ -379,6 +382,7 @@ }, { "name": "SmallRye Reactive Streams Operators", + "shortName": "reactive streams", "labels": [ "smallrye-reactive-streams-operators", "smallrye-reactive-streams", diff --git a/devtools/common/src/main/java/io/quarkus/cli/commands/AddExtensions.java b/devtools/common/src/main/java/io/quarkus/cli/commands/AddExtensions.java index 2f0b4e19b4fdb..8e03dd215a159 100644 --- a/devtools/common/src/main/java/io/quarkus/cli/commands/AddExtensions.java +++ b/devtools/common/src/main/java/io/quarkus/cli/commands/AddExtensions.java @@ -6,6 +6,7 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; @@ -38,9 +39,11 @@ public AddExtensions(final ProjectWriter writer, final String pom) throws IOExce * * @param query the query * @param extensions the extension list + * @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(String query, List extensions) { + static SelectionResult select(String query, List extensions, boolean labelLookup) { String q = query.trim().toLowerCase(); // Try exact matches @@ -70,8 +73,13 @@ static SelectionResult select(String query, List extensions) { } // find by labels - List matchesLabels = extensions.stream() - .filter(extension -> extension.labels().contains(q)).collect(Collectors.toList()); + List matchesLabels; + if (labelLookup) { + matchesLabels = extensions.stream() + .filter(extension -> extension.labels().contains(q)).collect(Collectors.toList()); + } else { + matchesLabels = new ArrayList<>(); + } Set candidates = new LinkedHashSet<>(); candidates.addAll(matchesNameOrArtifactId); @@ -86,9 +94,8 @@ private static boolean matchesShortName(Extension extension, String q) { } private static boolean matchesArtifactId(String artifactId, String q) { - return (artifactId.equalsIgnoreCase(q) || - artifactId.equalsIgnoreCase("quarkus-" + q) || - artifactId.equalsIgnoreCase("quarkus-smallrye-" + q)); + return artifactId.equalsIgnoreCase(q) || + artifactId.equalsIgnoreCase("quarkus-" + q); } public AddExtensionResult addExtensions(final Set extensions) throws IOException { @@ -107,7 +114,7 @@ public AddExtensionResult addExtensions(final Set extensions) throws IOE // GAV case. updated = addExtensionAsGAV(query) || updated; } else { - SelectionResult result = select(query, registry); + SelectionResult result = select(query, registry, false); if (!result.matches()) { StringBuilder sb = new StringBuilder(); // We have 3 cases, we can still have a single candidate, but the match is on label @@ -117,14 +124,6 @@ public AddExtensionResult addExtensions(final Set extensions) throws IOE // No matches at all. print(NOK + " Cannot find a dependency matching '" + query + "', maybe a typo?"); success = false; - } else if (candidates.size() == 1) { - sb.append(NOK).append(" One extension matching '").append(query).append("'"); - sb.append(System.lineSeparator()).append(" * ") - .append(candidates.iterator().next().managementKey()); - sb.append(System.lineSeparator()) - .append(" Use the exact name or the full GAV to add the extension"); - print(sb.toString()); - success = false; } else { sb.append(NOK).append(" Multiple extensions matching '").append(query).append("'"); result.getExtensions() diff --git a/devtools/common/src/test/java/io/quarkus/cli/commands/AddExtensionsTest.java b/devtools/common/src/test/java/io/quarkus/cli/commands/AddExtensionsTest.java index 202e4b7d1c81e..f056518c4e625 100644 --- a/devtools/common/src/test/java/io/quarkus/cli/commands/AddExtensionsTest.java +++ b/devtools/common/src/test/java/io/quarkus/cli/commands/AddExtensionsTest.java @@ -209,9 +209,13 @@ void testMultiMatchByLabels() { List extensions = asList(e1, e2, e3); Collections.shuffle(extensions); - SelectionResult matches = AddExtensions.select("foo", extensions); + SelectionResult matches = AddExtensions.select("foo", extensions, true); Assertions.assertFalse(matches.matches()); Assertions.assertEquals(2, matches.getExtensions().size()); + + matches = AddExtensions.select("foo", extensions, false); + Assertions.assertFalse(matches.matches()); + Assertions.assertEquals(0, matches.getExtensions().size()); } @Test @@ -225,7 +229,7 @@ void testThatSingleLabelMatchIsNotAMatch() { List extensions = asList(e1, e2); Collections.shuffle(extensions); - SelectionResult matches = AddExtensions.select("foo", extensions); + SelectionResult matches = AddExtensions.select("foo", extensions, true); Assertions.assertFalse(matches.matches()); Assertions.assertEquals(1, matches.getExtensions().size()); } @@ -244,7 +248,11 @@ void testMultiMatchByArtifactIdsAndNames() { List extensions = asList(e1, e2, e3); Collections.shuffle(extensions); - SelectionResult matches = AddExtensions.select("foo", extensions); + SelectionResult matches = AddExtensions.select("foo", extensions, false); + Assertions.assertFalse(matches.matches()); + Assertions.assertEquals(2, matches.getExtensions().size()); + + matches = AddExtensions.select("foo", extensions, true); Assertions.assertFalse(matches.matches()); Assertions.assertEquals(3, matches.getExtensions().size()); @@ -265,7 +273,7 @@ void testShortNameSelection() { List extensions = asList(e1, e2, e3); Collections.shuffle(extensions); - SelectionResult matches = AddExtensions.select("foo", extensions); + SelectionResult matches = AddExtensions.select("foo", extensions, false); Assertions.assertTrue(matches.matches()); Assertions.assertEquals(1, matches.getExtensions().size()); Assertions.assertNotNull(matches.getMatch()); @@ -287,33 +295,12 @@ void testArtifactIdSelectionWithQuarkusPrefix() { List extensions = asList(e1, e2, e3); Collections.shuffle(extensions); - SelectionResult matches = AddExtensions.select("foo", extensions); + SelectionResult matches = AddExtensions.select("foo", extensions, false); Assertions.assertEquals(1, matches.getExtensions().size()); Assertions.assertNotNull(matches.getMatch()); Assertions.assertTrue(matches.getMatch().getArtifactId().equalsIgnoreCase("quarkus-foo")); } - @Test - void testArtifactIdSelectionWithQuarkusSmallRyePrefix() { - Extension e1 = new Extension("org.acme", "quarkus-smallrye-foo", "1.0") - .setName("some complex seo unaware name") - .setShortName("foo") - .setLabels(new String[] { "foo", "bar" }); - Extension e2 = new Extension("org.acme", "quarkus-foo-bar", "1.0") - .setName("some foo bar") - .setLabels(new String[] { "foo", "bar", "baz" }); - Extension e3 = new Extension("org.acme", "quarkus-unrelated", "1.0") - .setName("unrelated") - .setLabels(new String[] { "foo" }); - - List extensions = asList(e1, e2, e3); - Collections.shuffle(extensions); - SelectionResult matches = AddExtensions.select("foo", extensions); - Assertions.assertEquals(1, matches.getExtensions().size()); - Assertions.assertNotNull(matches.getMatch()); - Assertions.assertTrue(matches.getMatch().getArtifactId().equalsIgnoreCase("quarkus-smallrye-foo")); - } - @Test void addDuplicatedExtension() throws IOException { final File pom = new File("target/extensions-test", "pom.xml");