From a28bae6f870b6fe311b0714651c4feb987dab305 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 22 Aug 2024 17:02:05 +0200 Subject: [PATCH] Experiment with a plugin that generates Spring config metadata The plugin is installed at the deployment phase and assembles everything that is in the classpath. The metadata is augmented with some additional Quarkus metadata. There is still some improvements to make but it's a start. Also the big problem is that we will have to add this plugin for each deployment module in the tree. --- build-parent/pom.xml | 17 ++ .../pom.xml | 180 ++++++++++++++++ .../metadata/AttachDeploymentMetadata.java | 202 ++++++++++++++++++ ...arkusConfigAdditionalMetadataProperty.java | 24 +++ .../SpringConfigMetadataDeprecation.java | 5 + .../spring/SpringConfigMetadataGroup.java | 5 + .../spring/SpringConfigMetadataHint.java | 7 + .../spring/SpringConfigMetadataHintValue.java | 5 + .../spring/SpringConfigMetadataModel.java | 9 + .../spring/SpringConfigMetadataProperty.java | 6 + .../m2e/lifecycle-mapping-metadata.xml | 17 ++ .../resources/META-INF/plexus/components.xml | 20 ++ devtools/pom.xml | 1 + extensions/hibernate-orm/deployment/pom.xml | 4 + .../deployment/pom.xml | 4 + .../hibernate-validator/deployment/pom.xml | 4 + 16 files changed, 510 insertions(+) create mode 100644 devtools/extension-deployment-metadata-maven-plugin/pom.xml create mode 100644 devtools/extension-deployment-metadata-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/AttachDeploymentMetadata.java create mode 100644 devtools/extension-deployment-metadata-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/model/spring/QuarkusConfigAdditionalMetadataProperty.java create mode 100644 devtools/extension-deployment-metadata-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/model/spring/SpringConfigMetadataDeprecation.java create mode 100644 devtools/extension-deployment-metadata-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/model/spring/SpringConfigMetadataGroup.java create mode 100644 devtools/extension-deployment-metadata-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/model/spring/SpringConfigMetadataHint.java create mode 100644 devtools/extension-deployment-metadata-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/model/spring/SpringConfigMetadataHintValue.java create mode 100644 devtools/extension-deployment-metadata-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/model/spring/SpringConfigMetadataModel.java create mode 100644 devtools/extension-deployment-metadata-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/model/spring/SpringConfigMetadataProperty.java create mode 100644 devtools/extension-deployment-metadata-maven-plugin/src/main/resources/META-INF/m2e/lifecycle-mapping-metadata.xml create mode 100644 devtools/extension-deployment-metadata-maven-plugin/src/main/resources/META-INF/plexus/components.xml diff --git a/build-parent/pom.xml b/build-parent/pom.xml index 7fbd54ce270d3e..9e3d8f6918e640 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -398,6 +398,23 @@ maven-compiler-plugin + + io.quarkus + quarkus-extension-deployment-metadata-maven-plugin + ${project.version} + + + generate-config-doc + package + + attach-deployment-metadata + + + ${skipDocs} + + + + org.codehaus.mojo properties-maven-plugin diff --git a/devtools/extension-deployment-metadata-maven-plugin/pom.xml b/devtools/extension-deployment-metadata-maven-plugin/pom.xml new file mode 100644 index 00000000000000..eb86fe9f022acc --- /dev/null +++ b/devtools/extension-deployment-metadata-maven-plugin/pom.xml @@ -0,0 +1,180 @@ + + 4.0.0 + + io.quarkus + quarkus-devtools-all + 999-SNAPSHOT + + quarkus-extension-deployment-metadata-maven-plugin + Quarkus - Extension deployment metadata Maven plugin + maven-plugin + + 3.9.8 + + + + + + maven-compiler-plugin + + + maven-javadoc-plugin + + true + none + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + io.quarkus + quarkus-enforcer-rules + ${project.version} + + + + + enforce + ${maven-enforcer-plugin.phase} + + + + + classpath:enforcer-rules/quarkus-require-java-version.xml + + + classpath:enforcer-rules/quarkus-require-maven-version.xml + + + classpath:enforcer-rules/quarkus-banned-dependencies.xml + + + + + com.google.code.findbugs:jsr305 + + com.google.guava:listenablefuture + + + + + + enforce + + + + + + + + + org.eclipse.sisu + sisu-maven-plugin + 0.9.0.M3 + + + index-project + + main-index + test-index + + + + + + org.apache.maven.plugins + maven-plugin-plugin + + quarkus-config-doc + true + + + + help-goal + + helpmojo + + + + default-config-doc + generate-resources + + + + + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + javax.inject + javax.inject + + + org.apache.maven + maven-plugin-api + provided + + + org.apache.maven.plugin-tools + maven-plugin-annotations + + + org.apache.maven + maven-core + provided + + + org.apache.maven + maven-embedder + provided + + + org.apache.maven + maven-settings-builder + provided + + + org.apache.maven + maven-resolver-provider + provided + + + org.apache.maven + maven-model + provided + + + org.apache.maven + maven-model-builder + provided + + + org.apache.maven + maven-artifact + provided + + + org.apache.maven + maven-settings + provided + + + org.apache.maven + maven-builder-support + provided + + + org.apache.maven + maven-repository-metadata + provided + + + diff --git a/devtools/extension-deployment-metadata-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/AttachDeploymentMetadata.java b/devtools/extension-deployment-metadata-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/AttachDeploymentMetadata.java new file mode 100644 index 00000000000000..c3bc361913e1cf --- /dev/null +++ b/devtools/extension-deployment-metadata-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/AttachDeploymentMetadata.java @@ -0,0 +1,202 @@ +package io.quarkus.maven.extension.deployment.metadata; + +import java.io.File; +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.apache.maven.artifact.Artifact; +import org.apache.maven.execution.MavenSession; +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.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.plugins.annotations.ResolutionScope; +import org.apache.maven.project.MavenProject; +import org.apache.maven.project.MavenProjectHelper; + +import io.quarkus.annotation.processor.documentation.config.merger.JavadocMerger; +import io.quarkus.annotation.processor.documentation.config.merger.JavadocRepository; +import io.quarkus.annotation.processor.documentation.config.merger.MergedModel; +import io.quarkus.annotation.processor.documentation.config.merger.ModelMerger; +import io.quarkus.annotation.processor.documentation.config.model.AbstractConfigItem; +import io.quarkus.annotation.processor.documentation.config.model.ConfigItemVisitor; +import io.quarkus.annotation.processor.documentation.config.model.ConfigProperty; +import io.quarkus.annotation.processor.documentation.config.model.ConfigRoot; +import io.quarkus.annotation.processor.documentation.config.model.Extension; +import io.quarkus.annotation.processor.documentation.config.model.Extension.NameSource; +import io.quarkus.annotation.processor.documentation.config.model.JavadocElements.JavadocElement; +import io.quarkus.annotation.processor.documentation.config.util.JacksonMappers; +import io.quarkus.maven.extension.deployment.metadata.model.spring.QuarkusConfigAdditionalMetadataProperty; +import io.quarkus.maven.extension.deployment.metadata.model.spring.QuarkusConfigAdditionalMetadataProperty.ConfigPhase; +import io.quarkus.maven.extension.deployment.metadata.model.spring.SpringConfigMetadataDeprecation; +import io.quarkus.maven.extension.deployment.metadata.model.spring.SpringConfigMetadataGroup; +import io.quarkus.maven.extension.deployment.metadata.model.spring.SpringConfigMetadataHint; +import io.quarkus.maven.extension.deployment.metadata.model.spring.SpringConfigMetadataHintValue; +import io.quarkus.maven.extension.deployment.metadata.model.spring.SpringConfigMetadataModel; +import io.quarkus.maven.extension.deployment.metadata.model.spring.SpringConfigMetadataProperty; + +@Mojo(name = "attach-deployment-metadata", defaultPhase = LifecyclePhase.PACKAGE, threadSafe = true, requiresDependencyResolution = ResolutionScope.COMPILE) +public class AttachDeploymentMetadata extends AbstractMojo { + + private static final String META_INF = "META-INF"; + private static final Path OUTPUT_FILE = Path.of("config-metadata.json"); + + @Parameter(defaultValue = "${session}", readonly = true) + private MavenSession session; + + @Parameter(defaultValue = "${project}", readonly = true) + private MavenProject project; + + @Component + private MavenProjectHelper projectHelper; + + @Parameter(defaultValue = "${project.build.directory}", required = true, readonly = true) + protected File targetDirectory; + + @Parameter(defaultValue = "${project.build.outputDirectory}", required = true, readonly = true) + protected File outputDirectory; + + @Parameter(defaultValue = "false") + private boolean skip; + + private final List openZipFileSystems = new ArrayList<>(); + + @Override + public void execute() throws MojoExecutionException, MojoFailureException { + if (skip) { + return; + } + + try { + List metaInfDirectories = getMetaInfDirectories(); + + MergedModel mergedModel = ModelMerger.mergeModel(metaInfDirectories); + if (mergedModel.isEmpty()) { + return; + } + + Map configRoots = mergedModel.getConfigRoots().get(getExtension()); + if (configRoots == null || configRoots.isEmpty()) { + return; + } + + JavadocRepository javadocRepository = JavadocMerger.mergeJavadocElements(metaInfDirectories); + + // TODO: for now we don't generate the groups, we will see how it goes + List groups = new ArrayList<>(); + List properties = new ArrayList<>(); + List hints = new ArrayList<>(); + + for (ConfigRoot configRoot : configRoots.values()) { + configRoot.walk(new ConfigItemVisitor() { + + @Override + public void visit(AbstractConfigItem configItem) { + if (configItem instanceof ConfigProperty configProperty) { + // TODO: we will need to add more metadata on our side + SpringConfigMetadataDeprecation deprecation = configProperty.isDeprecated() + ? new SpringConfigMetadataDeprecation("warning", "", "", "") + : null; + + // TODO: be careful, this is asciidoc, we will discuss this further with the IDE teams + String description = javadocRepository + .getElement(configProperty.getSourceClass(), configProperty.getSourceName()) + .map(JavadocElement::description).orElse(null); + List hintValues = List.of(); + + ConfigPhase phase = ConfigPhase.of(configProperty.getPhase()); + + properties.add(new SpringConfigMetadataProperty(configProperty.getPath(), configProperty.getType(), + description, configProperty.getSourceClass(), configProperty.getDefaultValue(), + deprecation, new QuarkusConfigAdditionalMetadataProperty(phase, + configProperty.getEnvironmentVariable(), configProperty.isOptional()))); + if (configProperty.isEnum()) { + hintValues = configProperty.getEnumAcceptedValues().values().entrySet().stream() + .map(e -> new SpringConfigMetadataHintValue(e.getValue().configValue(), + javadocRepository.getElement(configProperty.getType(), e.getKey()) + .map(JavadocElement::description).orElse(null))) + .toList(); + hints.add(new SpringConfigMetadataHint(configProperty.getPath(), hintValues)); + } + + for (String additionalPath : configProperty.getAdditionalPaths()) { + // TODO, we will need to find a way to identify the environment variables + // for the additional paths + properties.add(new SpringConfigMetadataProperty(additionalPath, configProperty.getType(), + description, configProperty.getSourceClass(), configProperty.getDefaultValue(), + deprecation, new QuarkusConfigAdditionalMetadataProperty(phase, + null, configProperty.isOptional()))); + if (configProperty.isEnum()) { + hints.add(new SpringConfigMetadataHint(additionalPath, hintValues)); + } + } + } + } + }); + } + + SpringConfigMetadataModel springConfigMetadataModel = new SpringConfigMetadataModel(groups, properties, hints); + + Path springConfigMetadataModelPath = targetDirectory.toPath().resolve(OUTPUT_FILE); + JacksonMappers.jsonObjectWriter().writeValue(springConfigMetadataModelPath.toFile(), springConfigMetadataModel); + + // attach the metadata to the build + // note that we probably want to attach a zip/jar containing all sorts of metadata rather than a single file + projectHelper.attachArtifact(project, "json", "config-metadata", springConfigMetadataModelPath.toFile()); + } catch (Exception e) { + getLog().error("Unable to generate the metadata", e); + } finally { + for (FileSystem openZipFileSystem : openZipFileSystems) { + try { + openZipFileSystem.close(); + } catch (IOException e) { + getLog().error("Unable to close jar file system", e); + } + } + } + } + + private List getMetaInfDirectories() { + List classPathElements = new ArrayList<>(); + classPathElements.add(outputDirectory.toPath().resolve(META_INF)); + + for (Artifact dependency : project.getArtifacts()) { + if (!dependency.getArtifactHandler().isAddedToClasspath()) { + continue; + } + + Path artifactPath = dependency.getFile().toPath(); + + if (Files.isDirectory(artifactPath)) { + classPathElements.add(dependency.getFile().toPath().resolve(META_INF)); + } else { + // it's a jar + try { + FileSystem jarFs = FileSystems.newFileSystem(artifactPath); + openZipFileSystems.add(jarFs); + classPathElements.add(jarFs.getPath("/" + META_INF)); + } catch (IOException e) { + getLog().error("Unable to open jar: " + artifactPath, e); + } + } + } + + return Collections.unmodifiableList(classPathElements); + } + + private Extension getExtension() { + return new Extension(project.getGroupId(), + project.getArtifactId().substring(0, project.getArtifactId().length() - "-deployment".length()), null, + NameSource.NONE, true); + } +} diff --git a/devtools/extension-deployment-metadata-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/model/spring/QuarkusConfigAdditionalMetadataProperty.java b/devtools/extension-deployment-metadata-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/model/spring/QuarkusConfigAdditionalMetadataProperty.java new file mode 100644 index 00000000000000..78b33c5f816e7f --- /dev/null +++ b/devtools/extension-deployment-metadata-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/model/spring/QuarkusConfigAdditionalMetadataProperty.java @@ -0,0 +1,24 @@ +package io.quarkus.maven.extension.deployment.metadata.model.spring; + +public record QuarkusConfigAdditionalMetadataProperty(ConfigPhase phase, String environmentVariable, boolean optional) { + + public enum ConfigPhase { + RUN_TIME, + BUILD_TIME, + BUILD_AND_RUN_TIME_FIXED; + + public static ConfigPhase of(io.quarkus.annotation.processor.documentation.config.model.ConfigPhase phase) { + switch (phase) { + case BUILD_AND_RUN_TIME_FIXED: + return ConfigPhase.BUILD_AND_RUN_TIME_FIXED; + case BUILD_TIME: + return ConfigPhase.BUILD_TIME; + case RUN_TIME: + return ConfigPhase.RUN_TIME; + default: + throw new IllegalStateException( + "Phase " + phase + " not supported in " + ConfigPhase.class.getSimpleName()); + } + } + } +} diff --git a/devtools/extension-deployment-metadata-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/model/spring/SpringConfigMetadataDeprecation.java b/devtools/extension-deployment-metadata-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/model/spring/SpringConfigMetadataDeprecation.java new file mode 100644 index 00000000000000..26a21f32a1026b --- /dev/null +++ b/devtools/extension-deployment-metadata-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/model/spring/SpringConfigMetadataDeprecation.java @@ -0,0 +1,5 @@ +package io.quarkus.maven.extension.deployment.metadata.model.spring; + +public record SpringConfigMetadataDeprecation(String level, String reason, String replacement, String since) { + +} diff --git a/devtools/extension-deployment-metadata-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/model/spring/SpringConfigMetadataGroup.java b/devtools/extension-deployment-metadata-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/model/spring/SpringConfigMetadataGroup.java new file mode 100644 index 00000000000000..68c7947db2be3f --- /dev/null +++ b/devtools/extension-deployment-metadata-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/model/spring/SpringConfigMetadataGroup.java @@ -0,0 +1,5 @@ +package io.quarkus.maven.extension.deployment.metadata.model.spring; + +public record SpringConfigMetadataGroup(String name, String type, String description, String sourceType, String sourceMethod) { + +} diff --git a/devtools/extension-deployment-metadata-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/model/spring/SpringConfigMetadataHint.java b/devtools/extension-deployment-metadata-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/model/spring/SpringConfigMetadataHint.java new file mode 100644 index 00000000000000..bf0a5394466656 --- /dev/null +++ b/devtools/extension-deployment-metadata-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/model/spring/SpringConfigMetadataHint.java @@ -0,0 +1,7 @@ +package io.quarkus.maven.extension.deployment.metadata.model.spring; + +import java.util.List; + +public record SpringConfigMetadataHint(String name, List values) { + +} diff --git a/devtools/extension-deployment-metadata-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/model/spring/SpringConfigMetadataHintValue.java b/devtools/extension-deployment-metadata-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/model/spring/SpringConfigMetadataHintValue.java new file mode 100644 index 00000000000000..353787ae68794b --- /dev/null +++ b/devtools/extension-deployment-metadata-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/model/spring/SpringConfigMetadataHintValue.java @@ -0,0 +1,5 @@ +package io.quarkus.maven.extension.deployment.metadata.model.spring; + +public record SpringConfigMetadataHintValue(String value, String description) { + +} diff --git a/devtools/extension-deployment-metadata-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/model/spring/SpringConfigMetadataModel.java b/devtools/extension-deployment-metadata-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/model/spring/SpringConfigMetadataModel.java new file mode 100644 index 00000000000000..96d74ed4e0ad03 --- /dev/null +++ b/devtools/extension-deployment-metadata-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/model/spring/SpringConfigMetadataModel.java @@ -0,0 +1,9 @@ +package io.quarkus.maven.extension.deployment.metadata.model.spring; + +import java.util.List; + +public record SpringConfigMetadataModel(List groups, + List properties, + List hints) { + +} diff --git a/devtools/extension-deployment-metadata-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/model/spring/SpringConfigMetadataProperty.java b/devtools/extension-deployment-metadata-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/model/spring/SpringConfigMetadataProperty.java new file mode 100644 index 00000000000000..c8fa6b48fef3ce --- /dev/null +++ b/devtools/extension-deployment-metadata-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/model/spring/SpringConfigMetadataProperty.java @@ -0,0 +1,6 @@ +package io.quarkus.maven.extension.deployment.metadata.model.spring; + +public record SpringConfigMetadataProperty(String name, String type, String description, String sourceType, String defaultValue, + SpringConfigMetadataDeprecation deprecation, QuarkusConfigAdditionalMetadataProperty quarkus) { + +} diff --git a/devtools/extension-deployment-metadata-maven-plugin/src/main/resources/META-INF/m2e/lifecycle-mapping-metadata.xml b/devtools/extension-deployment-metadata-maven-plugin/src/main/resources/META-INF/m2e/lifecycle-mapping-metadata.xml new file mode 100644 index 00000000000000..89e04568801b03 --- /dev/null +++ b/devtools/extension-deployment-metadata-maven-plugin/src/main/resources/META-INF/m2e/lifecycle-mapping-metadata.xml @@ -0,0 +1,17 @@ + + + + + + attach-deployment-metadata + + + + + true + false + + + + + \ No newline at end of file diff --git a/devtools/extension-deployment-metadata-maven-plugin/src/main/resources/META-INF/plexus/components.xml b/devtools/extension-deployment-metadata-maven-plugin/src/main/resources/META-INF/plexus/components.xml new file mode 100644 index 00000000000000..d19a022a59fd65 --- /dev/null +++ b/devtools/extension-deployment-metadata-maven-plugin/src/main/resources/META-INF/plexus/components.xml @@ -0,0 +1,20 @@ + + + + org.apache.maven.lifecycle.Lifecycle + org.apache.maven.lifecycle.Lifecycle + quarkus-extension-deployment-metadata + + quarkus-config-doc + + my-plugin-not-used-phase + + + + io.quarkus:quarkus-extension-deployment-metadata-maven-plugin:${project.version}:attach-deployment-metadata + + + + + + diff --git a/devtools/pom.xml b/devtools/pom.xml index b1cd04e9e4a382..1377ddf889e565 100644 --- a/devtools/pom.xml +++ b/devtools/pom.xml @@ -27,5 +27,6 @@ gradle cli config-doc-maven-plugin + extension-deployment-metadata-maven-plugin diff --git a/extensions/hibernate-orm/deployment/pom.xml b/extensions/hibernate-orm/deployment/pom.xml index e17abfe0a53897..9aea83d99e4574 100644 --- a/extensions/hibernate-orm/deployment/pom.xml +++ b/extensions/hibernate-orm/deployment/pom.xml @@ -164,6 +164,10 @@ + + io.quarkus + quarkus-extension-deployment-metadata-maven-plugin + de.thetaphi forbiddenapis diff --git a/extensions/hibernate-search-orm-elasticsearch/deployment/pom.xml b/extensions/hibernate-search-orm-elasticsearch/deployment/pom.xml index 6862482c6010a1..caa2e7f9b0d14e 100644 --- a/extensions/hibernate-search-orm-elasticsearch/deployment/pom.xml +++ b/extensions/hibernate-search-orm-elasticsearch/deployment/pom.xml @@ -90,6 +90,10 @@ + + io.quarkus + quarkus-extension-deployment-metadata-maven-plugin + maven-surefire-plugin diff --git a/extensions/hibernate-validator/deployment/pom.xml b/extensions/hibernate-validator/deployment/pom.xml index 3b525bd111dbc1..da5bf42f0e902f 100644 --- a/extensions/hibernate-validator/deployment/pom.xml +++ b/extensions/hibernate-validator/deployment/pom.xml @@ -87,6 +87,10 @@ + + io.quarkus + quarkus-extension-deployment-metadata-maven-plugin +