Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Record noteworthy build items into extension metadata #40306

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package io.quarkus.builder.item;

public @interface AddToMetadata {
String value();
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import org.jboss.logging.Logger;
import org.wildfly.common.function.Functions;

import io.quarkus.bootstrap.BootstrapConstants;
import io.quarkus.bootstrap.model.ApplicationModel;
import io.quarkus.builder.BuildChainBuilder;
import io.quarkus.builder.BuildContext;
Expand Down Expand Up @@ -157,7 +158,7 @@ public String getId() {

// the proxy objects used for run time config in the recorders
Map<Class<?>, Object> proxies = new HashMap<>();
for (Class<?> clazz : ServiceUtil.classesNamedIn(classLoader, "META-INF/quarkus-build-steps.list")) {
for (Class<?> clazz : ServiceUtil.classesNamedIn(classLoader, BootstrapConstants.BUILD_STEPS_PATH)) {
try {
result = result.andThen(ExtensionLoader.loadStepsFromClass(clazz, readResult, proxies, bsf));
} catch (Throwable e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.util.HashMap;
import java.util.Map;

import io.quarkus.builder.item.AddToMetadata;
import io.quarkus.builder.item.MultiBuildItem;

/**
Expand All @@ -17,6 +18,7 @@
*
* {@link RunningDevService} helps to manage the lifecycle of the running dev service.
*/
@AddToMetadata("dev-service")
public final class DevServicesResultBuildItem extends MultiBuildItem {

private final String name;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package io.quarkus.deployment.pkg.steps;

import static io.quarkus.deployment.pkg.PackageConfig.JarConfig.JarType.*;
import static io.quarkus.deployment.pkg.PackageConfig.JarConfig.JarType.LEGACY_JAR;
import static io.quarkus.deployment.pkg.PackageConfig.JarConfig.JarType.MUTABLE_JAR;
import static io.quarkus.deployment.pkg.PackageConfig.JarConfig.JarType.UBER_JAR;

import java.io.BufferedInputStream;
import java.io.BufferedWriter;
Expand Down Expand Up @@ -1474,6 +1476,7 @@ private static class IsEntryIgnoredForUberJarPredicate implements Predicate<Stri
"META-INF/DEPENDENCIES.txt",
"META-INF/beans.xml",
"META-INF/quarkus-config-roots.list",
"META-INF/noteworthy-build-items.list",
"META-INF/quarkus-javadoc.properties",
"META-INF/quarkus-extension.properties",
"META-INF/quarkus-extension.json",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ final public class Constants {
public static final String ANNOTATION_CONFIG_WITH_DEFAULT = "io.smallrye.config.WithDefault";
public static final String ANNOTATION_CONFIG_WITH_UNNAMED_KEY = "io.smallrye.config.WithUnnamedKey";

public static final String ANNOTATION_ADD_BUILD_ITEM_TO_METADATA = "io.quarkus.builder.item.AddToMetadata";

public static final Set<String> SUPPORTED_ANNOTATIONS_TYPES = Set.of(ANNOTATION_BUILD_STEP, ANNOTATION_CONFIG_GROUP,
ANNOTATION_CONFIG_ROOT, ANNOTATION_RECORDER, ANNOTATION_CONFIG_MAPPING);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.quarkus.annotation.processor;

import static io.quarkus.annotation.processor.Constants.ANNOTATION_ADD_BUILD_ITEM_TO_METADATA;
import static io.quarkus.annotation.processor.Constants.ANNOTATION_CONFIG_GROUP;
import static io.quarkus.annotation.processor.Constants.ANNOTATION_CONFIG_MAPPING;
import static javax.lang.model.util.ElementFilter.constructorsIn;
Expand Down Expand Up @@ -54,6 +55,7 @@
import javax.lang.model.type.PrimitiveType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import javax.tools.FileObject;
import javax.tools.StandardLocation;
Expand Down Expand Up @@ -82,11 +84,13 @@ public class ExtensionAnnotationProcessor extends AbstractProcessor {

private static final Pattern REMOVE_LEADING_SPACE = Pattern.compile("^ ", Pattern.MULTILINE);
private static final String QUARKUS_GENERATED = "io.quarkus.Generated";
public static final String META_INF_NOTEWORTHY_BUILD_ITEMS_LIST = "META-INF/noteworthy-build-items.list";

private final ConfigDocWriter configDocWriter = new ConfigDocWriter();
private final ConfigDocItemScanner configDocItemScanner = new ConfigDocItemScanner();
private final Set<String> generatedAccessors = new ConcurrentHashMap<String, Boolean>().keySet(Boolean.TRUE);
private final Set<String> generatedJavaDocs = new ConcurrentHashMap<String, Boolean>().keySet(Boolean.TRUE);
private final Set<String> noteworthyBuildItems = new ConcurrentHashMap<String, Boolean>().keySet(Boolean.TRUE);
private final boolean generateDocs = !(Boolean.getBoolean("skipDocs") || Boolean.getBoolean("quickly"));

private final Map<String, Boolean> ANNOTATION_USAGE_TRACKER = new ConcurrentHashMap<>();
Expand Down Expand Up @@ -270,6 +274,21 @@ public FileVisitResult postVisitDirectory(final Path dir, final IOException exc)
}
}

if (!noteworthyBuildItems.isEmpty()) {
try {
final FileObject noteworthies = filer.createResource(StandardLocation.CLASS_OUTPUT, Constants.EMPTY,
META_INF_NOTEWORTHY_BUILD_ITEMS_LIST);
Writer writer = noteworthies.openWriter();
for (String o : noteworthyBuildItems) {
writer.append(o);
writer.append("\n");
}
writer.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}

try {
if (generateDocs) {
final Set<ConfigDocGeneratedOutput> outputs = configDocItemScanner
Expand Down Expand Up @@ -357,6 +376,7 @@ private void processBuildStep(RoundEnvironment roundEnv, TypeElement annotation)
.toString();
if (processorClassNames.add(binaryName)) {
validateRecordBuildSteps(clazz);
recordNoteworthyBuildItems(clazz);
recordConfigJavadoc(clazz);
generateAccessor(clazz);
final StringBuilder rbn = getRelativeBinaryName(clazz, new StringBuilder());
Expand Down Expand Up @@ -433,6 +453,64 @@ private void validateRecordBuildSteps(TypeElement clazz) {
}
}

private void recordNoteworthyBuildItems(TypeElement clazz) {
for (Element e : clazz.getEnclosedElements()) {
if (e.getKind() != ElementKind.METHOD) {
continue;
}
ExecutableElement ex = (ExecutableElement) e;
if (!isAnnotationPresent(ex, Constants.ANNOTATION_BUILD_STEP)) {
continue;
}

if (!(e instanceof ExecutableElement)) {
continue;
}

ExecutableElement exel = (ExecutableElement) e;

TypeMirror returned = exel.getReturnType();
if (returned.getKind() != TypeKind.VOID) {
Types typeUtils = processingEnv.getTypeUtils();

List<? extends AnnotationMirror> allAnnotations = typeUtils.asElement(returned)
.getAnnotationMirrors();
Optional<? extends AnnotationMirror> oam = allAnnotations.stream()
.filter(this::isAddMetadataAnnotation)
.findAny();
if (oam.isPresent()) {
AnnotationMirror am = oam.get();

Map<? extends ExecutableElement, ? extends AnnotationValue> elementValues = am.getElementValues();
Optional<? extends Map.Entry<? extends ExecutableElement, ? extends AnnotationValue>> valueEntry = elementValues
.entrySet()
.stream()
.filter(entry -> entry.getKey()
.getSimpleName()
.toString()
.equals("value"))
.findAny();
if (valueEntry.isPresent()) {
String value = valueEntry.get()
.getValue()
.getValue()// First getValue gets from the entry, the second gets from the annotation
.toString();
noteworthyBuildItems.add(value);
}

}
}
}
}

private boolean isAddMetadataAnnotation(AnnotationMirror meth) {
Types typeUtils = processingEnv.getTypeUtils();
TypeElement element = (TypeElement) typeUtils.asElement(meth.getAnnotationType());
String name = element.getQualifiedName()
.toString();
return ANNOTATION_ADD_BUILD_ITEM_TO_METADATA.equals(name);
}

private Name getPackageName(TypeElement clazz) {
return processingEnv.getElementUtils()
.getPackageOf(clazz)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
Expand Down Expand Up @@ -54,9 +55,36 @@ void shouldGenerateABscFile(Results results) throws IOException {
assertEquals("org.acme.examples.ClassWithBuildStep", contents);
}

private String removeLineBreaks(String s) {
return s.replace(System.getProperty("line.separator"), "")
.replace("\n", "");
@Test
@Classpath("org.acme.examples.ClassWithBuildStep")
void shouldNotGenerateANoteworthyBuildItemsFileIfThereAreNoAnnotationsForIt(Results results) throws IOException {
assertNoErrrors(results);
List<JavaFileObject> sources = results.sources;
JavaFileObject buildItemsFile = sources.stream()
.filter(source -> source.getName()
.endsWith("build-items.list"))
.findAny()
.orElse(null);
assertNull(buildItemsFile);

}

@Test
@Classpath("org.acme.examples.ClassWithNoteworthyBuildItem")
void shouldGenerateANoteworthyBuildItemsFile(Results results) throws IOException {
assertNoErrrors(results);
List<JavaFileObject> sources = results.sources;
JavaFileObject buildItemsFile = sources.stream()
.filter(source -> source.getName()
.endsWith("build-items.list"))
.findAny()
.orElse(null);
assertNotNull(buildItemsFile);

String contents = removeLineBreaks(new String(buildItemsFile
.openInputStream()
.readAllBytes(), StandardCharsets.UTF_8));
assertEquals("some-cool-ability", contents);
}

@Test
Expand All @@ -65,6 +93,11 @@ void shouldProcessEmptyClassWithoutErrors(Results results) {
assertNoErrrors(results);
}

private String removeLineBreaks(String s) {
return s.replace(System.getProperty("line.separator"), "")
.replace("\n", "");
}

private static void assertNoErrrors(Results results) {
assertEquals(0, results.find()
.errors()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.acme.examples;

import io.quarkus.deployment.annotations.BuildStep;

public class ClassWithNoteworthyBuildItem {
@BuildStep
NoteworthyBuildItem devService() {
return new NoteworthyBuildItem();
}

// This one shouldn't get recorded for use in the metadata
@BuildStep
ArbitraryBuildItem arbitrary() {
return new ArbitraryBuildItem();
}

// This one shouldn't get recorded, obviously, and it should not cause runtime exceptions
@BuildStep
void boring() {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.acme.examples;

import io.quarkus.builder.item.AddToMetadata;
import io.quarkus.builder.item.MultiBuildItem;

@AddToMetadata("some-cool-ability")
public final class NoteworthyBuildItem extends MultiBuildItem {

}
5 changes: 5 additions & 0 deletions docs/src/main/asciidoc/extension-metadata.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,11 @@
Same as `quarkus-config-roots.list`, this file may appear in a runtime extension artifact as well in a deployment one. It is generated as part of the extension project build process from the descriptions of configuration options available in the classes annotated with `io.quarkus.runtime.annotations.ConfigRoot` and is not supported to be edited manually.

[[quarkus-build-steps]]
== META-INF/quarkus-build-steps.list

Check warning on line 181 in docs/src/main/asciidoc/extension-metadata.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Headings] Use sentence-style capitalization in 'META-INF/quarkus-build-steps.list'. Raw Output: {"message": "[Quarkus.Headings] Use sentence-style capitalization in 'META-INF/quarkus-build-steps.list'.", "location": {"path": "docs/src/main/asciidoc/extension-metadata.adoc", "range": {"start": {"line": 181, "column": 4}}}, "severity": "INFO"}

This file may appear in a deployment extension artifact. It contains a list of classes that implement Quarkus build steps (methods annotated with `io.quarkus.deployment.annotations.BuildStep`). This file is generated as part of the extension project build process and must not be edited manually.

Check warning on line 183 in docs/src/main/asciidoc/extension-metadata.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsWarnings] Consider using 'might (for possiblity)' or 'can (for ability)' rather than 'may' unless updating existing content that uses the term. Raw Output: {"message": "[Quarkus.TermsWarnings] Consider using 'might (for possiblity)' or 'can (for ability)' rather than 'may' unless updating existing content that uses the term.", "location": {"path": "docs/src/main/asciidoc/extension-metadata.adoc", "range": {"start": {"line": 183, "column": 11}}}, "severity": "WARNING"}

Check warning on line 183 in docs/src/main/asciidoc/extension-metadata.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsSuggestions] Depending on the context, consider using 'because' or 'while' rather than 'as'. Raw Output: {"message": "[Quarkus.TermsSuggestions] Depending on the context, consider using 'because' or 'while' rather than 'as'.", "location": {"path": "docs/src/main/asciidoc/extension-metadata.adoc", "range": {"start": {"line": 183, "column": 218}}}, "severity": "INFO"}

[[quarkus-build-steps]]
== META-INF/noteworthy-build-items.list

Check warning on line 186 in docs/src/main/asciidoc/extension-metadata.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Headings] Use sentence-style capitalization in 'META-INF/noteworthy-build-items.list'. Raw Output: {"message": "[Quarkus.Headings] Use sentence-style capitalization in 'META-INF/noteworthy-build-items.list'.", "location": {"path": "docs/src/main/asciidoc/extension-metadata.adoc", "range": {"start": {"line": 186, "column": 4}}}, "severity": "INFO"}

This file may appear in a deployment extension artifact. It contains a list of build items produced by this extension that represent noteworthy function which should be noted in the extension metadata.

Check warning on line 188 in docs/src/main/asciidoc/extension-metadata.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsWarnings] Consider using 'might (for possiblity)' or 'can (for ability)' rather than 'may' unless updating existing content that uses the term. Raw Output: {"message": "[Quarkus.TermsWarnings] Consider using 'might (for possiblity)' or 'can (for ability)' rather than 'may' unless updating existing content that uses the term.", "location": {"path": "docs/src/main/asciidoc/extension-metadata.adoc", "range": {"start": {"line": 188, "column": 11}}}, "severity": "WARNING"}

Check warning on line 188 in docs/src/main/asciidoc/extension-metadata.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsSuggestions] Depending on the context, consider using ', which (non restrictive clause preceded by a comma)' or 'that (restrictive clause without a comma)' rather than 'which'. Raw Output: {"message": "[Quarkus.TermsSuggestions] Depending on the context, consider using ', which (non restrictive clause preceded by a comma)' or 'that (restrictive clause without a comma)' rather than 'which'.", "location": {"path": "docs/src/main/asciidoc/extension-metadata.adoc", "range": {"start": {"line": 188, "column": 153}}}, "severity": "INFO"}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public interface BootstrapConstants {

String DESCRIPTOR_PATH = META_INF + '/' + DESCRIPTOR_FILE_NAME;
String BUILD_STEPS_PATH = META_INF + "/quarkus-build-steps.list";
String NOTEWORTHY_BUILD_ITEMS_PATH = META_INF + "/noteworthy-build-items.list";
String EXTENSION_METADATA_PATH = META_INF + '/' + QUARKUS_EXTENSION_FILE_NAME;

String PROP_DEPLOYMENT_ARTIFACT = "deployment-artifact";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,15 @@
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Scm;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.LifecyclePhase;
Expand Down Expand Up @@ -82,7 +85,7 @@
* Generates Quarkus extension descriptor for the runtime artifact.
* <p>
* <p/>
* Also generates META-INF/quarkus-extension.json which includes properties of
* Also generates META-INF/quarkus-extension.yaml which includes properties of
* the extension such as name, labels, maven coordinates, etc that are used by
* the tools.
*
Expand Down Expand Up @@ -459,6 +462,7 @@ public void execute() throws MojoExecutionException {

setBuiltWithQuarkusCoreVersion(extObject);
addJavaVersion(extObject);
addNoteworthyItems(extObject);
addCapabilities(extObject);
addSource(extObject);
addExtensionDependencies(extObject);
Expand Down Expand Up @@ -739,6 +743,29 @@ public void addJavaVersion(ObjectNode extObject) {
}
}

private void addNoteworthyItems(ObjectNode extObject) {
try {
Set<String> noteworthies = getNoteworthyList();
if (noteworthies != null) {
for (String noteworthy : noteworthies) {
ObjectNode metadataNode = getMetadataNode(extObject);
// Ignore if already set
String key = "provides-" + noteworthy;
if (!metadataNode.has(key) && noteworthy != null) {
metadataNode.put(key, "true");
}
}

}
} catch (IOException e) {
throw new RuntimeException(e);
} catch (MojoExecutionException e) {
throw new RuntimeException(e);
} catch (MojoFailureException e) {
throw new RuntimeException(e);
}
}

private void addCapabilities(ObjectNode extObject) throws MojoExecutionException {
ObjectNode capsNode = null;
if (!capabilities.getProvides().isEmpty()) {
Expand Down Expand Up @@ -1014,6 +1041,35 @@ private org.eclipse.aether.artifact.Artifact getDeploymentArtifact(org.eclipse.a
return DependencyUtils.toArtifact(deploymentStr);
}

private Set<String> getNoteworthyList() throws IOException, MojoExecutionException, MojoFailureException {

ArtifactCoords deploymentCoords = getDeploymentCoords();

// Ask Maven to resolve the artifact's location, so we can look inside it.
// That could be downloading it from a remote repository, searching the local repository or a cache.

String artifactId = deploymentCoords.getArtifactId();
org.eclipse.aether.artifact.Artifact aetherArtifact = new DefaultArtifact(
deploymentCoords.getGroupId(),
artifactId,
deploymentCoords.getClassifier(),
deploymentCoords.getType(),
deploymentCoords.getVersion());

final LocalProject localProject = workspaceProvider.getProject(deploymentCoords.getGroupId(),
deploymentCoords.getArtifactId());
if (localProject != null) {
Path file = localProject.getClassesDir().resolve(BootstrapConstants.NOTEWORTHY_BUILD_ITEMS_PATH);
if (Files.exists(file)) {
try (Stream<String> lines = Files.lines(file)) {
return lines.collect(Collectors.toSet());
}
}
}

return null;
}

private Properties getExtensionDescriptor(org.eclipse.aether.artifact.Artifact a, boolean packaged) {
final File f;
try {
Expand Down
Loading
Loading