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);