diff --git a/build-parent/pom.xml b/build-parent/pom.xml
index bee4d6e4bab8b0..d17824be0db2a9 100644
--- a/build-parent/pom.xml
+++ b/build-parent/pom.xml
@@ -131,6 +131,7 @@
${project.version}
+ generate-extension-descriptor
extension-descriptor
diff --git a/devtools/maven/pom.xml b/devtools/maven/pom.xml
index 1eeeb868ddb7d0..51e59b85751b0e 100644
--- a/devtools/maven/pom.xml
+++ b/devtools/maven/pom.xml
@@ -100,6 +100,11 @@
freemarker
+
+ org.glassfish
+ javax.json
+
+
com.fasterxml.jackson.core
diff --git a/devtools/maven/src/main/java/io/quarkus/maven/GenerateExtensionsJsonMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/GenerateExtensionsJsonMojo.java
new file mode 100644
index 00000000000000..6317b52ced88f4
--- /dev/null
+++ b/devtools/maven/src/main/java/io/quarkus/maven/GenerateExtensionsJsonMojo.java
@@ -0,0 +1,245 @@
+package io.quarkus.maven;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.FileSystem;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.List;
+import java.util.Properties;
+
+import javax.json.Json;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonWriter;
+import javax.json.JsonWriterFactory;
+import javax.json.stream.JsonGenerator;
+
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugins.annotations.Component;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.project.MavenProject;
+import org.eclipse.aether.RepositorySystem;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.artifact.DefaultArtifact;
+import org.eclipse.aether.graph.Dependency;
+import org.eclipse.aether.repository.RemoteRepository;
+import org.eclipse.aether.resolution.ArtifactDescriptorException;
+import org.eclipse.aether.resolution.ArtifactDescriptorRequest;
+import org.eclipse.aether.resolution.ArtifactRequest;
+import org.eclipse.aether.resolution.ArtifactResolutionException;
+import org.eclipse.aether.resolution.ArtifactResult;
+
+import io.quarkus.bootstrap.BootstrapConstants;
+import io.quarkus.bootstrap.BootstrapDependencyProcessingException;
+import io.quarkus.bootstrap.resolver.maven.DeploymentInjectionException;
+import io.quarkus.bootstrap.util.ZipUtils;
+
+/**
+ * This goal generates a list of extensions for a given BOM
+ * and stores it in a JSON format file that is later used by the tools
+ * as the catalog of available extensions.
+ */
+@Mojo(name = "generate-extensions-json")
+public class GenerateExtensionsJsonMojo extends AbstractMojo {
+
+ private static final String PROP_ARTIFACT_ID = "artifactId";
+ private static final String PROP_GROUP_ID = "groupId";
+ private static final String PROP_GUIDE = "guide";
+ private static final String PROP_LABELS = "labels";
+ private static final String PROP_NAME = "name";
+ private static final String PROP_SHORT_NAME = "shortName";
+
+ @Parameter(property = "bomGroupId", defaultValue = "io.quarkus")
+ private String bomGroupId;
+
+ @Parameter(property = "bomArtifactId", defaultValue = "quarkus-bom")
+ private String bomArtifactId;
+
+ @Parameter(property = "bomVersion", defaultValue = "${project.version}")
+ private String bomVersion;
+
+ @Parameter(property = "outputFile", defaultValue = "${project.build.directory}/extensions.json")
+ private File outputFile;
+
+ @Component
+ private RepositorySystem repoSystem;
+
+ @Parameter(defaultValue = "${repositorySystemSession}", readonly = true)
+ private RepositorySystemSession repoSession;
+
+ @Parameter(defaultValue = "${project.remoteProjectRepositories}", readonly = true, required = true)
+ private List repos;
+
+ @Component
+ private MavenProject project;
+
+ public GenerateExtensionsJsonMojo() {
+ MojoLogger.logSupplier = this::getLog;
+ }
+
+ @Override
+ public void execute() throws MojoExecutionException, MojoFailureException {
+
+ final DefaultArtifact bomArtifact = new DefaultArtifact(bomGroupId, bomArtifactId, "", "pom", bomVersion);
+ debug("Generating catalog of extensions for %s", bomArtifact);
+
+ final List deps;
+ try {
+ deps = repoSystem
+ .readArtifactDescriptor(repoSession,
+ new ArtifactDescriptorRequest().setRepositories(repos).setArtifact(bomArtifact))
+ .getManagedDependencies();
+ } catch (ArtifactDescriptorException e) {
+ throw new MojoExecutionException("Failed to read descriptor of " + bomArtifact, e);
+ }
+ if (deps.isEmpty()) {
+ getLog().warn("BOM " + bomArtifact + " does not include any dependency");
+ return;
+ }
+
+ /*
+ * final List requests = deps.stream()
+ * .map(d -> new ArtifactRequest()
+ * .setArtifact(d.getArtifact())
+ * .setRepositories(repos))
+ * .collect(Collectors.toList());
+ * final List resolvedDeps;
+ * try {
+ * resolvedDeps = repoSystem.resolveArtifacts(repoSession, requests);
+ * } catch (ArtifactResolutionException e) {
+ * throw new MojoExecutionException("Failed to resolve dependencies defined in " + bomArtifact, e);
+ * }
+ *
+ * for (ArtifactResult resolved : resolvedDeps) {
+ * if (!resolved.getArtifact().getExtension().equals("jar")) {
+ * continue;
+ * }
+ * processDependency(resolved.getArtifact().getFile().toPath());
+ * }
+ */
+
+ final JsonArrayBuilder extJsonBuilder = Json.createArrayBuilder();
+ for (Dependency dep : deps) {
+ if (!dep.getArtifact().getExtension().equals("jar")) {
+ continue;
+ }
+ ArtifactResult resolved = null;
+ try {
+ resolved = repoSystem.resolveArtifact(repoSession,
+ new ArtifactRequest().setRepositories(repos).setArtifact(dep.getArtifact()));
+ } catch (ArtifactResolutionException e) {
+ getLog().error("Failed to resolve dependency " + dep.getArtifact() + " defined in " + bomArtifact);
+ //throw new MojoExecutionException("Failed to resolve dependency " + dep.getArtifact() + " defined in " + bomArtifact, e);
+ }
+ if (resolved != null) {
+ processDependency(resolved.getArtifact(), extJsonBuilder);
+ }
+ }
+
+ final JsonWriterFactory jsonWriterFactory = Json
+ .createWriterFactory(Collections.singletonMap(JsonGenerator.PRETTY_PRINTING, true));
+ try (JsonWriter jsonWriter = jsonWriterFactory.createWriter(new FileOutputStream(outputFile))) {
+ jsonWriter.writeArray(extJsonBuilder.build());
+ } catch (IOException e) {
+ throw new MojoExecutionException("Failed to persist " + outputFile, e);
+ }
+ }
+
+ private void processDependency(Artifact artifact, JsonArrayBuilder extJsonBuilder) {
+ final Path path = artifact.getFile().toPath();
+ try {
+ if (Files.isDirectory(path)) {
+ processMetaInfDir(artifact, path.resolve(BootstrapConstants.META_INF), extJsonBuilder);
+ } else {
+ try (FileSystem artifactFs = ZipUtils.newFileSystem(path)) {
+ processMetaInfDir(artifact, artifactFs.getPath(BootstrapConstants.META_INF), extJsonBuilder);
+ }
+ }
+ } catch (Throwable t) {
+ throw new DeploymentInjectionException("Failed to inject extension deplpyment dependencies", t);
+ }
+ }
+
+ private boolean processMetaInfDir(Artifact artifact, Path metaInfDir, JsonArrayBuilder extJsonBuilder)
+ throws BootstrapDependencyProcessingException {
+ if (!Files.exists(metaInfDir)) {
+ return false;
+ }
+ final Path p = metaInfDir.resolve(BootstrapConstants.DESCRIPTOR_FILE_NAME);
+ if (!Files.exists(p)) {
+ return false;
+ }
+ processPlatformArtifact(artifact, p, extJsonBuilder);
+ return true;
+ }
+
+ private void processPlatformArtifact(Artifact artifact, Path descriptor, JsonArrayBuilder extJsonBuilder)
+ throws BootstrapDependencyProcessingException {
+ final Properties extProps = resolveDescriptor(descriptor);
+ if (extProps == null) {
+ return;
+ }
+ debug("Adding Quarkus extension %s", artifact);
+
+ final JsonObjectBuilder jsonExtBuilder = Json.createObjectBuilder()
+ .add(PROP_GROUP_ID, artifact.getGroupId())
+ .add(PROP_ARTIFACT_ID, artifact.getArtifactId());
+
+ addJsonProp(PROP_NAME, extProps, jsonExtBuilder);
+ addJsonProp(PROP_SHORT_NAME, extProps, jsonExtBuilder);
+ addJsonProp(PROP_GUIDE, extProps, jsonExtBuilder);
+
+ final String labelsStr = extProps.getProperty(PROP_LABELS);
+ if (labelsStr != null) {
+ final JsonArrayBuilder jsonArr = Json.createArrayBuilder();
+ for (String label : labelsStr.split("\\s*,\\s*")) {
+ jsonArr.add(label);
+ }
+ jsonExtBuilder.add(PROP_LABELS, jsonArr.build());
+ }
+
+ extJsonBuilder.add(jsonExtBuilder.build());
+ }
+
+ private static void addJsonProp(String name, Properties props, JsonObjectBuilder builder) {
+ final String value = props.getProperty(name);
+ if (value == null) {
+ return;
+ }
+ builder.add(name, value);
+ }
+
+ private Properties resolveDescriptor(final Path path) throws BootstrapDependencyProcessingException {
+ final Properties rtProps;
+ if (!Files.exists(path)) {
+ // not a platform artifact
+ return null;
+ }
+ rtProps = new Properties();
+ try (BufferedReader reader = Files.newBufferedReader(path)) {
+ rtProps.load(reader);
+ } catch (IOException e) {
+ throw new BootstrapDependencyProcessingException("Failed to load " + path, e);
+ }
+ return rtProps;
+ }
+
+ private void debug(String msg, Object... args) {
+ if (!getLog().isDebugEnabled()) {
+ return;
+ }
+ if (args.length == 0) {
+ getLog().debug(msg);
+ return;
+ }
+ getLog().debug(String.format(msg, args));
+ }
+}
diff --git a/extensions/arc/runtime/pom.xml b/extensions/arc/runtime/pom.xml
index ddaf16f354f076..996c845c680f66 100644
--- a/extensions/arc/runtime/pom.xml
+++ b/extensions/arc/runtime/pom.xml
@@ -33,6 +33,31 @@
io.quarkus
quarkus-bootstrap-maven-plugin
+
+
+ generate-extension-descriptor
+
+
+
+ name
+ Arc
+
+
+ shortName
+ CDI
+
+
+ guide
+ https://quarkus.io/guides/cdi-reference
+
+
+ labels
+ arc, cdi, dependency-injection, di
+
+
+
+
+
maven-compiler-plugin
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 e79fa676298bb2..fd103c9cd1e4f8 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
@@ -63,10 +63,14 @@ public class ExtensionDescriptorMojo extends AbstractMojo {
@Parameter(required = true)
private String deployment;
+ @Parameter(required = false)
+ private Properties properties = new Properties();
+
@Override
public void execute() throws MojoExecutionException {
final Properties props = new Properties();
+ props.putAll(properties);
props.setProperty(BootstrapConstants.PROP_DEPLOYMENT_ARTIFACT, deployment);
final Path output = outputDirectory.toPath().resolve(BootstrapConstants.META_INF);