Skip to content

Commit

Permalink
Clarify ValidationErrorBuildItem/ArtifactResultBuildItem/EmptyBuildItem
Browse files Browse the repository at this point in the history
  • Loading branch information
Sgitario authored and evanchooly committed Sep 8, 2022
1 parent 3479bbe commit 2367d0e
Show file tree
Hide file tree
Showing 6 changed files with 59 additions and 31 deletions.
35 changes: 15 additions & 20 deletions core/builder/src/main/java/io/quarkus/builder/BuildStepBuilder.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.quarkus.builder;

import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
Expand All @@ -8,7 +9,6 @@
import org.wildfly.common.Assert;

import io.quarkus.builder.item.BuildItem;
import io.quarkus.builder.item.EmptyBuildItem;

/**
* A builder for build step instances within a chain. A build step can consume and produce items. It may also register
Expand Down Expand Up @@ -89,9 +89,7 @@ public BuildStepBuilder afterProduce(Class<? extends BuildItem> type) {
*/
public BuildStepBuilder produces(Class<? extends BuildItem> type) {
Assert.checkNotNullParam("type", type);
if (EmptyBuildItem.class.isAssignableFrom(type)) {
throw new IllegalArgumentException("Cannot produce an empty build item, use @Produce(class) instead");
}
checkType(type);
addProduces(new ItemId(type), Constraint.REAL, ProduceFlags.NONE);
return this;
}
Expand All @@ -108,9 +106,7 @@ public BuildStepBuilder produces(Class<? extends BuildItem> type) {
public BuildStepBuilder produces(Class<? extends BuildItem> type, ProduceFlag flag) {
Assert.checkNotNullParam("type", type);
Assert.checkNotNullParam("flag", flag);
if (EmptyBuildItem.class.isAssignableFrom(type)) {
throw new IllegalArgumentException("Cannot produce an empty build item, use @Produce(class) instead");
}
checkType(type);
addProduces(new ItemId(type), Constraint.REAL, ProduceFlags.of(flag));
return this;
}
Expand All @@ -128,9 +124,7 @@ public BuildStepBuilder produces(Class<? extends BuildItem> type, ProduceFlag fl
public BuildStepBuilder produces(Class<? extends BuildItem> type, ProduceFlag flag1, ProduceFlag flag2) {
Assert.checkNotNullParam("type", type);
Assert.checkNotNullParam("flag", flag1);
if (EmptyBuildItem.class.isAssignableFrom(type)) {
throw new IllegalArgumentException("Cannot produce an empty build item, use @Produce(class) instead");
}
checkType(type);
addProduces(new ItemId(type), Constraint.REAL, ProduceFlags.of(flag1).with(flag2));
return this;
}
Expand All @@ -147,9 +141,7 @@ public BuildStepBuilder produces(Class<? extends BuildItem> type, ProduceFlag fl
public BuildStepBuilder produces(Class<? extends BuildItem> type, ProduceFlags flags) {
Assert.checkNotNullParam("type", type);
Assert.checkNotNullParam("flag", flags);
if (EmptyBuildItem.class.isAssignableFrom(type)) {
throw new IllegalArgumentException("Cannot produce an empty build item, use @Produce(class) instead");
}
checkType(type);
addProduces(new ItemId(type), Constraint.REAL, flags);
return this;
}
Expand All @@ -163,9 +155,7 @@ public BuildStepBuilder produces(Class<? extends BuildItem> type, ProduceFlags f
*/
public BuildStepBuilder consumes(Class<? extends BuildItem> type) {
Assert.checkNotNullParam("type", type);
if (EmptyBuildItem.class.isAssignableFrom(type)) {
throw new IllegalArgumentException("Cannot consume an empty build item, use @Consume(class) instead");
}
checkType(type);
addConsumes(new ItemId(type), Constraint.REAL, ConsumeFlags.NONE);
return this;
}
Expand All @@ -180,9 +170,7 @@ public BuildStepBuilder consumes(Class<? extends BuildItem> type) {
*/
public BuildStepBuilder consumes(Class<? extends BuildItem> type, ConsumeFlags flags) {
Assert.checkNotNullParam("type", type);
if (EmptyBuildItem.class.isAssignableFrom(type)) {
throw new IllegalArgumentException("Cannot consume an empty build item, use @Consume(class) instead");
}
checkType(type);
addConsumes(new ItemId(type), Constraint.REAL, flags);
return this;
}
Expand All @@ -201,7 +189,7 @@ public BuildChainBuilder build() {
+ " Either change the return type of the method to a build item type,"
+ " add a parameter of type BuildProducer<[some build item type]>/Consumer<[some build item type]>,"
+ " or annotate the method with @Produces."
+ " Use @Produce(EmptyBuildItem.class) if you want to always execute this step.");
+ " Use @Produce(ArtifactResultBuildItem.class) if you want to always execute this step.");
}
if (BuildChainBuilder.LOG_CONFLICT_CAUSING) {
chainBuilder.addStep(this, new Exception().getStackTrace());
Expand Down Expand Up @@ -266,4 +254,11 @@ public String toString() {
builder.append("]");
return builder.toString();
}

private void checkType(Class<?> type) {
int modifiers = type.getModifiers();
if (Modifier.isInterface(modifiers) || Modifier.isAbstract(modifiers)) {
throw new IllegalArgumentException("Cannot consume/produce interface or abstract class build items");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ public String getId() {
"change the return type of the method to a build item type",
"add a parameter of type BuildProducer<[some build item type]>/Consumer<[some build item type]>",
"annotate the method with @Produces",
"Use @Produce(EmptyBuildItem.class) if you want to always execute this step");
"Use @Produce(ArtifactResultBuildItem.class) if you want to always execute this step");
}

@Test
Expand Down
36 changes: 36 additions & 0 deletions docs/src/main/asciidoc/writing-extensions.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,8 @@ This is a very flexible mechanism.
NOTE: `BuildItem` instances should be immutable, as the producer/consumer model does not allow for mutation to be correctly
ordered. This is not enforced but failure to adhere to this rule can result in race conditions.

NOTE: Build steps are executed if and only if they produce build items that are (transitively) needed by other build steps. Make sure your build step produces a build item, otherwise you should probably produce either `ValidationErrorBuildItem` for build validations, or `ArtifactResultBuildItem` for generated artifacts.

[id='simple-build-items']
===== Simple build items

Expand Down Expand Up @@ -809,6 +811,40 @@ SomeOtherBuildItem secondBuildStep() {
}
----

[id='validation-error-build-item']
===== Validation Error build items

They represent build items with validation errors that make the build fail. These build items are consumed during the initialization of the CDI container.

.Example of usage of an validation error build item in a "pseudo-target" style
[source%nowrap,java]
----
@BuildStep
void checkCompatibility(Capabilities capabilities, BuildProducer<ValidationErrorBuildItem> validationErrors) {
if (capabilities.isMissing(Capability.RESTEASY_REACTIVE)
&& capabilities.isMissing(Capability.RESTEASY_CLASSIC)) {
validationErrors.produce(new ValidationErrorBuildItem(
new ConfigurationException("Cannot use both RESTEasy Classic and Reactive extensions at the same time")));
}
}
----

[id='artifact-result-build-item']
===== Artifact Result build items

They represent build items containing the runnable artifact generated by the build, such as an uberjar or thin jar.
These build items can also be used to always execute a build step without needing to produce anything.

.Example of build step that is always executed in a "pseudo-target" style
[source%nowrap,java]
----
@BuildStep
@Produce(ArtifactResultBuildItem.class)
void runBuildStepThatProducesNothing() {
// ...
}
----

[id='injection']
==== Injection

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,9 @@
import io.quarkus.arc.deployment.SyntheticBeansRuntimeInitBuildItem;
import io.quarkus.arc.deployment.UnremovableBeanBuildItem;
import io.quarkus.arc.deployment.UnremovableBeanBuildItem.BeanTypeExclusion;
import io.quarkus.arc.deployment.ValidationPhaseBuildItem.ValidationErrorBuildItem;
import io.quarkus.arc.deployment.staticmethods.InterceptedStaticMethodsTransformersRegisteredBuildItem;
import io.quarkus.arc.processor.DotNames;
import io.quarkus.builder.item.EmptyBuildItem;
import io.quarkus.datasource.common.runtime.DataSourceUtil;
import io.quarkus.datasource.common.runtime.DatabaseKind;
import io.quarkus.deployment.Capabilities;
Expand All @@ -88,7 +88,6 @@
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.BuildSteps;
import io.quarkus.deployment.annotations.Consume;
import io.quarkus.deployment.annotations.Produce;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.AdditionalApplicationArchiveMarkerBuildItem;
import io.quarkus.deployment.builditem.AdditionalIndexedClassesBuildItem;
Expand Down Expand Up @@ -191,12 +190,12 @@ void registerHibernateOrmMetadataForCoreDialects(
}

@BuildStep
@Produce(EmptyBuildItem.class)
void checkTransactionsSupport(Capabilities capabilities) {
void checkTransactionsSupport(Capabilities capabilities, BuildProducer<ValidationErrorBuildItem> validationErrors) {
// JTA is necessary for blocking Hibernate ORM but not necessarily for Hibernate Reactive
if (capabilities.isMissing(Capability.TRANSACTIONS)
&& capabilities.isMissing(Capability.HIBERNATE_REACTIVE)) {
throw new ConfigurationException("The Hibernate ORM extension is only functional in a JTA environment.");
validationErrors.produce(new ValidationErrorBuildItem(
new ConfigurationException("The Hibernate ORM extension is only functional in a JTA environment.")));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,9 @@
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;

import io.quarkus.builder.item.EmptyBuildItem;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.Consume;
import io.quarkus.deployment.annotations.Produce;
import io.quarkus.deployment.builditem.BytecodeTransformerBuildItem;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import io.quarkus.deployment.builditem.FeatureBuildItem;
Expand Down Expand Up @@ -135,7 +133,6 @@ public FeatureBuildItem feature() {
* }
*
*/
@Produce(EmptyBuildItem.class)
@Consume(MinNettyAllocatorMaxOrderBuildItem.class)
@BuildStep
void adaptNetty(CombinedIndexBuildItem combinedIndexBuildItem, BuildProducer<BytecodeTransformerBuildItem> producer)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
import org.jboss.resteasy.reactive.common.util.RestMediaType;

import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.builder.item.EmptyBuildItem;
import io.quarkus.deployment.Capabilities;
import io.quarkus.deployment.Capability;
import io.quarkus.deployment.Feature;
Expand All @@ -29,6 +28,7 @@
import io.quarkus.deployment.builditem.BytecodeTransformerBuildItem;
import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.deployment.builditem.GeneratedClassBuildItem;
import io.quarkus.deployment.pkg.builditem.ArtifactResultBuildItem;
import io.quarkus.gizmo.ClassOutput;
import io.quarkus.resteasy.reactive.common.deployment.JaxRsResourceIndexBuildItem;
import io.quarkus.resteasy.reactive.links.runtime.GetterAccessorsContainer;
Expand Down Expand Up @@ -85,14 +85,15 @@ AdditionalBeanBuildItem registerRestLinksProviderProducer() {
}

@BuildStep
@Produce(EmptyBuildItem.class)
@Produce(ArtifactResultBuildItem.class)
void validateJsonNeededForHal(Capabilities capabilities,
ResteasyReactiveResourceMethodEntriesBuildItem resourceMethodEntriesBuildItem) {
boolean isHalSupported = capabilities.isPresent(Capability.HAL);
if (isHalSupported && isHalMediaTypeUsedInAnyResource(resourceMethodEntriesBuildItem.getEntries())) {

if (!capabilities.isPresent(Capability.RESTEASY_REACTIVE_JSON_JSONB) && !capabilities.isPresent(
Capability.RESTEASY_REACTIVE_JSON_JACKSON)) {

throw new IllegalStateException("Cannot generate HAL endpoints without "
+ "either 'quarkus-resteasy-reactive-jsonb' or 'quarkus-resteasy-reactive-jackson'");
}
Expand Down

0 comments on commit 2367d0e

Please sign in to comment.