Skip to content

Commit

Permalink
Properly integrate bootstrap config into build system
Browse files Browse the repository at this point in the history
This round makes bootstrap config work properly with
the build system without the arbitrary restrictions of
the first iteration.
This is accomplished mostly by moving config generation
and setup into their own build steps
  • Loading branch information
geoand committed Feb 10, 2020
1 parent 4a680de commit d31fe54
Show file tree
Hide file tree
Showing 13 changed files with 295 additions and 190 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,14 @@
import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.annotations.Weak;
import io.quarkus.deployment.builditem.AdditionalApplicationArchiveMarkerBuildItem;
import io.quarkus.deployment.builditem.BootstrapConfigSetupCompleteBuildItem;
import io.quarkus.deployment.builditem.BytecodeRecorderObjectLoaderBuildItem;
import io.quarkus.deployment.builditem.CapabilityBuildItem;
import io.quarkus.deployment.builditem.ConfigurationBuildItem;
import io.quarkus.deployment.builditem.DeploymentClassLoaderBuildItem;
import io.quarkus.deployment.builditem.MainBootstrapConfigBytecodeRecorderBuildItem;
import io.quarkus.deployment.builditem.MainBytecodeRecorderBuildItem;
import io.quarkus.deployment.builditem.RunTimeConfigurationProxyBuildItem;
import io.quarkus.deployment.builditem.RunTimeConfigurationSourceValueBuildItem;
import io.quarkus.deployment.builditem.RuntimeConfigSetupCompleteBuildItem;
import io.quarkus.deployment.builditem.StaticBytecodeRecorderBuildItem;
import io.quarkus.deployment.configuration.BuildTimeConfigurationReader;
import io.quarkus.deployment.configuration.DefaultValuesConfigurationSource;
Expand Down Expand Up @@ -354,10 +354,8 @@ public static Consumer<BuildChainBuilder> loadStepsFrom(Class<?> clazz, BuildTim
if (phase == ConfigPhase.BUILD_AND_RUN_TIME_FIXED) {
runTimeProxies.computeIfAbsent(parameterClass, readResult::requireRootObjectForClass);
}
} else if (phase == ConfigPhase.BOOTSTRAP) {
throw reportError(parameter, "Bootstrap configuration cannot be consumed here");
} else if (phase == ConfigPhase.RUN_TIME) {
throw reportError(parameter, "Run time configuration cannot be consumed here");
} else if (phase.isReadAtMain()) {
throw reportError(parameter, phase + " configuration cannot be consumed here");
} else {
throw reportError(parameterClass, "Unknown value for ConfigPhase");
}
Expand Down Expand Up @@ -471,10 +469,8 @@ public static Consumer<BuildChainBuilder> loadStepsFrom(Class<?> clazz, BuildTim
if (phase == ConfigPhase.BUILD_AND_RUN_TIME_FIXED) {
runTimeProxies.computeIfAbsent(fieldClass, readResult::requireRootObjectForClass);
}
} else if (phase == ConfigPhase.BOOTSTRAP) {
throw reportError(field, "Bootstrap configuration cannot be consumed here");
} else if (phase == ConfigPhase.RUN_TIME) {
throw reportError(field, "Run time configuration cannot be consumed here");
} else if (phase.isReadAtMain()) {
throw reportError(field, phase + " configuration cannot be consumed here");
} else {
throw reportError(fieldClass, "Unknown value for ConfigPhase");
}
Expand Down Expand Up @@ -545,10 +541,8 @@ public static Consumer<BuildChainBuilder> loadStepsFrom(Class<?> clazz, BuildTim
final ConfigPhase phase = annotation.phase();
if (phase.isAvailableAtBuild()) {
paramSuppList.add(() -> readResult.requireRootObjectForClass(parameterClass));
} else if (phase == ConfigPhase.BOOTSTRAP) {
throw reportError(parameter, "Bootstrap configuration cannot be consumed here");
} else if (phase == ConfigPhase.RUN_TIME) {
throw reportError(parameter, "Run time configuration cannot be consumed here");
} else if (phase.isReadAtMain()) {
throw reportError(parameter, phase + " configuration cannot be consumed here");
} else {
throw reportError(parameter,
"Unsupported conditional class configuration build phase " + phase);
Expand Down Expand Up @@ -580,10 +574,8 @@ public static Consumer<BuildChainBuilder> loadStepsFrom(Class<?> clazz, BuildTim
if (phase.isAvailableAtBuild()) {
setup = setup.andThen(o -> ReflectUtil.setFieldVal(field, o,
readResult.requireRootObjectForClass(fieldClass)));
} else if (phase == ConfigPhase.BOOTSTRAP) {
throw reportError(field, "Bootstrap configuration cannot be consumed here");
} else if (phase == ConfigPhase.RUN_TIME) {
throw reportError(field, "Run time configuration cannot be consumed here");
} else if (phase.isReadAtMain()) {
throw reportError(field, phase + " configuration cannot be consumed here");
} else {
throw reportError(field,
"Unsupported conditional class configuration build phase " + phase);
Expand Down Expand Up @@ -644,10 +636,9 @@ public static Consumer<BuildChainBuilder> loadStepsFrom(Class<?> clazz, BuildTim
assert recordAnnotation != null;
final ExecutionTime executionTime = recordAnnotation.value();
final boolean optional = recordAnnotation.optional();

methodStepConfig = methodStepConfig.andThen(bsb -> bsb.produces(
executionTime == ExecutionTime.STATIC_INIT ? StaticBytecodeRecorderBuildItem.class
: determineMainRecorderBuildItemType(method),
: MainBytecodeRecorderBuildItem.class,
optional ? ProduceFlags.of(ProduceFlag.WEAK) : ProduceFlags.NONE));
}
EnumSet<ConfigPhase> methodConsumingConfigPhases = consumingConfigPhases.clone();
Expand Down Expand Up @@ -744,14 +735,8 @@ public static Consumer<BuildChainBuilder> loadStepsFrom(Class<?> clazz, BuildTim
if (isRecorder && phase == ConfigPhase.BUILD_AND_RUN_TIME_FIXED) {
runTimeProxies.computeIfAbsent(parameterClass, readResult::requireRootObjectForClass);
}
} else if (phase == ConfigPhase.BOOTSTRAP || phase == ConfigPhase.RUN_TIME) {
} else if (phase.isReadAtMain()) {
if (isRecorder) {
if ((phase == ConfigPhase.BOOTSTRAP)
&& !method.getReturnType().equals(RunTimeConfigurationSourceValueBuildItem.class)) {
throw reportError(parameter,
"Bootstrap configuration can only be used in a Build step that returns "
+ RunTimeConfigurationSourceValueBuildItem.class.getSimpleName());
}
methodParamFns.add((bc, bri) -> {
final RunTimeConfigurationProxyBuildItem proxies = bc
.consume(RunTimeConfigurationProxyBuildItem.class);
Expand All @@ -760,9 +745,7 @@ public static Consumer<BuildChainBuilder> loadStepsFrom(Class<?> clazz, BuildTim
runTimeProxies.computeIfAbsent(parameterClass, ReflectUtil::newInstance);
} else {
throw reportError(parameter,
String.format(
"%s configuration cannot be consumed here unless the method is a @Recorder",
phase == ConfigPhase.RUN_TIME ? "Run time" : "Bootstrap"));
phase + " configuration cannot be consumed here unless the method is a @Recorder");
}
} else {
throw reportError(parameterClass, "Unknown value for ConfigPhase");
Expand Down Expand Up @@ -797,12 +780,6 @@ public static Consumer<BuildChainBuilder> loadStepsFrom(Class<?> clazz, BuildTim
resultConsumer = Functions.discardingBiConsumer();
} else if (rawTypeExtends(returnType, BuildItem.class)) {
final Class<? extends BuildItem> type = method.getReturnType().asSubclass(BuildItem.class);
if (type.equals(RunTimeConfigurationSourceValueBuildItem.class)
&& (!isRecorder || recordAnnotation.value() != ExecutionTime.RUNTIME_INIT)) {
throw reportError(method,
"A Build step that returns " + RunTimeConfigurationSourceValueBuildItem.class.getSimpleName()
+ " must also be annotated with @Record(ExecutionTime.RUNTIME_INIT)");
}
if (overridable) {
if (weak) {
methodStepConfig = methodStepConfig
Expand Down Expand Up @@ -868,14 +845,20 @@ public static Consumer<BuildChainBuilder> loadStepsFrom(Class<?> clazz, BuildTim
throw reportError(method,
"Bytecode recorder is static but an injected config object is declared as run time");
}

methodStepConfig = methodStepConfig
.andThen(bsb -> bsb.consumes(RunTimeConfigurationProxyBuildItem.class));

if (methodConsumingConfigPhases.contains(ConfigPhase.BOOTSTRAP)) {
methodStepConfig = methodStepConfig
.andThen(bsb -> bsb.afterProduce(BootstrapConfigSetupCompleteBuildItem.class));
}
if (methodConsumingConfigPhases.contains(ConfigPhase.RUN_TIME)) {
methodStepConfig = methodStepConfig
.andThen(bsb -> bsb.afterProduce(RuntimeConfigSetupCompleteBuildItem.class));
}
}
if (methodConsumingConfigPhases.contains(ConfigPhase.BOOTSTRAP)
&& methodConsumingConfigPhases.contains(ConfigPhase.RUN_TIME)) {
throw reportError(method,
"Bootstrap configuration cannot be used together in a build step with run time configuration");
}

if (methodConsumingConfigPhases.contains(ConfigPhase.BUILD_AND_RUN_TIME_FIXED)
|| methodConsumingConfigPhases.contains(ConfigPhase.BUILD_TIME)) {
methodStepConfig = methodStepConfig
Expand Down Expand Up @@ -965,14 +948,9 @@ public void execute(final BuildContext bc) {
if (recordAnnotation.value() == ExecutionTime.STATIC_INIT) {
bc.produce(new StaticBytecodeRecorderBuildItem(bri));
} else {
Class<? extends BuildItem> buildItemClass = determineMainRecorderBuildItemType(method);
if (buildItemClass.equals(MainBytecodeRecorderBuildItem.class)) {
bc.produce(new MainBytecodeRecorderBuildItem(bri));
} else {
assert buildItemClass == MainBootstrapConfigBytecodeRecorderBuildItem.class;
bc.produce(new MainBootstrapConfigBytecodeRecorderBuildItem(bri));
}
bc.produce(new MainBytecodeRecorderBuildItem(bri));
}

}
}

Expand All @@ -989,12 +967,6 @@ public String toString() {
return chainConfig;
}

private static Class<? extends BuildItem> determineMainRecorderBuildItemType(Method method) {
return method.getReturnType().equals(RunTimeConfigurationSourceValueBuildItem.class)
? MainBootstrapConfigBytecodeRecorderBuildItem.class
: MainBytecodeRecorderBuildItem.class;
}

private static BooleanSupplier and(BooleanSupplier a, BooleanSupplier b) {
return () -> a.getAsBoolean() && b.getAsBoolean();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.quarkus.deployment.builditem;

import io.quarkus.builder.item.EmptyBuildItem;

/**
* Marker used by Build Steps that consume bootstrap configuration to ensure that they run after the bootstrap config has been
* setup
*/
public final class BootstrapConfigSetupCompleteBuildItem extends EmptyBuildItem {
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,23 @@
public final class MainBytecodeRecorderBuildItem extends MultiBuildItem {

private final BytecodeRecorderImpl bytecodeRecorder;
private final String generatedStartupContextClassName;

public MainBytecodeRecorderBuildItem(BytecodeRecorderImpl bytecodeRecorder) {
this.bytecodeRecorder = bytecodeRecorder;
this.generatedStartupContextClassName = null;
}

public MainBytecodeRecorderBuildItem(String generatedStartupContextClassName) {
this.generatedStartupContextClassName = generatedStartupContextClassName;
this.bytecodeRecorder = null;
}

public BytecodeRecorderImpl getBytecodeRecorder() {
return bytecodeRecorder;
}

public String getGeneratedStartupContextClassName() {
return generatedStartupContextClassName;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,11 @@
/**
* This is a special build item that is intended to be used only to support bootstrap configuration in the following manner:
*
* A build step returns this build item (this is a limitation compared to other build items that can also be used with
* BuildProducer)
* containing a {@code RuntimeValue<ConfigSourceProvider>} that is obtained by calling a ({@code RUNTIME_INIT}) recorder.
* A build step produces this BuildItem with a {@code RuntimeValue&lt;ConfigSourceProvider&gt;} a payload that is obtained by
* calling
* a ({@code RUNTIME_INIT}) recorder.
* The build step can optionally use a configuration object that uses the {@code BOOTSTRAP} config phase and pass this
* configuration
* to the recorder to allow the recorder at runtime to customize its behavior
* configuration to the recorder to allow the recorder at runtime to customize its behavior
*/
public final class RunTimeConfigurationSourceValueBuildItem extends MultiBuildItem {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.quarkus.deployment.builditem;

import io.quarkus.builder.item.EmptyBuildItem;

/**
* Marker used by Build Steps that consume runtime configuration to ensure that they run after the runtime config has been setup
*/
public final class RuntimeConfigSetupCompleteBuildItem extends EmptyBuildItem {
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ public final class RunTimeConfigurationGenerator {

static final MethodDescriptor BTRTDVCS_NEW = MethodDescriptor.ofConstructor(BTRTDVCS_CLASS_NAME);

public static final FieldDescriptor C_INSTANCE = FieldDescriptor.of(CONFIG_CLASS_NAME, "INSTANCE",
CONFIG_CLASS_NAME);
static final FieldDescriptor C_BUILD_TIME_CONFIG_SOURCE = FieldDescriptor.of(CONFIG_CLASS_NAME, "buildTimeConfigSource",
ConfigSource.class);
static final FieldDescriptor C_BUILD_TIME_RUN_TIME_DEFAULTS_CONFIG_SOURCE = FieldDescriptor.of(CONFIG_CLASS_NAME,
Expand Down Expand Up @@ -310,6 +312,10 @@ static final class GenerateOperation implements AutoCloseable {
clinit.invokeVirtualMethod(HM_PUT, buildTimeValues, clinit.load(entry.getKey()), clinit.load(entry.getValue()));
}

// static field containing the instance of the class - is set when createBootstrapConfig is run
cc.getFieldCreator(C_INSTANCE)
.setModifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_VOLATILE);

// the build time config source field, to feed into the run time config
cc.getFieldCreator(C_BUILD_TIME_CONFIG_SOURCE)
.setModifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL);
Expand Down Expand Up @@ -671,6 +677,7 @@ public void run() {
try (MethodCreator mc = cc.getMethodCreator(C_CREATE_BOOTSTRAP_CONFIG)) {
mc.setModifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC);
ResultHandle instance = mc.newInstance(MethodDescriptor.ofConstructor(CONFIG_CLASS_NAME));
mc.writeStaticField(C_INSTANCE, instance);
mc.invokeVirtualMethod(C_BOOTSTRAP_CONFIG, instance);
mc.returnValue(instance);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package io.quarkus.deployment.steps;

import static io.quarkus.deployment.configuration.RunTimeConfigurationGenerator.C_CREATE_BOOTSTRAP_CONFIG;

import io.quarkus.deployment.GeneratedClassGizmoAdaptor;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.Produce;
import io.quarkus.deployment.builditem.BootstrapConfigSetupCompleteBuildItem;
import io.quarkus.deployment.builditem.GeneratedClassBuildItem;
import io.quarkus.deployment.builditem.MainBytecodeRecorderBuildItem;
import io.quarkus.gizmo.ClassCreator;
import io.quarkus.gizmo.ClassOutput;
import io.quarkus.gizmo.MethodCreator;
import io.quarkus.runtime.StartupContext;
import io.quarkus.runtime.StartupTask;

public class BootstrapConfigSetupBuildStep {

private static final String BOOTSTRAP_CONFIG_STARTUP_TASK_CLASS_NAME = "io.quarkus.deployment.steps.BootstrapConfigSetup";

/**
* Generates a StartupTask that creates a instance of the generated Config class
* It runs before any StartupTask that uses configuration
*/
@BuildStep
@Produce(BootstrapConfigSetupCompleteBuildItem.class)
void setupBootstrapConfig(BuildProducer<GeneratedClassBuildItem> generatedClass,
BuildProducer<MainBytecodeRecorderBuildItem> mainBytecodeRecorder) {
ClassOutput classOutput = new GeneratedClassGizmoAdaptor(generatedClass, true);

try (ClassCreator clazz = ClassCreator.builder().classOutput(classOutput)
.className(BOOTSTRAP_CONFIG_STARTUP_TASK_CLASS_NAME)
.interfaces(StartupTask.class).build()) {

try (MethodCreator deploy = clazz.getMethodCreator("deploy", void.class, StartupContext.class)) {
deploy.invokeStaticMethod(C_CREATE_BOOTSTRAP_CONFIG);
deploy.returnValue(null);
}
}

mainBytecodeRecorder.produce(new MainBytecodeRecorderBuildItem(BOOTSTRAP_CONFIG_STARTUP_TASK_CLASS_NAME));
}
}
Loading

0 comments on commit d31fe54

Please sign in to comment.