Repository: quarkusio/quarkus
Issue: Dependency parity validator is missing dependencies with different versions

Reproducible with the camel-quarkus-amqp module of `quarkus-master` branch of Camel Quarkus https://github.com/apache/camel-quarkus/tree/quarkus-master

From my point of view, the dependencies of the amqp runtime and deployment modules a in a perfect parity:
https://github.com/apache/camel-quarkus/blob/quarkus-master/extensions/amqp/runtime/pom.xml
https://github.com/apache/camel-quarkus/blob/quarkus-master/extensions/amqp/deployment/pom.xml

The error of the extension-descriptor mojo is as follows:

```
[ERROR] Quarkus Extension Dependency Verification Error
[ERROR] Deployment artifact org.apache.camel.quarkus:camel-quarkus-amqp-deployment::jar:1.1.0-SNAPSHOT was found to be missing dependencies on Quarkus extension artifacts marked with '-' below:
[ERROR] + org.apache.camel.quarkus:camel-quarkus-amqp-deployment::jar:1.1.0-SNAPSHOT
[ERROR] +     org.apache.camel.quarkus:camel-quarkus-amqp::jar:1.1.0-SNAPSHOT
[ERROR] +     org.amqphub.quarkus:quarkus-qpid-jms-deployment::jar:0.15.0
[ERROR] -         io.quarkus:quarkus-core-deployment::jar:999-SNAPSHOT
[ERROR] -         io.quarkus:quarkus-arc-deployment::jar:999-SNAPSHOT
[ERROR] -         io.quarkus:quarkus-netty-deployment::jar:999-SNAPSHOT
[ERROR] +     org.apache.camel.quarkus:camel-quarkus-core-deployment::jar:1.1.0-SNAPSHOT
[ERROR] +         org.apache.camel.quarkus:camel-quarkus-support-common-deployment::jar:1.1.0-SNAPSHOT
[ERROR] +     org.apache.camel.quarkus:camel-quarkus-jms-deployment::jar:1.1.0-SNAPSHOT
[ERROR] +         org.apache.camel.quarkus:camel-quarkus-support-spring-deployment::jar:1.1.0-SNAPSHOT
[ERROR] +             org.apache.camel.quarkus:camel-quarkus-support-commons-logging-deployment::jar:1.1.0-SNAPSHOT
```

Those missing dependencies are transitive dependencies of `org.amqphub.quarkus:quarkus-qpid-jms-deployment` I am not controling. 

Perhaps the mojo is too picky about the versions? org.amqphub.quarkus:quarkus-qpid-jms-deployment::jar:0.15.0 depends on quarkus 1.5.x while the mojo expects 999-SNAPSHOT (based on our BOMs)?

cc @aloubyansky 

Changed files:
independent-projects/bootstrap/maven-plugin/src/main/java/io/quarkus/maven/ExtensionDescriptorMojo.java

diff --git a/independent-projects/bootstrap/maven-plugin/src/main/java/io/quarkus/maven/ExtensionDescriptorMojo.java b/independent-projects/bootstrap/maven-plugin/src/main/java/io/quarkus/maven/ExtensionDescriptorMojo.java
index b9f7db275ed..75d0de4c0ea 100644
--- a/independent-projects/bootstrap/maven-plugin/src/main/java/io/quarkus/maven/ExtensionDescriptorMojo.java
+++ b/independent-projects/bootstrap/maven-plugin/src/main/java/io/quarkus/maven/ExtensionDescriptorMojo.java
@@ -69,8 +69,6 @@ public class ExtensionDescriptorMojo extends AbstractMojo {
     private static final String GROUP_ID = "group-id";
     private static final String ARTIFACT_ID = "artifact-id";
 
-    private static DefaultPrettyPrinter prettyPrinter = null;
-
     /**
      * The entry point to Aether, i.e. the component doing all the work.
      *
@@ -149,9 +147,6 @@ public void execute() throws MojoExecutionException {
             validateExtensionDeps();
         }
 
-        prettyPrinter = new DefaultPrettyPrinter();
-        prettyPrinter.indentArraysWith(DefaultIndenter.SYSTEM_LINEFEED_INSTANCE);
-
         final Properties props = new Properties();
         props.setProperty(BootstrapConstants.PROP_DEPLOYMENT_ARTIFACT, deployment);
         final Path output = outputDirectory.toPath().resolve(BootstrapConstants.META_INF);
@@ -257,6 +252,9 @@ public void execute() throws MojoExecutionException {
             extObject.put("description", project.getDescription());
         }
 
+        final DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter();
+        prettyPrinter.indentArraysWith(DefaultIndenter.SYSTEM_LINEFEED_INSTANCE);
+
         try (BufferedWriter bw = Files
                 .newBufferedWriter(output.resolve(BootstrapConstants.QUARKUS_EXTENSION_FILE_NAME))) {
             bw.write(getMapper(true).writer(prettyPrinter).writeValueAsString(extObject));
@@ -270,12 +268,13 @@ private void validateExtensionDeps() throws MojoExecutionException {
 
         final AppArtifactCoords deploymentCoords = AppArtifactCoords.fromString(deployment);
 
-        final Node rootDeployment = new Node(null, deploymentCoords, 2);
-        final Node rootRuntime = rootDeployment.newChild(toCoords(project.getArtifact()), 1);
+        final String rootDeploymentGact = gact(deploymentCoords);
+        final Node rootDeployment = new Node(null, rootDeploymentGact, 2);
+        final Node rootRuntime = rootDeployment.newChild(gact(project.getArtifact()), 1);
 
-        final Map<AppArtifactCoords, Node> expectedExtensionDeps = new HashMap<>();
-        expectedExtensionDeps.put(deploymentCoords, rootDeployment);
-        expectedExtensionDeps.put(rootRuntime.coords, rootRuntime);
+        final Map<String, Node> expectedExtensionDeps = new HashMap<>();
+        expectedExtensionDeps.put(rootDeploymentGact, rootDeployment);
+        expectedExtensionDeps.put(rootRuntime.gact, rootRuntime);
         // collect transitive extension deps
         final DependencyResult resolvedDeps;
 
@@ -316,8 +315,9 @@ public boolean visitEnter(DependencyNode node) {
                                         + BootstrapConstants.PROP_DEPLOYMENT_ARTIFACT + " property in its "
                                         + BootstrapConstants.DESCRIPTOR_PATH);
                             }
-                            currentNode = currentNode.newChild(AppArtifactCoords.fromString(deploymentStr), currentNodeId);
-                            expectedExtensionDeps.put(currentNode.coords, currentNode);
+                            currentNode = currentNode.newChild(gact(AppArtifactCoords.fromString(deploymentStr)),
+                                    currentNodeId);
+                            expectedExtensionDeps.put(currentNode.gact, currentNode);
                             extDepsTotal.incrementAndGet();
                         }
                     } catch (IOException e) {
@@ -353,9 +353,7 @@ public boolean visitEnter(DependencyNode dep) {
                 if (artifact == null) {
                     return true;
                 }
-                final AppArtifactCoords coords = new AppArtifactCoords(artifact.getGroupId(), artifact.getArtifactId(),
-                        artifact.getClassifier(), artifact.getExtension(), artifact.getVersion());
-                final Node node = expectedExtensionDeps.get(coords);
+                final Node node = expectedExtensionDeps.get(gact(artifact));
                 if (node != null && !node.included) {
                     node.included = true;
                     extDepsTotal.decrementAndGet();
@@ -374,12 +372,12 @@ public boolean visitLeave(DependencyNode node) {
             log.error("Quarkus Extension Dependency Verification Error");
             log.error("Deployment artifact " + deploymentCoords +
                     " was found to be missing dependencies on Quarkus extension artifacts marked with '-' below:");
-            final List<AppArtifactCoords> missing = rootDeployment.collectMissing(log);
+            final List<String> missing = rootDeployment.collectMissing(log);
             final StringBuilder buf = new StringBuilder();
             buf.append("Deployment artifact ");
             buf.append(deploymentCoords);
             buf.append(" is missing the following dependencies from its configuration: ");
-            final Iterator<AppArtifactCoords> i = missing.iterator();
+            final Iterator<String> i = missing.iterator();
             buf.append(i.next());
             while (i.hasNext()) {
                 buf.append(", ").append(i.next());
@@ -417,11 +415,6 @@ private boolean isAnalyzable(final File f) {
         return f != null && f.getName().endsWith(".jar") && f.exists() && !f.isDirectory();
     }
 
-    private AppArtifactCoords toCoords(Artifact a) {
-        return new AppArtifactCoords(a.getGroupId(), a.getArtifactId(), a.getClassifier(),
-                a.getArtifactHandler().getExtension(), a.getVersion());
-    }
-
     private void transformLegacyToNew(final Path output, ObjectNode extObject, ObjectMapper mapper)
             throws MojoExecutionException {
         ObjectNode metadata = null;
@@ -463,8 +456,6 @@ private void transformLegacyToNew(final Path output, ObjectNode extObject, Objec
 
         extObject.set("metadata", metadata);
 
-        //   updateSourceFiles(output, extObject, mapper);
-
     }
 
     /**
@@ -493,44 +484,74 @@ private ObjectMapper getMapper(boolean yaml) {
         }
     }
 
+    static String gact(AppArtifactCoords artifact) {
+        StringBuilder buf = new StringBuilder();
+        buf.append(artifact.getGroupId()).append(':').append(artifact.getArtifactId()).append(':');
+        final String classifier = artifact.getClassifier();
+        if (classifier != null && !classifier.isEmpty()) {
+            buf.append(classifier);
+        }
+        return buf.append(':').append(artifact.getType()).append(':').toString();
+    }
+
+    static String gact(Artifact artifact) {
+        StringBuilder buf = new StringBuilder();
+        buf.append(artifact.getGroupId()).append(':').append(artifact.getArtifactId()).append(':');
+        final String classifier = artifact.getClassifier();
+        if (classifier != null && !classifier.isEmpty()) {
+            buf.append(classifier);
+        }
+        return buf.append(':').append(artifact.getType()).append(':').toString();
+    }
+
+    static String gact(org.eclipse.aether.artifact.Artifact artifact) {
+        StringBuilder buf = new StringBuilder();
+        buf.append(artifact.getGroupId()).append(':').append(artifact.getArtifactId()).append(':');
+        final String classifier = artifact.getClassifier();
+        if (classifier != null && !classifier.isEmpty()) {
+            buf.append(classifier);
+        }
+        return buf.append(':').append(artifact.getExtension()).append(':').toString();
+    }
+
     private static class Node {
         final Node parent;
-        final AppArtifactCoords coords;
+        final String gact;
         final int id;
         boolean included;
         List<Node> children = new ArrayList<>(0);
 
-        Node(Node parent, AppArtifactCoords artifact, int id) {
+        Node(Node parent, String gact, int id) {
             this.parent = parent;
-            this.coords = artifact;
+            this.gact = gact;
             this.id = id;
         }
 
-        Node newChild(AppArtifactCoords artifact, int id) {
-            final Node child = new Node(this, artifact, id);
+        Node newChild(String gact, int id) {
+            final Node child = new Node(this, gact, id);
             children.add(child);
             return child;
         }
 
-        List<AppArtifactCoords> collectMissing(Log log) {
-            final List<AppArtifactCoords> missing = new ArrayList<>();
+        List<String> collectMissing(Log log) {
+            final List<String> missing = new ArrayList<>();
             collectMissing(log, 0, missing);
             return missing;
         }
 
-        private void collectMissing(Log log, int depth, List<AppArtifactCoords> missing) {
+        private void collectMissing(Log log, int depth, List<String> missing) {
             final StringBuilder buf = new StringBuilder();
             if (included) {
                 buf.append('+');
             } else {
                 buf.append('-');
-                missing.add(coords);
+                missing.add(gact);
             }
             buf.append(' ');
             for (int i = 0; i < depth; ++i) {
                 buf.append("    ");
             }
-            buf.append(coords);
+            buf.append(gact);
             log.error(buf.toString());
             for (Node child : children) {
                 child.collectMissing(log, depth + 1, missing);