ADMONITIONS = Map.of(
+ "CAUTION", "🔥",
+ "IMPORTANT", "❗",
+ "NOTE", "📌",
+ "TIP", "💡",
+ "WARNING", "⚠️");
+
+ private static final Pattern HEADER_PATTERN = Pattern.compile("^(=+) (.+)$");
+ private static final Pattern LIST_ITEM_PATTERN = Pattern.compile("^(\\*+|\\.+) (.+)$");
+ private static final Pattern IMAGE_BLOCK_PATTERN = Pattern.compile("^image::([^\\s]+)\\[(.*)\\]$");
+ private static final Pattern IMAGE_INLINE_PATTERN = Pattern.compile("image:([^\\s]+)\\[(.*)\\]");
+ private static final Pattern ADMONITION_BLOCK_START_PATTERN = Pattern
+ .compile("^\\[(" + String.join("|", ADMONITIONS.keySet()) + ")\\]$");
+ private static final String ADMONITION_BLOCK_DELIMITER = "====";
+ private static final Pattern ADMONITION_INLINE_PATTERN = Pattern
+ .compile("^(" + String.join("|", ADMONITIONS.keySet()) + "): (.*)$");
+ private static final Pattern BOLD_PATTERN = Pattern.compile("(?<=^|\\s)\\*(.+?)\\*(?=\\s|$)");
+ private static final Pattern ITALIC_PATTERN = Pattern.compile("__(.+?)__");
+ private static final Pattern BLOCK_TITLE_PATTERN = Pattern.compile("^\\.([a-z0-9].*)$");
+ private static final Pattern SOURCE_BLOCK_START_PATTERN = Pattern.compile("^\\[source(?:,[ ]*([a-z]+))?.*\\]$");
+ private static final Pattern SOURCE_BLOCK_DELIMITER_PATTERN = Pattern.compile("^(-----*)$");
+ private static final Pattern QUOTE_BLOCK_START_PATTERN = Pattern.compile("^\\[quote(?:, (.*?))?(?:, (.*?))?]$");
+ private static final Pattern QUOTE_BLOCK_DELIMITER_PATTERN = Pattern.compile("^(_____*)$");
+ private static final Pattern LINK_PATTERN = Pattern.compile("(?:link:)([^\\[]+)\\[(.*?)\\]");
+ private static final Pattern URL_PATTERN = Pattern.compile("\\b(http[^\\[]+)\\[(.*?)\\]");
+ private static final Pattern XREF_PATTERN = Pattern.compile("xref:([^\\[]+)\\[(.*?)\\]");
+ private static final Pattern ICON_PATTERN = Pattern.compile("\\bicon:([a-z0-9_-]+)\\[(?:role=([a-z0-9_-]+))?\\](?=\\s|$)");
+ private static final Pattern DESCRIPTION_LIST_PATTERN = Pattern.compile("^([a-z0-9][a-z0-9_ -]+)::(?=\\s|$)",
+ Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
+
public static String toMarkdown(String javadoc, JavadocFormat format) {
if (javadoc == null || javadoc.isBlank()) {
return null;
}
- if (format == JavadocFormat.MARKDOWN) {
- return javadoc;
- } else if (format == JavadocFormat.JAVADOC) {
- // the parser expects all the lines to start with "* "
- // we add it as it has been previously removed
- Javadoc parsedJavadoc = StaticJavaParser.parseJavadoc(START_OF_LINE.matcher(javadoc).replaceAll("* "));
+ switch (format) {
+ case MARKDOWN:
+ return javadoc;
+ case JAVADOC:
+ // the parser expects all the lines to start with "* "
+ // we add it as it has been previously removed
+ Javadoc parsedJavadoc = StaticJavaParser.parseJavadoc(START_OF_LINE.matcher(javadoc).replaceAll("* "));
- // HTML is valid Javadoc but we need to drop the Javadoc tags e.g. {@link ...}
- return simplifyJavadoc(parsedJavadoc.getDescription());
+ // HTML is valid Javadoc but we need to drop the Javadoc tags e.g. {@link ...}
+ return simplifyJavadoc(parsedJavadoc.getDescription());
+ case ASCIIDOC:
+ return asciidocToMarkdown(javadoc);
+ default:
+ throw new IllegalArgumentException("Converting from " + format + " to Markdown is not supported");
}
-
- // it's Asciidoc, the fun begins...
- return "";
}
/**
@@ -84,4 +122,285 @@ private static String escapeHtml(String s) {
}
return out.toString();
}
+
+ /**
+ * This obviously don't handle the whole complexity of Asciidoc but should handle most cases.
+ *
+ * One thing that might be worth adding is support for titles for source blocks and admonitions but we can add it later on.
+ *
+ * It doesn't support tables (yet).
+ */
+ private static String asciidocToMarkdown(String asciidoc) {
+ List lines = asciidoc.lines().toList();
+ List result = new ArrayList<>();
+ String currentAdmonition = null;
+ boolean inAdmonitionPreamble = false;
+ boolean inAdmonitionBlock = false;
+ String currentSourceBlockLanguage = null;
+ boolean inSourcePreamble = false;
+ boolean inSourceBlock = false;
+ String currentSourceBlockTitle = null;
+ String currentSourceBlockDelimiter = null;
+ boolean inQuoteBlock = false;
+ boolean quoteStarted = false;
+ String currentQuoteBlockDelimiter = null;
+ String quoteAuthor = null;
+ String quoteSource = null;
+
+ String linePrefix = "";
+
+ for (String line : lines) {
+ String markdownLine = line;
+
+ if (inAdmonitionPreamble) {
+ if (ADMONITION_BLOCK_DELIMITER.equals(line)) {
+ inAdmonitionBlock = true;
+ inAdmonitionPreamble = false;
+ result.add("> [!" + currentAdmonition + "]");
+ continue;
+ } else {
+ // we haven't found a proper delimiter so we ignore the admonition altogether
+ inAdmonitionPreamble = false;
+ }
+ }
+
+ if (inAdmonitionBlock) {
+ if (ADMONITION_BLOCK_DELIMITER.equals(line)) {
+ inAdmonitionBlock = false;
+ currentAdmonition = null;
+ linePrefix = "";
+ continue;
+ } else {
+ linePrefix = "> ";
+ }
+ }
+
+ if (inSourcePreamble) {
+ Matcher blockTitleMatcher = BLOCK_TITLE_PATTERN.matcher(line);
+ if (blockTitleMatcher.matches()) {
+ currentSourceBlockTitle = blockTitleMatcher.group(1);
+ }
+ }
+
+ if (inSourceBlock) {
+ if (currentSourceBlockDelimiter.equals(line)) {
+ // End of source block
+ result.add(linePrefix + "```");
+ inSourcePreamble = false;
+ inSourceBlock = false;
+ currentSourceBlockLanguage = null;
+ currentSourceBlockDelimiter = null;
+ currentSourceBlockTitle = null;
+ continue;
+ } else {
+ // Inside source block
+ result.add(linePrefix + markdownLine);
+ continue;
+ }
+ }
+
+ Matcher sourceBlockStartMatcher = SOURCE_BLOCK_START_PATTERN.matcher(line);
+ if (sourceBlockStartMatcher.matches()) {
+ if (!Strings.isBlank(sourceBlockStartMatcher.group(1))) {
+ currentSourceBlockLanguage = sourceBlockStartMatcher.group(1).trim();
+ }
+ inSourcePreamble = true;
+ // Skip the start marker
+ continue;
+ }
+
+ Matcher sourceBlockDelimiterMatcher = SOURCE_BLOCK_DELIMITER_PATTERN.matcher(line);
+ if (sourceBlockDelimiterMatcher.matches()) {
+ currentSourceBlockDelimiter = sourceBlockDelimiterMatcher.group(0);
+ // Start of code block
+ if (!Strings.isBlank(currentSourceBlockTitle)) {
+ result.add(linePrefix + "**" + currentSourceBlockTitle + "**");
+ result.add(linePrefix + "");
+ }
+ result.add(
+ linePrefix + "```" + (!Strings.isBlank(currentSourceBlockLanguage) ? currentSourceBlockLanguage : ""));
+ inSourcePreamble = false;
+ inSourceBlock = true;
+ continue;
+ }
+
+ if (inQuoteBlock) {
+ Matcher quoteBlockDelimiterMatcher = QUOTE_BLOCK_DELIMITER_PATTERN.matcher(line);
+ if (!quoteStarted && quoteBlockDelimiterMatcher.matches()) {
+ currentQuoteBlockDelimiter = quoteBlockDelimiterMatcher.group(0);
+ continue;
+ } else if (line.equals(currentQuoteBlockDelimiter)) {
+ // End of quote block
+ if (quoteAuthor != null || quoteSource != null) {
+ result.add(linePrefix + ">");
+ result.add(linePrefix + "> — " + (quoteAuthor != null ? quoteAuthor : "")
+ + (quoteSource != null ? ", " + quoteSource : ""));
+ }
+ inQuoteBlock = false;
+ quoteStarted = false;
+ currentQuoteBlockDelimiter = null;
+ continue;
+ } else {
+ // Inside quote block
+ result.add(linePrefix + "> " + line);
+ quoteStarted = true;
+ continue;
+ }
+ }
+
+ Matcher quoteBlockStartMatcher = QUOTE_BLOCK_START_PATTERN.matcher(line);
+ if (quoteBlockStartMatcher.matches()) {
+ // Start of quote block
+ quoteAuthor = quoteBlockStartMatcher.group(1);
+ quoteSource = quoteBlockStartMatcher.group(2);
+ inQuoteBlock = true;
+ continue;
+ }
+
+ Matcher admonitionBlockStartMatcher = ADMONITION_BLOCK_START_PATTERN.matcher(line);
+ if (admonitionBlockStartMatcher.matches()) {
+ currentAdmonition = admonitionBlockStartMatcher.group(1);
+ inAdmonitionPreamble = true;
+ // Skip the start marker
+ continue;
+ }
+
+ // Convert headings
+ Matcher headingMatcher = HEADER_PATTERN.matcher(line);
+ if (headingMatcher.find()) {
+ int level = headingMatcher.group(1).length();
+ String text = headingMatcher.group(2);
+ markdownLine = "#".repeat(level) + " " + text;
+ }
+
+ // Convert list items
+ Matcher listItemMatcher = LIST_ITEM_PATTERN.matcher(line);
+ if (listItemMatcher.find()) {
+ String marker = listItemMatcher.group(1);
+ String text = listItemMatcher.group(2);
+ if (marker.startsWith("*")) {
+ markdownLine = "- " + text;
+ } else if (marker.startsWith(".")) {
+ markdownLine = "1. " + text;
+ }
+ }
+
+ // Convert italic and bold
+ markdownLine = convertInline(markdownLine, ITALIC_PATTERN, "*");
+ markdownLine = convertInline(markdownLine, BOLD_PATTERN, "**");
+
+ // Inline Admonitions
+ if (!inAdmonitionBlock) {
+ Matcher admonitionInlineMatcher = ADMONITION_INLINE_PATTERN.matcher(line);
+ if (admonitionInlineMatcher.find()) {
+ String admonition = admonitionInlineMatcher.group(1);
+ if (ADMONITIONS.containsKey(admonition)) {
+ markdownLine = "> " + ADMONITIONS.get(admonition) + " " + admonitionInlineMatcher.group(2);
+ } else {
+ markdownLine = "> " + markdownLine;
+ }
+ }
+ }
+
+ // Convert block images
+ Matcher blockImageMatcher = IMAGE_BLOCK_PATTERN.matcher(line);
+ if (blockImageMatcher.find()) {
+ String target = blockImageMatcher.group(1);
+ String altText = blockImageMatcher.group(2);
+ markdownLine = "![" + altText + "](" + target + ")";
+ }
+
+ // Convert inline images
+ Matcher inlineImageMatcher = IMAGE_INLINE_PATTERN.matcher(line);
+ if (inlineImageMatcher.find()) {
+ String target = inlineImageMatcher.group(1);
+ String altText = inlineImageMatcher.group(2);
+ markdownLine = line.replace(inlineImageMatcher.group(), "![" + altText + "](" + target + ")");
+ }
+
+ // Convert links
+ markdownLine = convertLinksAndXrefs(markdownLine, LINK_PATTERN, "link");
+ // Convert direct URL links
+ markdownLine = convertLinksAndXrefs(markdownLine, URL_PATTERN, "url");
+ // Convert xrefs
+ markdownLine = convertLinksAndXrefs(markdownLine, XREF_PATTERN, "xref");
+
+ // Convert icons
+ markdownLine = convertIcons(markdownLine);
+
+ // Convert description lists: we only convert the title
+ markdownLine = convertDescriptionLists(markdownLine);
+
+ result.add(linePrefix + markdownLine);
+ }
+
+ return result.stream().collect(Collectors.joining("\n"));
+ }
+
+ private static String convertInline(String line, Pattern pattern, String markdownDelimiter) {
+ Matcher matcher = pattern.matcher(line);
+ StringBuffer sb = new StringBuffer();
+ while (matcher.find()) {
+ matcher.appendReplacement(sb, markdownDelimiter + matcher.group(1) + markdownDelimiter);
+ }
+ matcher.appendTail(sb);
+ return sb.toString();
+ }
+
+ private static String convertLinksAndXrefs(String line, Pattern pattern, String type) {
+ Matcher matcher = pattern.matcher(line);
+ StringBuffer sb = new StringBuffer();
+ while (matcher.find()) {
+ if (type.equals("link") || type.equals("url")) {
+ matcher.appendReplacement(sb, "[" + matcher.group(2) + "](" + matcher.group(1) + ")");
+ } else if (type.equals("xref")) {
+ String xref = matcher.group(1);
+ if (xref.contains(".adoc")) {
+ xref = "https://quarkus.io/guides/" + xref.replace(".adoc", "");
+ } else {
+ xref = "#" + xref;
+ }
+
+ matcher.appendReplacement(sb, "[" + matcher.group(2) + "](" + xref + ")");
+ }
+ }
+ matcher.appendTail(sb);
+ return sb.toString();
+ }
+
+ private static String convertIcons(String line) {
+ Matcher matcher = ICON_PATTERN.matcher(line);
+ StringBuffer sb = new StringBuffer();
+ while (matcher.find()) {
+ String icon = matcher.group(1);
+ String emoji;
+
+ switch (icon) {
+ case "check":
+ emoji = "✅";
+ break;
+ case "times":
+ emoji = "❌";
+ break;
+ default:
+ // TODO we probably need to collect the errors and log them instead
+ throw new IllegalArgumentException("Icon " + matcher.group(1) + " is not mapped.");
+ }
+
+ matcher.appendReplacement(sb, emoji);
+ }
+ matcher.appendTail(sb);
+ return sb.toString();
+ }
+
+ private static String convertDescriptionLists(String line) {
+ Matcher matcher = DESCRIPTION_LIST_PATTERN.matcher(line);
+ StringBuffer sb = new StringBuffer();
+ while (matcher.find()) {
+ String descriptionTitle = matcher.group(1);
+ matcher.appendReplacement(sb, "**" + descriptionTitle + "**");
+ }
+ matcher.appendTail(sb);
+ return sb.toString();
+ }
}
diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/merger/ModelMerger.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/merger/ModelMerger.java
index 4deff0ca7c3be..a101d7a8caf76 100644
--- a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/merger/ModelMerger.java
+++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/merger/ModelMerger.java
@@ -43,12 +43,29 @@ public static MergedModel mergeModel(List buildOutputDirectories) {
* target/ directories found in the parent directory scanned).
*/
public static MergedModel mergeModel(JavadocRepository javadocRepository, List buildOutputDirectories) {
+ return mergeModel(javadocRepository, buildOutputDirectories, false);
+ }
+
+ /**
+ * Merge all the resolved models obtained from a list of build output directories (e.g. in the case of Maven, the list of
+ * target/ directories found in the parent directory scanned).
+ */
+ public static MergedModel mergeModel(List buildOutputDirectories, boolean mergeCommonOrInternalExtensions) {
+ return mergeModel(null, buildOutputDirectories, mergeCommonOrInternalExtensions);
+ }
+
+ /**
+ * Merge all the resolved models obtained from a list of build output directories (e.g. in the case of Maven, the list of
+ * target/ directories found in the parent directory scanned).
+ */
+ public static MergedModel mergeModel(JavadocRepository javadocRepository, List buildOutputDirectories,
+ boolean mergeCommonOrInternalExtensions) {
// keyed on extension and then top level prefix
Map> configRoots = new HashMap<>();
// keyed on file name
Map configRootsInSpecificFile = new TreeMap<>();
// keyed on extension
- Map> generatedConfigSections = new TreeMap<>();
+ Map> generatedConfigSections = new HashMap<>();
for (Path buildOutputDirectory : buildOutputDirectories) {
Path resolvedModelPath = buildOutputDirectory.resolve(Outputs.QUARKUS_CONFIG_DOC_MODEL);
@@ -85,7 +102,8 @@ public static MergedModel mergeModel(JavadocRepository javadocRepository, List extensionConfigRoots = configRoots.computeIfAbsent(configRoot.getExtension(),
+ Map extensionConfigRoots = configRoots.computeIfAbsent(
+ normalizeExtension(configRoot.getExtension(), mergeCommonOrInternalExtensions),
e -> new TreeMap<>());
ConfigRootKey configRootKey = getConfigRootKey(javadocRepository, configRoot);
@@ -102,6 +120,7 @@ public static MergedModel mergeModel(JavadocRepository javadocRepository, List> extensionConfigRootsEntry : configRoots.entrySet()) {
@@ -116,6 +135,18 @@ public static MergedModel mergeModel(JavadocRepository javadocRepository, List
> retainBestExtensionKey(
Map> configRoots) {
return configRoots.entrySet().stream().collect(Collectors.toMap(e -> {
diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/AbstractConfigItem.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/AbstractConfigItem.java
index 643ed96d28620..a454e64e58647 100644
--- a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/AbstractConfigItem.java
+++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/AbstractConfigItem.java
@@ -7,35 +7,36 @@
public sealed abstract class AbstractConfigItem implements Comparable
permits ConfigProperty, ConfigSection {
- protected final String sourceClass;
- protected final String sourceName;
- protected final SourceType sourceType;
+ protected final String sourceType;
+ protected final String sourceElementName;
+ protected final SourceElementType sourceElementType;
protected final Path path;
protected final String type;
protected Deprecation deprecation;
- public AbstractConfigItem(String sourceClass, String sourceName, SourceType sourceType, Path path, String type,
+ public AbstractConfigItem(String sourceType, String sourceElementName, SourceElementType sourceElementType, Path path,
+ String type,
Deprecation deprecation) {
- this.sourceClass = sourceClass;
- this.sourceName = sourceName;
this.sourceType = sourceType;
+ this.sourceElementName = sourceElementName;
+ this.sourceElementType = sourceElementType;
this.path = path;
this.type = type;
this.deprecation = deprecation;
}
- public String getSourceClass() {
- return sourceClass;
+ public String getSourceType() {
+ return sourceType;
}
- public String getSourceName() {
- return sourceName;
+ public String getSourceElementName() {
+ return sourceElementName;
}
- public SourceType getSourceType() {
- return sourceType;
+ public SourceElementType getSourceElementType() {
+ return sourceElementType;
}
public Path getPath() {
diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/ConfigProperty.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/ConfigProperty.java
index 1be3576d4e27d..ab49c16be665a 100644
--- a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/ConfigProperty.java
+++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/ConfigProperty.java
@@ -29,14 +29,14 @@ public final class ConfigProperty extends AbstractConfigItem {
private final String javadocSiteLink;
- public ConfigProperty(ConfigPhase phase, String sourceClass, String sourceName, SourceType sourceType, PropertyPath path,
- List additionalPaths, String type, String typeDescription, boolean map, boolean list,
- boolean optional,
+ public ConfigProperty(ConfigPhase phase, String sourceType, String sourceElementName, SourceElementType sourceElementType,
+ PropertyPath path, List additionalPaths, String type, String typeDescription, boolean map,
+ boolean list, boolean optional,
String mapKey, boolean unnamedMapKey, boolean withinMap, boolean converted, @JsonProperty("enum") boolean isEnum,
EnumAcceptedValues enumAcceptedValues,
String defaultValue, String javadocSiteLink,
Deprecation deprecation) {
- super(sourceClass, sourceName, sourceType, path, type, deprecation);
+ super(sourceType, sourceElementName, sourceElementType, path, type, deprecation);
this.phase = phase;
this.additionalPaths = additionalPaths != null ? Collections.unmodifiableList(additionalPaths) : List.of();
this.typeDescription = typeDescription;
diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/ConfigSection.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/ConfigSection.java
index da33958996e84..4b0774ef6ac1c 100644
--- a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/ConfigSection.java
+++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/ConfigSection.java
@@ -11,9 +11,9 @@ public final class ConfigSection extends AbstractConfigItem implements ConfigIte
private final List items = new ArrayList<>();
private final int level;
- public ConfigSection(String sourceClass, String sourceName, SourceType sourceType, SectionPath path, String type, int level,
- boolean generated, Deprecation deprecation) {
- super(sourceClass, sourceName, sourceType, path, type, deprecation);
+ public ConfigSection(String sourceType, String sourceElementName, SourceElementType sourceElementType, SectionPath path,
+ String type, int level, boolean generated, Deprecation deprecation) {
+ super(sourceType, sourceElementName, sourceElementType, path, type, deprecation);
this.generated = generated;
this.level = level;
}
diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/Extension.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/Extension.java
index fcb44039f24de..8472659071881 100644
--- a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/Extension.java
+++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/Extension.java
@@ -5,10 +5,24 @@
import com.fasterxml.jackson.annotation.JsonIgnore;
public record Extension(String groupId, String artifactId, String name,
- NameSource nameSource, boolean detected) implements Comparable {
+ NameSource nameSource, boolean commonOrInternal, boolean detected) implements Comparable {
+
+ private static final String ARTIFACT_COMMON_SUFFIX = "-common";
+ private static final String ARTIFACT_INTERNAL_SUFFIX = "-internal";
+
+ public static Extension of(String groupId, String artifactId, String name,
+ NameSource nameSource) {
+ boolean commonOrInternal = artifactId.endsWith(ARTIFACT_COMMON_SUFFIX) || artifactId.endsWith(ARTIFACT_INTERNAL_SUFFIX);
+ if (commonOrInternal) {
+ nameSource = nameSource == NameSource.EXTENSION_METADATA ? NameSource.EXTENSION_METADATA_COMMON_INTERNAL
+ : (nameSource == NameSource.POM_XML ? NameSource.POM_XML_COMMON_INTERNAL : nameSource);
+ }
+
+ return new Extension(groupId, artifactId, name, nameSource, commonOrInternal, true);
+ }
public static Extension createNotDetected() {
- return new Extension("not.detected", "not.detected", "Not detected", NameSource.NONE, false);
+ return new Extension("not.detected", "not.detected", "Not detected", NameSource.NONE, false, false);
}
@Override
@@ -50,6 +64,27 @@ public boolean splitOnConfigRootDescription() {
return "io.quarkus".equals(groupId) && "quarkus-core".equals(artifactId);
}
+ @JsonIgnore
+ public Extension normalizeCommonOrInternal() {
+ if (!commonOrInternal()) {
+ return this;
+ }
+
+ String normalizedArtifactId = artifactId;
+ if (artifactId.endsWith(ARTIFACT_COMMON_SUFFIX)) {
+ normalizedArtifactId = artifactId.substring(0, artifactId.length() - ARTIFACT_COMMON_SUFFIX.length());
+ }
+ if (artifactId.endsWith(ARTIFACT_INTERNAL_SUFFIX)) {
+ normalizedArtifactId = artifactId.substring(0, artifactId.length() - ARTIFACT_INTERNAL_SUFFIX.length());
+ }
+
+ if (normalizedArtifactId.equals(artifactId)) {
+ return this;
+ }
+
+ return new Extension(groupId, normalizedArtifactId, name, nameSource, commonOrInternal, detected);
+ }
+
@Override
public int compareTo(Extension other) {
if (name != null && other.name != null) {
diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/SourceType.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/SourceElementType.java
similarity index 74%
rename from core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/SourceType.java
rename to core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/SourceElementType.java
index 6da161b75714e..8d3fc0d573038 100644
--- a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/SourceType.java
+++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/model/SourceElementType.java
@@ -1,6 +1,6 @@
package io.quarkus.annotation.processor.documentation.config.model;
-public enum SourceType {
+public enum SourceElementType {
METHOD,
FIELD;
diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/resolver/ConfigResolver.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/resolver/ConfigResolver.java
index d6fa963d6dd12..34131fcf17a6c 100644
--- a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/resolver/ConfigResolver.java
+++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/resolver/ConfigResolver.java
@@ -134,8 +134,8 @@ private void resolveProperty(ConfigRoot configRoot, Map e
if (configSection != null) {
configSection.appendState(discoveryConfigProperty.isSectionGenerated(), deprecation);
} else {
- configSection = new ConfigSection(discoveryConfigProperty.getSourceClass(),
- discoveryConfigProperty.getSourceName(), discoveryConfigProperty.getSourceType(),
+ configSection = new ConfigSection(discoveryConfigProperty.getSourceType(),
+ discoveryConfigProperty.getSourceElementName(), discoveryConfigProperty.getSourceElementType(),
new SectionPath(path), typeQualifiedName,
context.getSectionLevel(), discoveryConfigProperty.isSectionGenerated(), deprecation);
context.getItemCollection().addItem(configSection);
@@ -202,9 +202,9 @@ private void resolveProperty(ConfigRoot configRoot, Map e
// this is a standard property
ConfigProperty configProperty = new ConfigProperty(phase,
- discoveryConfigProperty.getSourceClass(),
- discoveryConfigProperty.getSourceName(),
discoveryConfigProperty.getSourceType(),
+ discoveryConfigProperty.getSourceElementName(),
+ discoveryConfigProperty.getSourceElementType(),
propertyPath, additionalPropertyPaths,
typeQualifiedName, typeSimplifiedName,
discoveryConfigProperty.getType().isMap(), discoveryConfigProperty.getType().isList(),
diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/AbstractConfigListener.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/AbstractConfigListener.java
index 4ab20e4ee24e3..4eb92d7e84277 100644
--- a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/AbstractConfigListener.java
+++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/AbstractConfigListener.java
@@ -67,7 +67,7 @@ public void onResolvedEnum(TypeElement enumTypeElement) {
}
protected void handleCommonPropertyAnnotations(DiscoveryConfigProperty.Builder builder,
- Map propertyAnnotations, ResolvedType resolvedType, String sourceName) {
+ Map propertyAnnotations, ResolvedType resolvedType, String sourceElementName) {
AnnotationMirror deprecatedAnnotation = propertyAnnotations.get(Deprecated.class.getName());
if (deprecatedAnnotation != null) {
diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/ConfigMappingListener.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/ConfigMappingListener.java
index 1847c8e203503..cda99142c602e 100644
--- a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/ConfigMappingListener.java
+++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/ConfigMappingListener.java
@@ -15,7 +15,7 @@
import io.quarkus.annotation.processor.documentation.config.discovery.DiscoveryRootElement;
import io.quarkus.annotation.processor.documentation.config.discovery.ResolvedType;
import io.quarkus.annotation.processor.documentation.config.model.ConfigPhase;
-import io.quarkus.annotation.processor.documentation.config.model.SourceType;
+import io.quarkus.annotation.processor.documentation.config.model.SourceElementType;
import io.quarkus.annotation.processor.documentation.config.util.ConfigNamingUtil;
import io.quarkus.annotation.processor.documentation.config.util.Markers;
import io.quarkus.annotation.processor.documentation.config.util.Types;
@@ -125,11 +125,11 @@ public void onEnclosedMethod(DiscoveryRootElement discoveryRootElement, TypeElem
Map methodAnnotations = utils.element().getAnnotations(method);
- String sourceName = method.getSimpleName().toString();
+ String sourceElementName = method.getSimpleName().toString();
DiscoveryConfigProperty.Builder builder = DiscoveryConfigProperty.builder(clazz.getQualifiedName().toString(),
- sourceName, SourceType.METHOD, resolvedType);
+ sourceElementName, SourceElementType.METHOD, resolvedType);
- String name = ConfigNamingUtil.hyphenate(sourceName);
+ String name = ConfigNamingUtil.hyphenate(sourceElementName);
AnnotationMirror withNameAnnotation = methodAnnotations.get(Types.ANNOTATION_CONFIG_WITH_NAME);
if (withNameAnnotation != null) {
name = withNameAnnotation.getElementValues().values().iterator().next().getValue().toString();
@@ -152,7 +152,7 @@ public void onEnclosedMethod(DiscoveryRootElement discoveryRootElement, TypeElem
}
if (resolvedType.isMap()) {
- String mapKey = ConfigNamingUtil.hyphenate(sourceName);
+ String mapKey = ConfigNamingUtil.hyphenate(sourceElementName);
AnnotationMirror configDocMapKeyAnnotation = methodAnnotations.get(Types.ANNOTATION_CONFIG_DOC_MAP_KEY);
if (configDocMapKeyAnnotation != null) {
mapKey = configDocMapKeyAnnotation.getElementValues().values().iterator().next().getValue().toString();
@@ -169,7 +169,7 @@ public void onEnclosedMethod(DiscoveryRootElement discoveryRootElement, TypeElem
builder.converted();
}
- handleCommonPropertyAnnotations(builder, methodAnnotations, resolvedType, sourceName);
+ handleCommonPropertyAnnotations(builder, methodAnnotations, resolvedType, sourceElementName);
discoveryRootElement.addProperty(builder.build());
}
diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/LegacyConfigRootListener.java b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/LegacyConfigRootListener.java
index 6a1ca922d3da3..2410b8c62b9d6 100644
--- a/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/LegacyConfigRootListener.java
+++ b/core/processor/src/main/java/io/quarkus/annotation/processor/documentation/config/scanner/LegacyConfigRootListener.java
@@ -16,7 +16,7 @@
import io.quarkus.annotation.processor.documentation.config.discovery.DiscoveryRootElement;
import io.quarkus.annotation.processor.documentation.config.discovery.ResolvedType;
import io.quarkus.annotation.processor.documentation.config.model.ConfigPhase;
-import io.quarkus.annotation.processor.documentation.config.model.SourceType;
+import io.quarkus.annotation.processor.documentation.config.model.SourceElementType;
import io.quarkus.annotation.processor.documentation.config.util.ConfigNamingUtil;
import io.quarkus.annotation.processor.documentation.config.util.Markers;
import io.quarkus.annotation.processor.documentation.config.util.Types;
@@ -123,11 +123,11 @@ public void onEnclosedField(DiscoveryRootElement discoveryRootElement, TypeEleme
Map fieldAnnotations = utils.element().getAnnotations(field);
- String sourceName = field.getSimpleName().toString();
- String name = ConfigNamingUtil.hyphenate(sourceName);
+ String sourceElementName = field.getSimpleName().toString();
+ String name = ConfigNamingUtil.hyphenate(sourceElementName);
DiscoveryConfigProperty.Builder builder = DiscoveryConfigProperty.builder(clazz.getQualifiedName().toString(),
- sourceName, SourceType.FIELD, resolvedType);
+ sourceElementName, SourceElementType.FIELD, resolvedType);
AnnotationMirror configItemAnnotation = fieldAnnotations.get(Types.ANNOTATION_CONFIG_ITEM);
if (configItemAnnotation != null) {
@@ -158,7 +158,7 @@ public void onEnclosedField(DiscoveryRootElement discoveryRootElement, TypeEleme
builder.name(name);
if (resolvedType.isMap()) {
- String mapKey = ConfigNamingUtil.hyphenate(sourceName);
+ String mapKey = ConfigNamingUtil.hyphenate(sourceElementName);
AnnotationMirror configDocMapKeyAnnotation = fieldAnnotations.get(Types.ANNOTATION_CONFIG_DOC_MAP_KEY);
if (configDocMapKeyAnnotation != null) {
mapKey = configDocMapKeyAnnotation.getElementValues().values().iterator().next().getValue().toString();
@@ -171,7 +171,7 @@ public void onEnclosedField(DiscoveryRootElement discoveryRootElement, TypeEleme
builder.converted();
}
- handleCommonPropertyAnnotations(builder, fieldAnnotations, resolvedType, sourceName);
+ handleCommonPropertyAnnotations(builder, fieldAnnotations, resolvedType, sourceElementName);
discoveryRootElement.addProperty(builder.build());
}
diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/util/ExtensionUtil.java b/core/processor/src/main/java/io/quarkus/annotation/processor/util/ExtensionUtil.java
index 6634c70469e59..f30eca643686e 100644
--- a/core/processor/src/main/java/io/quarkus/annotation/processor/util/ExtensionUtil.java
+++ b/core/processor/src/main/java/io/quarkus/annotation/processor/util/ExtensionUtil.java
@@ -1,11 +1,15 @@
package io.quarkus.annotation.processor.util;
+import java.io.IOException;
+import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.Map;
import java.util.Optional;
import javax.annotation.processing.ProcessingEnvironment;
import javax.tools.Diagnostic.Kind;
+import javax.tools.StandardLocation;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
@@ -19,14 +23,12 @@
public final class ExtensionUtil {
+ private static final String RUNTIME_MARKER_FILE = "META-INF/quarkus.properties";
+
private static final String ARTIFACT_DEPLOYMENT_SUFFIX = "-deployment";
- private static final String ARTIFACT_COMMON_SUFFIX = "-common";
- private static final String ARTIFACT_INTERNAL_SUFFIX = "-internal";
private static final String NAME_QUARKUS_PREFIX = "Quarkus - ";
private static final String NAME_RUNTIME_SUFFIX = " - Runtime";
private static final String NAME_DEPLOYMENT_SUFFIX = " - Deployment";
- private static final String NAME_COMMON_SUFFIX = " - Common";
- private static final String NAME_INTERNAL_SUFFIX = " - Internal";
private final ProcessingEnvironment processingEnv;
private final FilerUtil filerUtil;
@@ -111,27 +113,19 @@ private Extension getExtensionFromPom(Path pom, Document doc) {
return Extension.createNotDetected();
}
- boolean commonOrInternal = false;
+ boolean runtime = isRuntime();
- if (artifactId.endsWith(ARTIFACT_DEPLOYMENT_SUFFIX)) {
+ if (!runtime && artifactId.endsWith(ARTIFACT_DEPLOYMENT_SUFFIX)) {
artifactId = artifactId.substring(0, artifactId.length() - ARTIFACT_DEPLOYMENT_SUFFIX.length());
}
- if (artifactId.endsWith(ARTIFACT_COMMON_SUFFIX)) {
- artifactId = artifactId.substring(0, artifactId.length() - ARTIFACT_COMMON_SUFFIX.length());
- commonOrInternal = true;
- }
- if (artifactId.endsWith(ARTIFACT_INTERNAL_SUFFIX)) {
- artifactId = artifactId.substring(0, artifactId.length() - ARTIFACT_INTERNAL_SUFFIX.length());
- commonOrInternal = true;
- }
NameSource nameSource;
Optional nameFromExtensionMetadata = getExtensionNameFromExtensionMetadata();
if (nameFromExtensionMetadata.isPresent()) {
name = nameFromExtensionMetadata.get();
- nameSource = commonOrInternal ? NameSource.EXTENSION_METADATA_COMMON_INTERNAL : NameSource.EXTENSION_METADATA;
+ nameSource = NameSource.EXTENSION_METADATA;
} else if (name != null) {
- nameSource = commonOrInternal ? NameSource.POM_XML_COMMON_INTERNAL : NameSource.POM_XML;
+ nameSource = NameSource.POM_XML;
} else {
nameSource = NameSource.NONE;
}
@@ -140,18 +134,15 @@ private Extension getExtensionFromPom(Path pom, Document doc) {
if (name.startsWith(NAME_QUARKUS_PREFIX)) {
name = name.substring(NAME_QUARKUS_PREFIX.length()).trim();
}
- if (name.endsWith(NAME_DEPLOYMENT_SUFFIX)) {
+ if (!runtime && name.endsWith(NAME_DEPLOYMENT_SUFFIX)) {
name = name.substring(0, name.length() - NAME_DEPLOYMENT_SUFFIX.length());
- } else if (name.endsWith(NAME_RUNTIME_SUFFIX)) {
+ }
+ if (runtime && name.endsWith(NAME_RUNTIME_SUFFIX)) {
name = name.substring(0, name.length() - NAME_RUNTIME_SUFFIX.length());
- } else if (name.endsWith(NAME_COMMON_SUFFIX)) {
- name = name.substring(0, name.length() - NAME_COMMON_SUFFIX.length());
- } else if (name.endsWith(NAME_INTERNAL_SUFFIX)) {
- name = name.substring(0, name.length() - NAME_INTERNAL_SUFFIX.length());
}
}
- return new Extension(groupId, artifactId, name, nameSource, true);
+ return Extension.of(groupId, artifactId, name, nameSource);
}
private Optional getExtensionNameFromExtensionMetadata() {
@@ -168,4 +159,14 @@ private Optional getExtensionNameFromExtensionMetadata() {
return Optional.of(extensionName.trim());
}
+
+ private boolean isRuntime() {
+ try {
+ Path runtimeMarkerFile = Paths
+ .get(processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, "", RUNTIME_MARKER_FILE).toUri());
+ return Files.exists(runtimeMarkerFile);
+ } catch (IOException e) {
+ return false;
+ }
+ }
}
diff --git a/core/processor/src/test/java/io/quarkus/annotation/processor/documentation/config/formatter/AsciidocToMarkdownTransformerTest.java b/core/processor/src/test/java/io/quarkus/annotation/processor/documentation/config/formatter/AsciidocToMarkdownTransformerTest.java
new file mode 100644
index 0000000000000..f0ab9d73c7013
--- /dev/null
+++ b/core/processor/src/test/java/io/quarkus/annotation/processor/documentation/config/formatter/AsciidocToMarkdownTransformerTest.java
@@ -0,0 +1,200 @@
+package io.quarkus.annotation.processor.documentation.config.formatter;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+import io.quarkus.annotation.processor.documentation.config.model.JavadocFormat;
+
+public class AsciidocToMarkdownTransformerTest {
+
+ @Test
+ public void testSourceBlocks() {
+ String asciidoc = """
+ [source,java]
+ ----
+ System.out.println("Hello, World!");
+ ----""";
+ String expectedMarkdown = """
+ ```java
+ System.out.println("Hello, World!");
+ ```""";
+
+ assertEquals(expectedMarkdown, JavadocToMarkdownTransformer.toMarkdown(asciidoc, JavadocFormat.ASCIIDOC));
+ }
+
+ @Test
+ public void testQuoteBlocks() {
+ String asciidoc = """
+ [quote, John Doe]
+ ____
+ This is a quote.
+ ____""";
+ String expectedMarkdown = """
+ > This is a quote.
+ >
+ > — John Doe""";
+
+ assertEquals(expectedMarkdown, JavadocToMarkdownTransformer.toMarkdown(asciidoc, JavadocFormat.ASCIIDOC));
+ }
+
+ @Test
+ public void testAdmonitions() {
+ String asciidoc = "NOTE: This is a note.";
+ String expectedMarkdown = "> 📌 This is a note.";
+
+ assertEquals(expectedMarkdown, JavadocToMarkdownTransformer.toMarkdown(asciidoc, JavadocFormat.ASCIIDOC));
+ }
+
+ @Test
+ public void testAdmonitionBlock() {
+ String asciidoc = """
+ [NOTE]
+ ====
+ This is an admonition block.
+ ====""";
+ String expectedMarkdown = """
+ > [!NOTE]
+ > This is an admonition block.""";
+
+ assertEquals(expectedMarkdown, JavadocToMarkdownTransformer.toMarkdown(asciidoc, JavadocFormat.ASCIIDOC));
+ }
+
+ @Test
+ public void testQuotes() {
+ String asciidoc = "_This is italic_, *This is bold*";
+ String expectedMarkdown = "_This is italic_, **This is bold**";
+
+ assertEquals(expectedMarkdown, JavadocToMarkdownTransformer.toMarkdown(asciidoc, JavadocFormat.ASCIIDOC));
+ }
+
+ @Test
+ public void testLists() {
+ String asciidoc = """
+ * Item 1
+ * Item 2
+ * Item 3""";
+ String expectedMarkdown = """
+ - Item 1
+ - Item 2
+ - Item 3""";
+
+ assertEquals(expectedMarkdown, JavadocToMarkdownTransformer.toMarkdown(asciidoc, JavadocFormat.ASCIIDOC));
+ }
+
+ @Test
+ public void testLinks() {
+ String asciidoc = "https://example.com[Example]";
+ String expectedMarkdown = "[Example](https://example.com)";
+
+ assertEquals(expectedMarkdown, JavadocToMarkdownTransformer.toMarkdown(asciidoc, JavadocFormat.ASCIIDOC));
+
+ asciidoc = "link:https://example.com[Example]";
+ expectedMarkdown = "[Example](https://example.com)";
+
+ assertEquals(expectedMarkdown, JavadocToMarkdownTransformer.toMarkdown(asciidoc, JavadocFormat.ASCIIDOC));
+
+ // TODO render the attributes
+ asciidoc = "link:{hibernate-search-docs-url}#backend-elasticsearch-configuration-index-settings[this section of the reference documentation]";
+ expectedMarkdown = "[this section of the reference documentation]({hibernate-search-docs-url}#backend-elasticsearch-configuration-index-settings)";
+ assertEquals(expectedMarkdown, JavadocToMarkdownTransformer.toMarkdown(asciidoc, JavadocFormat.ASCIIDOC));
+ }
+
+ @Test
+ public void testXrefs() {
+ String asciidoc = "xref:example.adoc[Example]";
+ String expectedMarkdown = "[Example](https://quarkus.io/guides/example)";
+
+ assertEquals(expectedMarkdown, JavadocToMarkdownTransformer.toMarkdown(asciidoc, JavadocFormat.ASCIIDOC));
+
+ asciidoc = "xref:example.adoc#test[Example]";
+ expectedMarkdown = "[Example](https://quarkus.io/guides/example#test)";
+
+ assertEquals(expectedMarkdown, JavadocToMarkdownTransformer.toMarkdown(asciidoc, JavadocFormat.ASCIIDOC));
+
+ asciidoc = "xref:test[Example]";
+ expectedMarkdown = "[Example](#test)";
+
+ assertEquals(expectedMarkdown, JavadocToMarkdownTransformer.toMarkdown(asciidoc, JavadocFormat.ASCIIDOC));
+ }
+
+ @Test
+ public void testComposite() {
+ String asciidoc = """
+ Here is some regular text.
+
+ *This is bold text* and visit https://example.com[Example] for more info.
+
+ [source,java]
+ ----
+ System.out.println("Hello, World!");
+ ----
+
+ A quote block:
+ [quote, John Doe]
+ ____
+ This is a quote.
+ ____
+
+ A list:
+ * Item 1
+ * Item 2
+ * Item 3""";
+ String expectedMarkdown = """
+ Here is some regular text.
+
+ **This is bold text** and visit [Example](https://example.com) for more info.
+
+ ```java
+ System.out.println("Hello, World!");
+ ```
+
+ A quote block:
+ > This is a quote.
+ >
+ > — John Doe
+
+ A list:
+ - Item 1
+ - Item 2
+ - Item 3""";
+
+ assertEquals(expectedMarkdown, JavadocToMarkdownTransformer.toMarkdown(asciidoc, JavadocFormat.ASCIIDOC));
+ }
+
+ @Test
+ public void testIcons() {
+ String asciidoc = "This looks right with role: icon:check[role=lime] and without role: icon:check[] and this looks bad with role: icon:times[role=red] and without role: icon:times[]";
+ String expectedMarkdown = "This looks right with role: ✅ and without role: ✅ and this looks bad with role: ❌ and without role: ❌";
+
+ assertEquals(expectedMarkdown, JavadocToMarkdownTransformer.toMarkdown(asciidoc, JavadocFormat.ASCIIDOC));
+ }
+
+ @Test
+ public void testDescriptionLists() {
+ String asciidoc = """
+ This is a simple paragraph.
+
+ Item title1:: item description 1
+ Item title2:: item description 2
+
+ Item title1::
+ Item description 1
+
+ Item title2::
+ Item description 2""";
+ String expectedMarkdown = """
+ This is a simple paragraph.
+
+ **Item title1** item description 1
+ **Item title2** item description 2
+
+ **Item title1**
+ Item description 1
+
+ **Item title2**
+ Item description 2""";
+
+ assertEquals(expectedMarkdown, JavadocToMarkdownTransformer.toMarkdown(asciidoc, JavadocFormat.ASCIIDOC));
+ }
+}
diff --git a/devtools/config-doc-maven-plugin/src/main/java/io/quarkus/maven/config/doc/GenerateConfigDocMojo.java b/devtools/config-doc-maven-plugin/src/main/java/io/quarkus/maven/config/doc/GenerateConfigDocMojo.java
index 3fd3c90adbb37..852ff6024d9bb 100644
--- a/devtools/config-doc-maven-plugin/src/main/java/io/quarkus/maven/config/doc/GenerateConfigDocMojo.java
+++ b/devtools/config-doc-maven-plugin/src/main/java/io/quarkus/maven/config/doc/GenerateConfigDocMojo.java
@@ -92,7 +92,7 @@ public void execute() throws MojoExecutionException, MojoFailureException {
List targetDirectories = findTargetDirectories(resolvedScanDirectory);
JavadocRepository javadocRepository = JavadocMerger.mergeJavadocElements(targetDirectories);
- MergedModel mergedModel = ModelMerger.mergeModel(javadocRepository, targetDirectories);
+ MergedModel mergedModel = ModelMerger.mergeModel(javadocRepository, targetDirectories, true);
Format normalizedFormat = Format.normalizeFormat(format);
diff --git a/devtools/config-doc-maven-plugin/src/main/java/io/quarkus/maven/config/doc/generator/AbstractFormatter.java b/devtools/config-doc-maven-plugin/src/main/java/io/quarkus/maven/config/doc/generator/AbstractFormatter.java
index 51d45e63baebe..a19b5b07db5b7 100644
--- a/devtools/config-doc-maven-plugin/src/main/java/io/quarkus/maven/config/doc/generator/AbstractFormatter.java
+++ b/devtools/config-doc-maven-plugin/src/main/java/io/quarkus/maven/config/doc/generator/AbstractFormatter.java
@@ -37,8 +37,8 @@ public boolean displayConfigRootDescription(ConfigRootKey configRootKey, int map
@Override
public String formatDescription(ConfigProperty configProperty) {
- Optional javadocElement = javadocRepository.getElement(configProperty.getSourceClass(),
- configProperty.getSourceName());
+ Optional javadocElement = javadocRepository.getElement(configProperty.getSourceType(),
+ configProperty.getSourceElementName());
if (javadocElement.isEmpty()) {
return null;
@@ -193,19 +193,21 @@ public String toAnchor(String value) {
@Override
public String formatSectionTitle(ConfigSection configSection) {
- Optional javadocElement = javadocRepository.getElement(configSection.getSourceClass(),
- configSection.getSourceName());
+ Optional javadocElement = javadocRepository.getElement(configSection.getSourceType(),
+ configSection.getSourceElementName());
if (javadocElement.isEmpty()) {
throw new IllegalStateException(
- "Couldn't find section title for: " + configSection.getSourceClass() + "#" + configSection.getSourceName());
+ "Couldn't find section title for: " + configSection.getSourceType() + "#"
+ + configSection.getSourceElementName());
}
String javadoc = JavadocTransformer.transform(javadocElement.get().description(), javadocElement.get().format(),
javadocFormat());
if (javadoc == null || javadoc.isBlank()) {
throw new IllegalStateException(
- "Couldn't find section title for: " + configSection.getSourceClass() + "#" + configSection.getSourceName());
+ "Couldn't find section title for: " + configSection.getSourceType() + "#"
+ + configSection.getSourceElementName());
}
return trimFinalDot(javadoc);
diff --git a/devtools/extension-deployment-maven-plugin/pom.xml b/devtools/extension-deployment-maven-plugin/pom.xml
new file mode 100644
index 0000000000000..2bd2ae1ae65a4
--- /dev/null
+++ b/devtools/extension-deployment-maven-plugin/pom.xml
@@ -0,0 +1,180 @@
+
+ 4.0.0
+
+ io.quarkus
+ quarkus-devtools-all
+ 999-SNAPSHOT
+
+ quarkus-extension-deployment-maven-plugin
+ Quarkus - Extension deployment 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-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/AttachConfigMetadataMojo.java b/devtools/extension-deployment-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/AttachConfigMetadataMojo.java
new file mode 100644
index 0000000000000..a3b3af24b11d0
--- /dev/null
+++ b/devtools/extension-deployment-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/AttachConfigMetadataMojo.java
@@ -0,0 +1,238 @@
+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 java.util.Optional;
+
+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.formatter.JavadocTransformer;
+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.MergedModel.ConfigRootKey;
+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.ConfigProperty.PropertyPath;
+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.model.JavadocFormat;
+import io.quarkus.annotation.processor.documentation.config.model.SourceElementType;
+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-config-metadata", defaultPhase = LifecyclePhase.PREPARE_PACKAGE, threadSafe = true, requiresDependencyResolution = ResolutionScope.COMPILE)
+public class AttachConfigMetadataMojo extends AbstractMojo {
+
+ private static final String DEPLOYMENT_ARTIFACT_SUFFIX = "-deployment";
+ 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();
+ SpringConfigMetadataModel springConfigMetadataModel = generateSpringConfigMetadataModel(metaInfDirectories);
+
+ 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 SpringConfigMetadataModel generateSpringConfigMetadataModel(List metaInfDirectories) {
+ MergedModel mergedModel = ModelMerger.mergeModel(metaInfDirectories);
+ if (mergedModel.isEmpty()) {
+ return SpringConfigMetadataModel.empty();
+ }
+
+ Map configRoots = mergedModel.getConfigRoots().get(getExtension());
+ if (configRoots == null || configRoots.isEmpty()) {
+ return SpringConfigMetadataModel.empty();
+ }
+
+ 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",
+ configProperty.getDeprecation().reason(),
+ configProperty.getDeprecation().replacement(),
+ configProperty.getDeprecation().since())
+ : null;
+
+ String description = getJavadoc(javadocRepository, configProperty.getSourceType(),
+ configProperty.getSourceElementName());
+
+ List hintValues = List.of();
+
+ ConfigPhase phase = ConfigPhase.of(configProperty.getPhase());
+
+ properties.add(new SpringConfigMetadataProperty(configProperty.getPath().property(),
+ configProperty.getType(), description,
+ configProperty.getSourceType(),
+ configProperty.getSourceElementType() == SourceElementType.FIELD
+ ? configProperty.getSourceElementName()
+ : null,
+ configProperty.getSourceElementType() == SourceElementType.METHOD
+ ? configProperty.getSourceElementName()
+ : null,
+ configProperty.getDefaultValue(),
+ deprecation, new QuarkusConfigAdditionalMetadataProperty(phase,
+ configProperty.getPath().environmentVariable(), configProperty.isOptional())));
+ if (configProperty.isEnum()) {
+ hintValues = configProperty.getEnumAcceptedValues().values().entrySet().stream()
+ .map(e -> new SpringConfigMetadataHintValue(e.getValue().configValue(),
+ getJavadoc(javadocRepository, configProperty.getType(), e.getKey())))
+ .toList();
+ hints.add(new SpringConfigMetadataHint(configProperty.getPath().property(), hintValues));
+ }
+
+ for (PropertyPath additionalPath : configProperty.getAdditionalPaths()) {
+ properties.add(
+ new SpringConfigMetadataProperty(additionalPath.property(), configProperty.getType(),
+ description, configProperty.getSourceType(),
+ configProperty.getSourceElementType() == SourceElementType.FIELD
+ ? configProperty.getSourceElementName()
+ : null,
+ configProperty.getSourceElementType() == SourceElementType.METHOD
+ ? configProperty.getSourceElementName()
+ : null,
+ configProperty.getDefaultValue(),
+ deprecation, new QuarkusConfigAdditionalMetadataProperty(phase,
+ additionalPath.environmentVariable(), configProperty.isOptional())));
+ if (configProperty.isEnum()) {
+ hints.add(new SpringConfigMetadataHint(additionalPath.property(), hintValues));
+ }
+ }
+ }
+ }
+ });
+ }
+
+ return new SpringConfigMetadataModel(groups, properties, hints);
+ }
+
+ 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_ARTIFACT_SUFFIX.length()),
+ null, NameSource.NONE, false, true);
+ }
+
+ private String getJavadoc(JavadocRepository javadocRepository, String sourceType, String sourceElementName) {
+ Optional javadocElement = javadocRepository
+ .getElement(sourceType, sourceElementName);
+ if (javadocElement.isEmpty()) {
+ return null;
+ }
+
+ return JavadocTransformer.transform(javadocElement.get().description(),
+ javadocElement.get().format(), JavadocFormat.MARKDOWN);
+ }
+}
diff --git a/devtools/extension-deployment-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/model/spring/QuarkusConfigAdditionalMetadataProperty.java b/devtools/extension-deployment-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/model/spring/QuarkusConfigAdditionalMetadataProperty.java
new file mode 100644
index 0000000000000..78b33c5f816e7
--- /dev/null
+++ b/devtools/extension-deployment-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-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/model/spring/SpringConfigMetadataDeprecation.java b/devtools/extension-deployment-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/model/spring/SpringConfigMetadataDeprecation.java
new file mode 100644
index 0000000000000..26a21f32a1026
--- /dev/null
+++ b/devtools/extension-deployment-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-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/model/spring/SpringConfigMetadataGroup.java b/devtools/extension-deployment-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/model/spring/SpringConfigMetadataGroup.java
new file mode 100644
index 0000000000000..68c7947db2be3
--- /dev/null
+++ b/devtools/extension-deployment-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-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/model/spring/SpringConfigMetadataHint.java b/devtools/extension-deployment-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/model/spring/SpringConfigMetadataHint.java
new file mode 100644
index 0000000000000..bf0a539446665
--- /dev/null
+++ b/devtools/extension-deployment-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-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/model/spring/SpringConfigMetadataHintValue.java b/devtools/extension-deployment-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/model/spring/SpringConfigMetadataHintValue.java
new file mode 100644
index 0000000000000..353787ae68794
--- /dev/null
+++ b/devtools/extension-deployment-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-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/model/spring/SpringConfigMetadataModel.java b/devtools/extension-deployment-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/model/spring/SpringConfigMetadataModel.java
new file mode 100644
index 0000000000000..302d9c8659552
--- /dev/null
+++ b/devtools/extension-deployment-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/model/spring/SpringConfigMetadataModel.java
@@ -0,0 +1,12 @@
+package io.quarkus.maven.extension.deployment.metadata.model.spring;
+
+import java.util.List;
+
+public record SpringConfigMetadataModel(List groups,
+ List properties,
+ List hints) {
+
+ public static SpringConfigMetadataModel empty() {
+ return new SpringConfigMetadataModel(List.of(), List.of(), List.of());
+ }
+}
diff --git a/devtools/extension-deployment-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/model/spring/SpringConfigMetadataProperty.java b/devtools/extension-deployment-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/model/spring/SpringConfigMetadataProperty.java
new file mode 100644
index 0000000000000..bdd199acb3ffd
--- /dev/null
+++ b/devtools/extension-deployment-maven-plugin/src/main/java/io/quarkus/maven/extension/deployment/metadata/model/spring/SpringConfigMetadataProperty.java
@@ -0,0 +1,9 @@
+package io.quarkus.maven.extension.deployment.metadata.model.spring;
+
+public record SpringConfigMetadataProperty(String name, String type, String description,
+ String sourceType,
+ // these two are not in the Spring format but were explicitly requested
+ String sourceField, String sourceMethod, String defaultValue,
+ SpringConfigMetadataDeprecation deprecation, QuarkusConfigAdditionalMetadataProperty quarkus) {
+
+}
diff --git a/devtools/extension-deployment-maven-plugin/src/main/resources/META-INF/m2e/lifecycle-mapping-metadata.xml b/devtools/extension-deployment-maven-plugin/src/main/resources/META-INF/m2e/lifecycle-mapping-metadata.xml
new file mode 100644
index 0000000000000..89e04568801b0
--- /dev/null
+++ b/devtools/extension-deployment-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-maven-plugin/src/main/resources/META-INF/plexus/components.xml b/devtools/extension-deployment-maven-plugin/src/main/resources/META-INF/plexus/components.xml
new file mode 100644
index 0000000000000..fde05a043dd99
--- /dev/null
+++ b/devtools/extension-deployment-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
+
+ attach-config-metadata
+
+ my-plugin-not-used-phase
+
+
+
+ io.quarkus:quarkus-extension-deployment-maven-plugin:${project.version}:attach-config-metadata
+
+
+
+
+
+
diff --git a/devtools/pom.xml b/devtools/pom.xml
index b1cd04e9e4a38..a232ce226f2f3 100644
--- a/devtools/pom.xml
+++ b/devtools/pom.xml
@@ -27,5 +27,6 @@
gradle
cli
config-doc-maven-plugin
+ extension-deployment-maven-plugin
diff --git a/extensions/hibernate-orm/deployment/pom.xml b/extensions/hibernate-orm/deployment/pom.xml
index e17abfe0a5389..1829df031af8e 100644
--- a/extensions/hibernate-orm/deployment/pom.xml
+++ b/extensions/hibernate-orm/deployment/pom.xml
@@ -118,6 +118,10 @@
+
+ io.quarkus
+ quarkus-extension-deployment-maven-plugin
+
maven-surefire-plugin
diff --git a/extensions/hibernate-search-orm-elasticsearch/deployment/pom.xml b/extensions/hibernate-search-orm-elasticsearch/deployment/pom.xml
index 6862482c6010a..e7f2a9942cf95 100644
--- a/extensions/hibernate-search-orm-elasticsearch/deployment/pom.xml
+++ b/extensions/hibernate-search-orm-elasticsearch/deployment/pom.xml
@@ -73,6 +73,10 @@
+
+ io.quarkus
+ quarkus-extension-deployment-maven-plugin
+
maven-compiler-plugin
diff --git a/extensions/hibernate-validator/deployment/pom.xml b/extensions/hibernate-validator/deployment/pom.xml
index 3b525bd111dbc..01a2c73ec73c0 100644
--- a/extensions/hibernate-validator/deployment/pom.xml
+++ b/extensions/hibernate-validator/deployment/pom.xml
@@ -70,6 +70,10 @@
+
+ io.quarkus
+ quarkus-extension-deployment-maven-plugin
+
maven-compiler-plugin
diff --git a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus-extension/code/extension-base/java/deployment/pom.tpl.qute.xml b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus-extension/code/extension-base/java/deployment/pom.tpl.qute.xml
index 83e3cf1eb8451..e928303dbf396 100644
--- a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus-extension/code/extension-base/java/deployment/pom.tpl.qute.xml
+++ b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus-extension/code/extension-base/java/deployment/pom.tpl.qute.xml
@@ -36,6 +36,16 @@
+ {#if quarkus.version.or(quarkus.bom.version).compareVersionTo("3.16") >= 0}
+
+ io.quarkus
+ quarkus-extension-deployment-maven-plugin
+ {#if quarkus.version}
+ $\{quarkus.version}
+ {/if}
+ true
+
+ {/if}
maven-compiler-plugin