Add first version of bootstrap config support

This commit is contained in:
Georgios Andrianakis
2020-01-22 17:15:36 +02:00
parent 2a3b0194cb
commit 4a680de093
30 changed files with 906 additions and 109 deletions

View File

@@ -363,12 +363,13 @@ stages:
parameters:
poolSettings: ${{parameters.poolSettings}}
expectUseVMs: ${{parameters.expectUseVMs}}
timeoutInMinutes: 30
timeoutInMinutes: 35
modules:
- tika
- hibernate-validator
- test-extension
- logging-gelf
- bootstrap-config
name: misc_2
- template: native-build-steps.yaml

View File

@@ -66,8 +66,10 @@ 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.StaticBytecodeRecorderBuildItem;
import io.quarkus.deployment.configuration.BuildTimeConfigurationReader;
import io.quarkus.deployment.configuration.DefaultValuesConfigurationSource;
@@ -352,6 +354,8 @@ public final class ExtensionLoader {
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 {
@@ -467,6 +471,8 @@ public final class ExtensionLoader {
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 {
@@ -539,6 +545,8 @@ public final class ExtensionLoader {
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 {
@@ -572,6 +580,8 @@ public final class ExtensionLoader {
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 {
@@ -634,9 +644,10 @@ public final class ExtensionLoader {
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
: MainBytecodeRecorderBuildItem.class,
: determineMainRecorderBuildItemType(method),
optional ? ProduceFlags.of(ProduceFlag.WEAK) : ProduceFlags.NONE));
}
EnumSet<ConfigPhase> methodConsumingConfigPhases = consumingConfigPhases.clone();
@@ -733,8 +744,14 @@ public final class ExtensionLoader {
if (isRecorder && phase == ConfigPhase.BUILD_AND_RUN_TIME_FIXED) {
runTimeProxies.computeIfAbsent(parameterClass, readResult::requireRootObjectForClass);
}
} else if (phase == ConfigPhase.RUN_TIME) {
} else if (phase == ConfigPhase.BOOTSTRAP || phase == ConfigPhase.RUN_TIME) {
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);
@@ -743,7 +760,9 @@ public final class ExtensionLoader {
runTimeProxies.computeIfAbsent(parameterClass, ReflectUtil::newInstance);
} else {
throw reportError(parameter,
"Run time configuration cannot be consumed here unless the method is a @Recorder");
String.format(
"%s configuration cannot be consumed here unless the method is a @Recorder",
phase == ConfigPhase.RUN_TIME ? "Run time" : "Bootstrap"));
}
} else {
throw reportError(parameterClass, "Unknown value for ConfigPhase");
@@ -778,6 +797,12 @@ public final class ExtensionLoader {
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
@@ -837,7 +862,8 @@ public final class ExtensionLoader {
throw reportError(method, "Unsupported method return type " + returnType);
}
if (methodConsumingConfigPhases.contains(ConfigPhase.RUN_TIME)) {
if (methodConsumingConfigPhases.contains(ConfigPhase.BOOTSTRAP)
|| methodConsumingConfigPhases.contains(ConfigPhase.RUN_TIME)) {
if (isRecorder && recordAnnotation.value() == ExecutionTime.STATIC_INIT) {
throw reportError(method,
"Bytecode recorder is static but an injected config object is declared as run time");
@@ -845,6 +871,11 @@ public final class ExtensionLoader {
methodStepConfig = methodStepConfig
.andThen(bsb -> bsb.consumes(RunTimeConfigurationProxyBuildItem.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
@@ -934,9 +965,14 @@ public final class ExtensionLoader {
if (recordAnnotation.value() == ExecutionTime.STATIC_INIT) {
bc.produce(new StaticBytecodeRecorderBuildItem(bri));
} else {
bc.produce(new MainBytecodeRecorderBuildItem(bri));
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));
}
}
}
}
@@ -953,6 +989,12 @@ public final class ExtensionLoader {
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();
}

View File

@@ -0,0 +1,24 @@
package io.quarkus.deployment.builditem;
import io.quarkus.builder.item.MultiBuildItem;
import io.quarkus.deployment.recording.BytecodeRecorderImpl;
/**
* This build item will be used to write bytecode that supports the Bootstrap phase of the configuration
* That code essentially uses part of the configuration system to pass configuration data to
* recorders that then use the configuration to create new configuration sources.
* These sources are then used to create the final runtime configuration which then passed on
* to all the other runtime recorders
*/
public final class MainBootstrapConfigBytecodeRecorderBuildItem extends MultiBuildItem {
private final BytecodeRecorderImpl bytecodeRecorder;
public MainBootstrapConfigBytecodeRecorderBuildItem(BytecodeRecorderImpl bytecodeRecorder) {
this.bytecodeRecorder = bytecodeRecorder;
}
public BytecodeRecorderImpl getBytecodeRecorder() {
return bytecodeRecorder;
}
}

View File

@@ -0,0 +1,29 @@
package io.quarkus.deployment.builditem;
import org.eclipse.microprofile.config.spi.ConfigSourceProvider;
import io.quarkus.builder.item.MultiBuildItem;
import io.quarkus.runtime.RuntimeValue;
/**
* 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.
* 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
*/
public final class RunTimeConfigurationSourceValueBuildItem extends MultiBuildItem {
private final RuntimeValue<ConfigSourceProvider> configSourcesValue;
public RunTimeConfigurationSourceValueBuildItem(RuntimeValue<ConfigSourceProvider> configSourcesValue) {
this.configSourcesValue = configSourcesValue;
}
public RuntimeValue<ConfigSourceProvider> getConfigSourcesValue() {
return configSourcesValue;
}
}

View File

@@ -64,11 +64,14 @@ public final class BuildTimeConfigurationReader {
final ConfigPatternMap<Container> buildTimePatternMap;
final ConfigPatternMap<Container> buildTimeRunTimePatternMap;
final ConfigPatternMap<Container> bootstrapPatternMap;
final ConfigPatternMap<Container> runTimePatternMap;
final List<RootDefinition> buildTimeVisibleRoots;
final List<RootDefinition> allRoots;
final boolean bootstrapRootsEmpty;
/**
* Construct a new instance.
*
@@ -77,6 +80,7 @@ public final class BuildTimeConfigurationReader {
public BuildTimeConfigurationReader(final List<Class<?>> configRoots) {
Assert.checkNotNullParam("configRoots", configRoots);
List<RootDefinition> bootstrapRoots = new ArrayList<>();
List<RootDefinition> runTimeRoots = new ArrayList<>();
List<RootDefinition> buildTimeRunTimeRoots = new ArrayList<>();
List<RootDefinition> buildTimeRoots = new ArrayList<>();
@@ -98,12 +102,15 @@ public final class BuildTimeConfigurationReader {
buildTimeRoots.add(definition);
} else if (phase == ConfigPhase.BUILD_AND_RUN_TIME_FIXED) {
buildTimeRunTimeRoots.add(definition);
} else if (phase == ConfigPhase.BOOTSTRAP) {
bootstrapRoots.add(definition);
} else {
assert phase == ConfigPhase.RUN_TIME;
runTimeRoots.add(definition);
}
}
bootstrapPatternMap = PatternMapBuilder.makePatterns(bootstrapRoots);
runTimePatternMap = PatternMapBuilder.makePatterns(runTimeRoots);
buildTimeRunTimePatternMap = PatternMapBuilder.makePatterns(buildTimeRunTimeRoots);
buildTimePatternMap = PatternMapBuilder.makePatterns(buildTimeRoots);
@@ -112,8 +119,12 @@ public final class BuildTimeConfigurationReader {
buildTimeVisibleRoots.addAll(buildTimeRoots);
buildTimeVisibleRoots.addAll(buildTimeRunTimeRoots);
List<RootDefinition> allRoots = new ArrayList<>(buildTimeVisibleRoots.size() + runTimeRoots.size());
bootstrapRootsEmpty = bootstrapRoots.isEmpty();
List<RootDefinition> allRoots = new ArrayList<>(
buildTimeVisibleRoots.size() + bootstrapRoots.size() + runTimeRoots.size());
allRoots.addAll(buildTimeVisibleRoots);
allRoots.addAll(bootstrapRoots);
allRoots.addAll(runTimeRoots);
this.allRoots = allRoots;
@@ -332,6 +343,21 @@ public final class BuildTimeConfigurationReader {
ni.goToStart();
ni.next();
matched = runTimePatternMap.match(ni);
if (matched != null) {
// it's a specified run-time default (record for later)
boolean old = ExpandingConfigSource.setExpanding(false);
try {
specifiedRunTimeDefaultValues.put(propertyName,
config.getOptionalValue(propertyName, String.class).orElse(""));
} finally {
ExpandingConfigSource.setExpanding(old);
}
continue;
}
// also check for the bootstrap properties since those need to be added to specifiedRunTimeDefaultValues as well
ni.goToStart();
ni.next();
matched = bootstrapPatternMap.match(ni);
if (matched != null) {
// it's a specified run-time default (record for later)
boolean old = ExpandingConfigSource.setExpanding(false);
@@ -354,7 +380,8 @@ public final class BuildTimeConfigurationReader {
}
}
return new ReadResult(objectsByRootClass, specifiedRunTimeDefaultValues, buildTimeRunTimeVisibleValues,
buildTimePatternMap, buildTimeRunTimePatternMap, runTimePatternMap, allRoots);
buildTimePatternMap, buildTimeRunTimePatternMap, bootstrapPatternMap, runTimePatternMap, allRoots,
bootstrapRootsEmpty);
}
/**
@@ -689,22 +716,28 @@ public final class BuildTimeConfigurationReader {
final Map<String, String> buildTimeRunTimeVisibleValues;
final ConfigPatternMap<Container> buildTimePatternMap;
final ConfigPatternMap<Container> buildTimeRunTimePatternMap;
final ConfigPatternMap<Container> bootstrapPatternMap;
final ConfigPatternMap<Container> runTimePatternMap;
final Map<Class<?>, RootDefinition> runTimeRootsByClass;
final List<RootDefinition> allRoots;
final boolean bootstrapRootsEmpty;
ReadResult(final Map<Class<?>, Object> objectsByRootClass, final Map<String, String> specifiedRunTimeDefaultValues,
final Map<String, String> buildTimeRunTimeVisibleValues,
final ConfigPatternMap<Container> buildTimePatternMap,
final ConfigPatternMap<Container> buildTimeRunTimePatternMap,
final ConfigPatternMap<Container> runTimePatternMap, final List<RootDefinition> allRoots) {
final ConfigPatternMap<Container> bootstrapPatternMap,
final ConfigPatternMap<Container> runTimePatternMap, final List<RootDefinition> allRoots,
boolean bootstrapRootsEmpty) {
this.objectsByRootClass = objectsByRootClass;
this.specifiedRunTimeDefaultValues = specifiedRunTimeDefaultValues;
this.buildTimeRunTimeVisibleValues = buildTimeRunTimeVisibleValues;
this.buildTimePatternMap = buildTimePatternMap;
this.buildTimeRunTimePatternMap = buildTimeRunTimePatternMap;
this.bootstrapPatternMap = bootstrapPatternMap;
this.runTimePatternMap = runTimePatternMap;
this.allRoots = allRoots;
this.bootstrapRootsEmpty = bootstrapRootsEmpty;
Map<Class<?>, RootDefinition> map = new HashMap<>();
for (RootDefinition root : allRoots) {
map.put(root.getConfigurationClass(), root);
@@ -740,6 +773,10 @@ public final class BuildTimeConfigurationReader {
return buildTimeRunTimePatternMap;
}
public ConfigPatternMap<Container> getBootstrapPatternMap() {
return bootstrapPatternMap;
}
public ConfigPatternMap<Container> getRunTimePatternMap() {
return runTimePatternMap;
}
@@ -748,6 +785,10 @@ public final class BuildTimeConfigurationReader {
return allRoots;
}
public boolean isBootstrapRootsEmpty() {
return bootstrapRootsEmpty;
}
public RootDefinition requireRootDefinitionForClass(Class<?> clazz) {
final RootDefinition def = runTimeRootsByClass.get(clazz);
if (def == null) {

View File

@@ -3,6 +3,7 @@ package io.quarkus.deployment.configuration;
import static io.quarkus.deployment.util.ReflectUtil.reportError;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -73,6 +74,7 @@ import io.smallrye.config.SmallRyeConfigBuilder;
public final class RunTimeConfigurationGenerator {
public static final String CONFIG_CLASS_NAME = "io.quarkus.runtime.generated.Config";
static final String BSDVCS_CLASS_NAME = "io.quarkus.runtime.generated.BootstrapDefaultValuesConfigSource";
static final String RTDVCS_CLASS_NAME = "io.quarkus.runtime.generated.RunTimeDefaultValuesConfigSource";
static final String BTRTDVCS_CLASS_NAME = "io.quarkus.runtime.generated.BuildTimeRunTimeDefaultValuesConfigSource";
@@ -84,13 +86,18 @@ public final class RunTimeConfigurationGenerator {
ConfigSource.class);
static final FieldDescriptor C_BUILD_TIME_RUN_TIME_DEFAULTS_CONFIG_SOURCE = FieldDescriptor.of(CONFIG_CLASS_NAME,
"buildTimeRunTimeDefaultsConfigSource", ConfigSource.class);
public static final MethodDescriptor C_CREATE_RUN_TIME_CONFIG = MethodDescriptor.ofMethod(CONFIG_CLASS_NAME,
"createRunTimeConfig", void.class);
public static final MethodDescriptor C_CREATE_BOOTSTRAP_CONFIG = MethodDescriptor.ofMethod(CONFIG_CLASS_NAME,
"createBootstrapConfig", CONFIG_CLASS_NAME);
public static final MethodDescriptor C_ENSURE_INITIALIZED = MethodDescriptor.ofMethod(CONFIG_CLASS_NAME,
"ensureInitialized", void.class);
static final FieldDescriptor C_BOOTSTRAP_DEFAULTS_CONFIG_SOURCE = FieldDescriptor.of(CONFIG_CLASS_NAME,
"bootstrapDefaultsConfigSource", ConfigSource.class);
static final FieldDescriptor C_RUN_TIME_DEFAULTS_CONFIG_SOURCE = FieldDescriptor.of(CONFIG_CLASS_NAME,
"runTimeDefaultsConfigSource", ConfigSource.class);
static final MethodDescriptor C_READ_CONFIG = MethodDescriptor.ofMethod(CONFIG_CLASS_NAME, "readConfig", void.class);
static final MethodDescriptor C_BOOTSTRAP_CONFIG = MethodDescriptor.ofMethod(CONFIG_CLASS_NAME, "readBootstrapConfig",
void.class);
public static final MethodDescriptor C_READ_CONFIG = MethodDescriptor.ofMethod(CONFIG_CLASS_NAME, "readConfig", void.class,
List.class);
static final FieldDescriptor C_SPECIFIED_RUN_TIME_CONFIG_SOURCE = FieldDescriptor.of(CONFIG_CLASS_NAME,
"specifiedRunTimeConfigSource",
ConfigSource.class);
@@ -138,8 +145,13 @@ public final class RunTimeConfigurationGenerator {
IntFunction.class);
static final MethodDescriptor CU_CONFIG_BUILDER = MethodDescriptor.ofMethod(ConfigUtils.class, "configBuilder",
SmallRyeConfigBuilder.class, boolean.class);
static final MethodDescriptor CU_CONFIG_BUILDER_WITH_ADD_DISCOVERED = MethodDescriptor.ofMethod(ConfigUtils.class,
"configBuilder",
SmallRyeConfigBuilder.class, boolean.class, boolean.class);
static final MethodDescriptor CU_ADD_SOURCE_PROVIDER = MethodDescriptor.ofMethod(ConfigUtils.class, "addSourceProvider",
void.class, SmallRyeConfigBuilder.class, ConfigSourceProvider.class);
static final MethodDescriptor CU_ADD_SOURCE_PROVIDERS = MethodDescriptor.ofMethod(ConfigUtils.class, "addSourceProviders",
void.class, SmallRyeConfigBuilder.class, Collection.class);
static final MethodDescriptor HM_NEW = MethodDescriptor.ofConstructor(HashMap.class);
static final MethodDescriptor HM_PUT = MethodDescriptor.ofMethod(HashMap.class, "put", Object.class, Object.class,
@@ -195,6 +207,7 @@ public final class RunTimeConfigurationGenerator {
static final MethodDescriptor QCF_SET_CONFIG = MethodDescriptor.ofMethod(QuarkusConfigFactory.class, "setConfig",
void.class, SmallRyeConfig.class);
static final MethodDescriptor BSDVCS_NEW = MethodDescriptor.ofConstructor(BSDVCS_CLASS_NAME);
static final MethodDescriptor RTDVCS_NEW = MethodDescriptor.ofConstructor(RTDVCS_CLASS_NAME);
static final MethodDescriptor SRC_GET_CONVERTER = MethodDescriptor.ofMethod(SmallRyeConfig.class, "getConverter",
@@ -229,11 +242,12 @@ public final class RunTimeConfigurationGenerator {
final ClassCreator cc;
final MethodCreator clinit;
final BytecodeCreator converterSetup;
final MethodCreator readBootstrapConfig;
final ResultHandle readBootstrapConfigNameBuilder;
final MethodCreator readConfig;
final ResultHandle readConfigNameBuilder;
final ResultHandle clinitNameBuilder;
final BuildTimeConfigurationReader.ReadResult buildTimeConfigResult;
final ConfigPatternMap<Container> runTimePatternMap;
final List<RootDefinition> roots;
// default values given in the build configuration
final Map<String, String> specifiedRunTimeDefaultValues;
@@ -309,6 +323,13 @@ public final class RunTimeConfigurationGenerator {
final ResultHandle buildTimeRunTimeDefaultValuesConfigSource = clinit.newInstance(BTRTDVCS_NEW);
clinit.writeStaticField(C_BUILD_TIME_RUN_TIME_DEFAULTS_CONFIG_SOURCE, buildTimeRunTimeDefaultValuesConfigSource);
// the bootstrap default values config source
if (!buildTimeReadResult.isBootstrapRootsEmpty()) {
cc.getFieldCreator(C_BOOTSTRAP_DEFAULTS_CONFIG_SOURCE)
.setModifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL);
clinit.writeStaticField(C_BOOTSTRAP_DEFAULTS_CONFIG_SOURCE, clinit.newInstance(BSDVCS_NEW));
}
// the run time default values config source
cc.getFieldCreator(C_RUN_TIME_DEFAULTS_CONFIG_SOURCE)
.setModifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL);
@@ -327,29 +348,37 @@ public final class RunTimeConfigurationGenerator {
// block for converter setup
converterSetup = clinit.createScope();
// create readBootstrapConfig method - this will always exist whether or not it contains a method body
// the method body will be empty when there are no bootstrap configuration roots
readBootstrapConfig = cc.getMethodCreator(C_BOOTSTRAP_CONFIG);
if (buildTimeReadResult.isBootstrapRootsEmpty()) {
readBootstrapConfigNameBuilder = null;
} else {
readBootstrapConfigNameBuilder = readBootstrapConfig.newInstance(SB_NEW);
readBootstrapConfig.invokeVirtualMethod(SB_APPEND_STRING, readBootstrapConfigNameBuilder,
readBootstrapConfig.load("quarkus"));
}
// create readConfig
readConfig = cc.getMethodCreator(C_READ_CONFIG);
// the readConfig name builder
readConfigNameBuilder = readConfig.newInstance(SB_NEW);
readConfig.invokeVirtualMethod(SB_APPEND_STRING, readConfigNameBuilder, readConfig.load("quarkus"));
runTimePatternMap = buildTimeReadResult.getRunTimePatternMap();
accessorFinder = new AccessorFinder();
}
// meant to be called in outside the constructor
private boolean bootstrapConfigSetupNeeded() {
return readBootstrapConfigNameBuilder != null;
}
public void run() {
// in clinit, load the build-time config
// make the build time config global until we read the run time config -
// at run time (when we're ready) we update the factory and then release the build time config
clinit.invokeStaticMethod(QCF_SET_CONFIG, clinitConfig);
// release any previous configuration
final ResultHandle clinitCpr = clinit.invokeStaticMethod(CPR_INSTANCE);
try (TryBlock getConfigTry = clinit.tryBlock()) {
final ResultHandle initialConfigHandle = getConfigTry.invokeVirtualMethod(CPR_GET_CONFIG,
clinitCpr);
getConfigTry.invokeVirtualMethod(CPR_RELEASE_CONFIG, clinitCpr, initialConfigHandle);
// ignore
getConfigTry.addCatch(IllegalStateException.class);
}
installConfiguration(clinitConfig, clinit);
// fill roots map
for (RootDefinition root : roots) {
@@ -361,19 +390,29 @@ public final class RunTimeConfigurationGenerator {
final ConfigPatternMap<Container> buildTimePatternMap = buildTimeConfigResult.getBuildTimePatternMap();
final ConfigPatternMap<Container> buildTimeRunTimePatternMap = buildTimeConfigResult
.getBuildTimeRunTimePatternMap();
final ConfigPatternMap<Container> bootstrapPatternMap = buildTimeConfigResult.getBootstrapPatternMap();
final ConfigPatternMap<Container> runTimePatternMap = buildTimeConfigResult.getRunTimePatternMap();
final BiFunction<Container, Container, Container> combinator = (a, b) -> a == null ? b : a;
final ConfigPatternMap<Container> buildTimeRunTimeIgnored = ConfigPatternMap.merge(buildTimePatternMap,
runTimePatternMap, combinator);
final ConfigPatternMap<Container> runTimeIgnored = ConfigPatternMap.merge(buildTimePatternMap,
buildTimeRunTimePatternMap, combinator);
final ConfigPatternMap<Container> buildTimeRunTimeIgnored = ConfigPatternMap
.merge(ConfigPatternMap.merge(buildTimePatternMap,
runTimePatternMap, combinator), bootstrapPatternMap, combinator);
final ConfigPatternMap<Container> runTimeIgnored = ConfigPatternMap
.merge(ConfigPatternMap.merge(buildTimePatternMap,
buildTimeRunTimePatternMap, combinator), bootstrapPatternMap, combinator);
final MethodDescriptor siParserBody = generateParserBody(buildTimeRunTimePatternMap, buildTimeRunTimeIgnored,
new StringBuilder("siParseKey"), false, false);
final MethodDescriptor rtParserBody = generateParserBody(runTimePatternMap, runTimeIgnored,
new StringBuilder("rtParseKey"), false, true);
// create the bootstrap config if necessary
ResultHandle bootstrapBuilder = null;
if (bootstrapConfigSetupNeeded()) {
bootstrapBuilder = readBootstrapConfig.invokeStaticMethod(CU_CONFIG_BUILDER_WITH_ADD_DISCOVERED,
readBootstrapConfig.load(false), readBootstrapConfig.load(false));
}
// create the run time config
final ResultHandle runTimeBuilder = readConfig.invokeStaticMethod(CU_CONFIG_BUILDER, readConfig.load(true));
@@ -399,16 +438,43 @@ public final class RunTimeConfigurationGenerator {
cc.getFieldCreator(C_SPECIFIED_RUN_TIME_CONFIG_SOURCE).setModifiers(Opcodes.ACC_STATIC | Opcodes.ACC_FINAL);
clinit.writeStaticField(C_SPECIFIED_RUN_TIME_CONFIG_SOURCE, specifiedRunTimeSource);
// add in the custom sources that bootstrap config needs
ResultHandle bootstrapConfigSourcesArray = null;
if (bootstrapConfigSetupNeeded()) {
bootstrapConfigSourcesArray = readBootstrapConfig.newArray(ConfigSource[].class, 4);
// build time config (expanded values)
readBootstrapConfig.writeArrayValue(bootstrapConfigSourcesArray, 0,
readBootstrapConfig.readStaticField(C_BUILD_TIME_CONFIG_SOURCE));
// specified run time config default values
readBootstrapConfig.writeArrayValue(bootstrapConfigSourcesArray, 1,
readBootstrapConfig.readStaticField(C_SPECIFIED_RUN_TIME_CONFIG_SOURCE));
// build time run time visible default config source
readBootstrapConfig.writeArrayValue(bootstrapConfigSourcesArray, 2,
readBootstrapConfig.readStaticField(C_BUILD_TIME_RUN_TIME_DEFAULTS_CONFIG_SOURCE));
// bootstrap config default values
readBootstrapConfig.writeArrayValue(bootstrapConfigSourcesArray, 3,
readBootstrapConfig.readStaticField(C_BOOTSTRAP_DEFAULTS_CONFIG_SOURCE));
}
// add in our custom sources
final ResultHandle array = readConfig.newArray(ConfigSource[].class, 4);
final ResultHandle runtimeConfigSourcesArray = readConfig.newArray(ConfigSource[].class,
bootstrapConfigSetupNeeded() ? 5 : 4);
// build time config (expanded values)
readConfig.writeArrayValue(array, 0, readConfig.readStaticField(C_BUILD_TIME_CONFIG_SOURCE));
readConfig.writeArrayValue(runtimeConfigSourcesArray, 0, readConfig.readStaticField(C_BUILD_TIME_CONFIG_SOURCE));
// specified run time config default values
readConfig.writeArrayValue(array, 1, readConfig.readStaticField(C_SPECIFIED_RUN_TIME_CONFIG_SOURCE));
readConfig.writeArrayValue(runtimeConfigSourcesArray, 1,
readConfig.readStaticField(C_SPECIFIED_RUN_TIME_CONFIG_SOURCE));
// run time config default values
readConfig.writeArrayValue(array, 2, readConfig.readStaticField(C_RUN_TIME_DEFAULTS_CONFIG_SOURCE));
readConfig.writeArrayValue(runtimeConfigSourcesArray, 2,
readConfig.readStaticField(C_RUN_TIME_DEFAULTS_CONFIG_SOURCE));
// build time run time visible default config source
readConfig.writeArrayValue(array, 3, readConfig.readStaticField(C_BUILD_TIME_RUN_TIME_DEFAULTS_CONFIG_SOURCE));
readConfig.writeArrayValue(runtimeConfigSourcesArray, 3,
readConfig.readStaticField(C_BUILD_TIME_RUN_TIME_DEFAULTS_CONFIG_SOURCE));
if (bootstrapConfigSetupNeeded()) {
// bootstrap config default values
readConfig.writeArrayValue(runtimeConfigSourcesArray, 4,
readConfig.readStaticField(C_BOOTSTRAP_DEFAULTS_CONFIG_SOURCE));
}
// add in known converters
for (Class<?> additionalType : additionalTypes) {
@@ -430,28 +496,41 @@ public final class RunTimeConfigurationGenerator {
for (Map.Entry<FieldDescriptor, Class<?>> entry : convertersToRegister.entrySet()) {
final FieldDescriptor descriptor = entry.getKey();
final Class<?> type = entry.getValue();
if (bootstrapConfigSetupNeeded()) {
readBootstrapConfig.invokeVirtualMethod(SRCB_WITH_CONVERTER, bootstrapBuilder,
readBootstrapConfig.loadClass(type),
readBootstrapConfig.load(100), readBootstrapConfig.readStaticField(descriptor));
}
readConfig.invokeVirtualMethod(SRCB_WITH_CONVERTER, runTimeBuilder, readConfig.loadClass(type),
readConfig.load(100), readConfig.readStaticField(descriptor));
}
}
// put them in the builder
readConfig.invokeVirtualMethod(SRCB_WITH_SOURCES, runTimeBuilder, array);
// put sources in the bootstrap builder
if (bootstrapConfigSetupNeeded()) {
readBootstrapConfig.invokeVirtualMethod(SRCB_WITH_SOURCES, bootstrapBuilder, bootstrapConfigSourcesArray);
}
// put sources in the builder
readConfig.invokeVirtualMethod(SRCB_WITH_SOURCES, runTimeBuilder, runtimeConfigSourcesArray);
final ResultHandle runTimeConfig = readConfig.invokeVirtualMethod(SRCB_BUILD, runTimeBuilder);
// install run time config
readConfig.invokeStaticMethod(QCF_SET_CONFIG, runTimeConfig);
// now invalidate the cached config, so the next one to load the config gets the new one
final ResultHandle configProviderResolver = readConfig.invokeStaticMethod(CPR_INSTANCE);
try (TryBlock getConfigTry = readConfig.tryBlock()) {
final ResultHandle initialConfigHandle = getConfigTry.invokeVirtualMethod(CPR_GET_CONFIG,
configProviderResolver);
getConfigTry.invokeVirtualMethod(CPR_RELEASE_CONFIG, configProviderResolver, initialConfigHandle);
// ignore
getConfigTry.addCatch(IllegalStateException.class);
// add the ConfigSourceProvider List passed as the readConfig method param
// (which were generated by the bootstrap config phase - an empty list is passed when there is no bootstrap phase)
readConfig.invokeStaticMethod(CU_ADD_SOURCE_PROVIDERS, runTimeBuilder, readConfig.getMethodParam(0));
ResultHandle bootstrapConfig = null;
if (bootstrapConfigSetupNeeded()) {
bootstrapConfig = readBootstrapConfig.invokeVirtualMethod(SRCB_BUILD, bootstrapBuilder);
installConfiguration(bootstrapConfig, readBootstrapConfig);
}
final ResultHandle runTimeConfig = readConfig.invokeVirtualMethod(SRCB_BUILD, runTimeBuilder);
installConfiguration(runTimeConfig, readConfig);
final ResultHandle clInitOldLen = clinit.invokeVirtualMethod(SB_LENGTH, clinitNameBuilder);
ResultHandle bcOldLen = null;
if (bootstrapConfigSetupNeeded()) {
bcOldLen = readBootstrapConfig.invokeVirtualMethod(SB_LENGTH, readBootstrapConfigNameBuilder);
}
final ResultHandle rcOldLen = readConfig.invokeVirtualMethod(SB_LENGTH, readConfigNameBuilder);
// generate eager config read (both build and run time at once)
@@ -487,6 +566,25 @@ public final class RunTimeConfigurationGenerator {
}
clinit.invokeStaticMethod(initGroup, clinitConfig, clinitNameBuilder, instance);
clinit.invokeVirtualMethod(SB_SET_LENGTH, clinitNameBuilder, clInitOldLen);
} else if (root.getConfigPhase() == ConfigPhase.BOOTSTRAP) {
if (bootstrapConfigSetupNeeded()) {
// config root field is volatile; we initialize and read config from the readBootstrapConfig method
cc.getFieldCreator(rootFieldDescriptor)
.setModifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_VOLATILE);
// construct instance in readBootstrapConfig
final ResultHandle instance = readBootstrapConfig.invokeStaticMethod(ctor);
// assign instance to field
readBootstrapConfig.writeStaticField(rootFieldDescriptor, instance);
if (!rootName.isEmpty()) {
readBootstrapConfig.invokeVirtualMethod(SB_APPEND_CHAR, readBootstrapConfigNameBuilder,
readBootstrapConfig.load('.'));
readBootstrapConfig.invokeVirtualMethod(SB_APPEND_STRING, readBootstrapConfigNameBuilder,
readBootstrapConfig.load(rootName));
}
readBootstrapConfig.invokeStaticMethod(initGroup, bootstrapConfig, readBootstrapConfigNameBuilder,
instance);
readBootstrapConfig.invokeVirtualMethod(SB_SET_LENGTH, readBootstrapConfigNameBuilder, bcOldLen);
}
} else if (root.getConfigPhase() == ConfigPhase.RUN_TIME) {
// config root field is volatile; we initialize and read config from the readConfig method
cc.getFieldCreator(rootFieldDescriptor)
@@ -569,12 +667,12 @@ public final class RunTimeConfigurationGenerator {
mc.returnValue(null);
}
// generate run time entry point
try (MethodCreator mc = cc.getMethodCreator(C_CREATE_RUN_TIME_CONFIG)) {
// generate bootstrap config entry point
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.invokeVirtualMethod(C_READ_CONFIG, instance);
mc.returnValue(null);
mc.invokeVirtualMethod(C_BOOTSTRAP_CONFIG, instance);
mc.returnValue(instance);
}
// wrap it up
@@ -600,6 +698,9 @@ public final class RunTimeConfigurationGenerator {
configurationException, emptyStackTraceElement);
isError.throwException(configurationException);
readBootstrapConfig.returnValue(null);
readBootstrapConfig.close();
readConfig.returnValue(null);
readConfig.close();
@@ -607,40 +708,39 @@ public final class RunTimeConfigurationGenerator {
clinit.close();
cc.close();
// generate run time default values config source class
try (ClassCreator dvcc = ClassCreator.builder().classOutput(classOutput).className(RTDVCS_CLASS_NAME)
.superClass(AbstractRawDefaultConfigSource.class).setFinal(true).build()) {
// implements abstract method AbstractRawDefaultConfigSource#getValue(NameIterator)
try (MethodCreator mc = dvcc.getMethodCreator("getValue", String.class, NameIterator.class)) {
final ResultHandle keyIter = mc.getMethodParam(0);
final MethodDescriptor md = generateDefaultValueParse(dvcc, runTimePatternMap,
new StringBuilder("getDefaultFor"));
if (md != null) {
// there is at least one default value
final BranchResult if1 = mc.ifNonZero(mc.invokeVirtualMethod(NI_HAS_NEXT, keyIter));
try (BytecodeCreator true1 = if1.trueBranch()) {
true1.invokeVirtualMethod(NI_NEXT, keyIter);
final BranchResult if2 = true1
.ifNonZero(true1.invokeVirtualMethod(NI_PREVIOUS_EQUALS, keyIter, true1.load("quarkus")));
try (BytecodeCreator true2 = if2.trueBranch()) {
final ResultHandle result = true2.invokeVirtualMethod(
md, mc.getThis(), keyIter);
true2.returnValue(result);
}
}
}
mc.returnValue(mc.loadNull());
}
if (bootstrapConfigSetupNeeded()) {
// generate bootstrap default values config source class
generateDefaultValuesConfigSourceClass(bootstrapPatternMap, BSDVCS_CLASS_NAME);
}
// generate run time default values config source class
generateDefaultValuesConfigSourceClass(runTimePatternMap, RTDVCS_CLASS_NAME);
// generate build time run time visible default values config source class
try (ClassCreator dvcc = ClassCreator.builder().classOutput(classOutput).className(BTRTDVCS_CLASS_NAME)
generateDefaultValuesConfigSourceClass(buildTimeRunTimePatternMap, BTRTDVCS_CLASS_NAME);
}
private void installConfiguration(ResultHandle config, MethodCreator methodCreator) {
// install config
methodCreator.invokeStaticMethod(QCF_SET_CONFIG, config);
// now invalidate the cached config, so the next one to load the config gets the new one
final ResultHandle configProviderResolver = methodCreator.invokeStaticMethod(CPR_INSTANCE);
try (TryBlock getConfigTry = methodCreator.tryBlock()) {
final ResultHandle initialConfigHandle = getConfigTry.invokeVirtualMethod(CPR_GET_CONFIG,
configProviderResolver);
getConfigTry.invokeVirtualMethod(CPR_RELEASE_CONFIG, configProviderResolver, initialConfigHandle);
// ignore
getConfigTry.addCatch(IllegalStateException.class);
}
}
private void generateDefaultValuesConfigSourceClass(ConfigPatternMap<Container> patternMap, String className) {
try (ClassCreator dvcc = ClassCreator.builder().classOutput(classOutput).className(className)
.superClass(AbstractRawDefaultConfigSource.class).setFinal(true).build()) {
// implements abstract method AbstractRawDefaultConfigSource#getValue(NameIterator)
try (MethodCreator mc = dvcc.getMethodCreator("getValue", String.class, NameIterator.class)) {
final ResultHandle keyIter = mc.getMethodParam(0);
final MethodDescriptor md = generateDefaultValueParse(dvcc, buildTimeRunTimePatternMap,
final MethodDescriptor md = generateDefaultValueParse(dvcc, patternMap,
new StringBuilder("getDefaultFor"));
if (md != null) {
// there is at least one default value

View File

@@ -40,6 +40,16 @@ public final class RootDefinition extends ClassDefinition {
"Run", "Time", "Config"),
"Configuration"),
"Config");
} else if (configPhase == ConfigPhase.BOOTSTRAP) {
trimmedSegments = withoutSuffix(
withoutSuffix(
withoutSuffix(
withoutSuffix(
segments,
"Bootstrap", "Configuration"),
"Bootstrap", "Config"),
"Configuration"),
"Config");
} else {
trimmedSegments = withoutSuffix(
withoutSuffix(

View File

@@ -34,6 +34,7 @@ public class ConfigDescriptionBuildStep {
List<ConfigDescriptionBuildItem> ret = new ArrayList<>();
processConfig(config.getReadResult().getBuildTimePatternMap(), ret, javadoc);
processConfig(config.getReadResult().getBuildTimeRunTimePatternMap(), ret, javadoc);
processConfig(config.getReadResult().getBootstrapPatternMap(), ret, javadoc);
processConfig(config.getReadResult().getRunTimePatternMap(), ret, javadoc);
return ret;
}

View File

@@ -5,12 +5,15 @@ import static io.quarkus.gizmo.MethodDescriptor.ofMethod;
import java.io.File;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import org.eclipse.microprofile.config.spi.ConfigSourceProvider;
import org.graalvm.nativeimage.ImageInfo;
import org.jboss.logging.Logger;
@@ -27,6 +30,7 @@ import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.deployment.builditem.GeneratedClassBuildItem;
import io.quarkus.deployment.builditem.JavaLibraryPathAdditionalPathBuildItem;
import io.quarkus.deployment.builditem.LaunchModeBuildItem;
import io.quarkus.deployment.builditem.MainBootstrapConfigBytecodeRecorderBuildItem;
import io.quarkus.deployment.builditem.MainBytecodeRecorderBuildItem;
import io.quarkus.deployment.builditem.MainClassBuildItem;
import io.quarkus.deployment.builditem.ObjectSubstitutionBuildItem;
@@ -50,6 +54,7 @@ import io.quarkus.gizmo.TryBlock;
import io.quarkus.runtime.Application;
import io.quarkus.runtime.LaunchMode;
import io.quarkus.runtime.NativeImageRuntimePropertiesRecorder;
import io.quarkus.runtime.RuntimeValue;
import io.quarkus.runtime.StartupContext;
import io.quarkus.runtime.StartupTask;
import io.quarkus.runtime.Timing;
@@ -68,6 +73,7 @@ class MainClassBuildStep {
MainClassBuildItem build(List<StaticBytecodeRecorderBuildItem> staticInitTasks,
List<ObjectSubstitutionBuildItem> substitutions,
List<MainBytecodeRecorderBuildItem> mainMethod,
List<MainBootstrapConfigBytecodeRecorderBuildItem> mainBootstrapConfig,
List<SystemPropertyBuildItem> properties,
List<JavaLibraryPathAdditionalPathBuildItem> javaLibraryPathAdditionalPaths,
Optional<SslTrustStoreSystemPropertyBuildItem> sslTrustStoreSystemProperty,
@@ -139,19 +145,7 @@ class MainClassBuildStep {
for (StaticBytecodeRecorderBuildItem holder : staticInitTasks) {
final BytecodeRecorderImpl recorder = holder.getBytecodeRecorder();
if (!recorder.isEmpty()) {
// Register substitutions in all recorders
for (ObjectSubstitutionBuildItem sub : substitutions) {
ObjectSubstitutionBuildItem.Holder holder1 = sub.holder;
recorder.registerSubstitution(holder1.from, holder1.to, holder1.substitution);
}
for (BytecodeRecorderObjectLoaderBuildItem item : loaders) {
recorder.registerObjectLoader(item.getObjectLoader());
}
recorder.writeBytecode(gizmoOutput);
ResultHandle dup = tryBlock.newInstance(ofConstructor(recorder.getClassName()));
tryBlock.invokeInterfaceMethod(ofMethod(StartupTask.class, "deploy", void.class, StartupContext.class), dup,
startupContext);
writeRecordedBytecode(recorder, substitutions, loaders, gizmoOutput, startupContext, tryBlock);
}
}
tryBlock.returnValue(null);
@@ -213,24 +207,61 @@ class MainClassBuildStep {
tryBlock = mv.tryBlock();
// Load the run time configuration
tryBlock.invokeStaticMethod(RunTimeConfigurationGenerator.C_CREATE_RUN_TIME_CONFIG);
// Load the bootstrap configuration
ResultHandle generatedConfig = tryBlock.invokeStaticMethod(RunTimeConfigurationGenerator.C_CREATE_BOOTSTRAP_CONFIG);
if (mainBootstrapConfig.isEmpty()) {
tryBlock.invokeVirtualMethod(RunTimeConfigurationGenerator.C_READ_CONFIG, generatedConfig,
tryBlock.invokeStaticMethod(ofMethod(Collections.class, "emptyList", List.class)));
} else {
/*
* This is not that great, since it is no by no means a general way of executing things before the main bytecode
* stuff executes.
* It's tailored made to support the bootstrap configuration stuff and would need to be rethought if we need
* a general mechanism of executing things before the main bytecode stuff
* (which would likely involve a new runtime phase)
*
* What this loop does is go through the MainBootstrapConfigBytecodeRecorderBuildItem objects which are guaranteed
* to be constructed from build steps that return RunTimeConfigurationSourceValueBuildItem - thus ensuring that
* the generated StartupTask will write its result (which is a RuntimeValue<ConfigSourceProvider>
* configSourcesValue) into the StartupContext.
* This value is then pulled out of the StartupContext by using the getLastValue method. All the
* ConfigSourceProvider objects are then collected and passed to Config.readConfig()
*/
ResultHandle configSourcesProvidersList = tryBlock.newInstance(ofConstructor(ArrayList.class, int.class),
tryBlock.load(mainBootstrapConfig.size()));
for (MainBootstrapConfigBytecodeRecorderBuildItem holder : mainBootstrapConfig) {
final BytecodeRecorderImpl recorder = holder.getBytecodeRecorder();
if (!recorder.isEmpty()) {
writeRecordedBytecode(recorder, substitutions, loaders, gizmoOutput, startupContext, tryBlock);
// we now get the result of the recorder invocation
ResultHandle isLastValueSet = tryBlock.invokeVirtualMethod(
ofMethod(StartupContext.class, "isLastValueSet", boolean.class), startupContext);
BytecodeCreator isLastValueSetTrue = tryBlock.ifNonZero(isLastValueSet).trueBranch();
ResultHandle getLastValue = isLastValueSetTrue
.invokeVirtualMethod(ofMethod(StartupContext.class, "getLastValue", Object.class), startupContext);
BytecodeCreator lastValueNotNull = isLastValueSetTrue.ifNull(getLastValue).falseBranch();
ResultHandle runtimeValue = lastValueNotNull.checkCast(getLastValue, RuntimeValue.class);
ResultHandle getValue = lastValueNotNull
.invokeVirtualMethod(MethodDescriptor.ofMethod(RuntimeValue.class, "getValue", Object.class),
runtimeValue);
ResultHandle configSourceProvider = lastValueNotNull.checkCast(getValue, ConfigSourceProvider.class);
lastValueNotNull.invokeVirtualMethod(
MethodDescriptor.ofMethod(ArrayList.class, "add", boolean.class, Object.class),
configSourcesProvidersList, configSourceProvider);
lastValueNotNull.breakScope();
isLastValueSetTrue.breakScope();
}
}
tryBlock.invokeVirtualMethod(RunTimeConfigurationGenerator.C_READ_CONFIG, generatedConfig,
configSourcesProvidersList);
}
for (MainBytecodeRecorderBuildItem holder : mainMethod) {
final BytecodeRecorderImpl recorder = holder.getBytecodeRecorder();
if (!recorder.isEmpty()) {
// Register substitutions in all recorders
for (ObjectSubstitutionBuildItem sub : substitutions) {
ObjectSubstitutionBuildItem.Holder holder1 = sub.holder;
recorder.registerSubstitution(holder1.from, holder1.to, holder1.substitution);
}
for (BytecodeRecorderObjectLoaderBuildItem item : loaders) {
recorder.registerObjectLoader(item.getObjectLoader());
}
recorder.writeBytecode(gizmoOutput);
ResultHandle dup = tryBlock.newInstance(ofConstructor(recorder.getClassName()));
tryBlock.invokeInterfaceMethod(ofMethod(StartupTask.class, "deploy", void.class, StartupContext.class), dup,
startupContext);
writeRecordedBytecode(recorder, substitutions, loaders, gizmoOutput, startupContext, tryBlock);
}
}
@@ -294,4 +325,20 @@ class MainClassBuildStep {
return new MainClassBuildItem(MAIN_CLASS);
}
private void writeRecordedBytecode(BytecodeRecorderImpl recorder, List<ObjectSubstitutionBuildItem> substitutions,
List<BytecodeRecorderObjectLoaderBuildItem> loaders, GeneratedClassGizmoAdaptor gizmoOutput,
ResultHandle startupContext, BytecodeCreator bytecodeCreator) {
for (ObjectSubstitutionBuildItem sub : substitutions) {
ObjectSubstitutionBuildItem.Holder holder1 = sub.holder;
recorder.registerSubstitution(holder1.from, holder1.to, holder1.substitution);
}
for (BytecodeRecorderObjectLoaderBuildItem item : loaders) {
recorder.registerObjectLoader(item.getObjectLoader());
}
recorder.writeBytecode(gizmoOutput);
ResultHandle dup = bytecodeCreator.newInstance(ofConstructor(recorder.getClassName()));
bytecodeCreator.invokeInterfaceMethod(ofMethod(StartupTask.class, "deploy", void.class, StartupContext.class), dup,
startupContext);
}
}

View File

@@ -82,7 +82,7 @@ public class StartupActionImpl implements StartupAction {
try {
final Class<?> configClass = Class.forName(RunTimeConfigurationGenerator.CONFIG_CLASS_NAME, true,
runtimeClassLoader);
configClass.getDeclaredMethod(RunTimeConfigurationGenerator.C_CREATE_RUN_TIME_CONFIG.getName())
configClass.getDeclaredMethod(RunTimeConfigurationGenerator.C_CREATE_BOOTSTRAP_CONFIG.getName())
.invoke(null);
} catch (Throwable t2) {
t.addSuppressed(t2);

View File

@@ -74,6 +74,7 @@ final public class Constants {
Constants.MEMORY_SIZE_NOTE_ANCHOR, "MemorySize");
public static final String CONFIG_PHASE_RUNTIME_ILLUSTRATION = "icon:cogs[title=Overridable at runtime]";
public static final String CONFIG_PHASE_BOOTSTRAP_ILLUSTRATION = "icon:cogs[title=Bootstrap - Overridable at runtime]";
public static final String CONFIG_PHASE_BUILD_TIME_ILLUSTRATION = "icon:archive[title=Fixed at build time]";
public static final String CONFIG_PHASE_LEGEND = String.format(
"%n%s Configuration property fixed at build time - %s Configuration property overridable at runtime %n",

View File

@@ -6,6 +6,8 @@ import io.quarkus.annotation.processor.Constants;
public enum ConfigPhase implements Comparable<ConfigPhase> {
RUN_TIME("The configuration is overridable at runtime", Constants.CONFIG_PHASE_RUNTIME_ILLUSTRATION, "RunTime"),
BOOTSTRAP("The configuration is used to bootstrap runtime Config Sources and is overridable at runtime",
Constants.CONFIG_PHASE_BOOTSTRAP_ILLUSTRATION, "Bootstrap"),
BUILD_TIME("The configuration is not overridable at runtime", Constants.CONFIG_PHASE_BUILD_TIME_ILLUSTRATION, "BuildTime"),
BUILD_AND_RUN_TIME_FIXED("The configuration is not overridable at runtime", Constants.CONFIG_PHASE_BUILD_TIME_ILLUSTRATION,
"BuildTime");
@@ -37,6 +39,20 @@ public enum ConfigPhase implements Comparable<ConfigPhase> {
return -1;
}
}
case BOOTSTRAP: {
switch (secondPhase) {
case BUILD_TIME:
return 1;
case BUILD_AND_RUN_TIME_FIXED:
return 1;
case BOOTSTRAP:
return 0;
case RUN_TIME:
return 1;
default:
return 1;
}
}
case RUN_TIME: {
switch (secondPhase) {
case RUN_TIME:

View File

@@ -14,6 +14,9 @@ public class StartupContext implements Closeable {
private static final Logger LOG = Logger.getLogger(StartupContext.class);
private final Map<String, Object> values = new HashMap<>();
private Object lastValue;
// this is done to distinguish between the value never having been set and having been set as null
private boolean lastValueSet = false;
private final List<Runnable> shutdownTasks = new ArrayList<>();
private final List<Runnable> lastShutdownTasks = new ArrayList<>();
private final ShutdownContext shutdownContext = new ShutdownContext() {
@@ -34,12 +37,22 @@ public class StartupContext implements Closeable {
public void putValue(String name, Object value) {
values.put(name, value);
lastValueSet = true;
this.lastValue = value;
}
public Object getValue(String name) {
return values.get(name);
}
public Object getLastValue() {
return lastValue;
}
public boolean isLastValueSet() {
return lastValueSet;
}
@Override
public void close() {
runAllInReverseOrder(shutdownTasks);

View File

@@ -9,6 +9,13 @@ public enum ConfigPhase {
* Values are read and available for usage at build time, and available on a read-only basis at run time.
*/
BUILD_AND_RUN_TIME_FIXED(true, true, false),
/**
* Values are read and available for usage at run time and are re-read on each program execution. These values
* are used to configure ConfigSourceProvider implementations
*/
BOOTSTRAP(false, true, true),
/**
* Values are read and available for usage at run time and are re-read on each program execution.
*/

View File

@@ -10,6 +10,7 @@ import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
@@ -52,13 +53,18 @@ public final class ConfigUtils {
return size -> new TreeSet<>();
}
public static SmallRyeConfigBuilder configBuilder(final boolean runTime) {
return configBuilder(runTime, true);
}
/**
* Get the basic configuration builder.
*
* @param runTime {@code true} if the configuration is run time, {@code false} if build time
* @param addDiscovered {@code true} if the ConfigSource and Converter objects should be auto-discovered
* @return the configuration builder
*/
public static SmallRyeConfigBuilder configBuilder(final boolean runTime) {
public static SmallRyeConfigBuilder configBuilder(final boolean runTime, final boolean addDiscovered) {
final SmallRyeConfigBuilder builder = new SmallRyeConfigBuilder();
final ApplicationPropertiesConfigSource.InFileSystem inFileSystem = new ApplicationPropertiesConfigSource.InFileSystem();
final ApplicationPropertiesConfigSource.InJar inJar = new ApplicationPropertiesConfigSource.InJar();
@@ -83,8 +89,10 @@ public final class ConfigUtils {
sources.add(new SysPropConfigSource());
builder.withSources(sources.toArray(new ConfigSource[0]));
}
builder.addDiscoveredSources();
builder.addDiscoveredConverters();
if (addDiscovered) {
builder.addDiscoveredSources();
builder.addDiscoveredConverters();
}
return builder;
}
@@ -101,6 +109,18 @@ public final class ConfigUtils {
}
}
/**
* Add a configuration source providers to the builder.
*
* @param builder the builder
* @param providers the providers to add
*/
public static void addSourceProviders(SmallRyeConfigBuilder builder, Collection<ConfigSourceProvider> providers) {
for (ConfigSourceProvider provider : providers) {
addSourceProvider(builder, provider);
}
}
static class EnvConfigSource implements ConfigSource {
static final Pattern REP_PATTERN = Pattern.compile("[^a-zA-Z0-9_]");

View File

@@ -0,0 +1,100 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-integration-test-bootstrap-config</artifactId>
<version>999-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
<artifactId>quarkus-integration-test-bootstrap-config-application</artifactId>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-integration-test-bootstrap-config-extension</artifactId>
<version>999-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>${compiler-plugin.version}</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<systemProperties>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
</systemProperties>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>native</id>
<activation>
<property>
<name>native</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
<configuration>
<systemProperties>
<native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path>
</systemProperties>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<properties>
<quarkus.package.type>native</quarkus.package.type>
</properties>
</profile>
</profiles>
</project>

View File

@@ -0,0 +1,18 @@
package io.quarkus.it.bootstrap.config;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import org.eclipse.microprofile.config.inject.ConfigProperty;
@Path("/greeting")
public class GreetingResource {
@ConfigProperty(name = "foo.key.i2")
String propertyFromBootstrapConfig;
@GET
public String greet() {
return "hello " + propertyFromBootstrapConfig;
}
}

View File

@@ -0,0 +1,2 @@
quarkus.dummy.name=foo
quarkus.application.name=dummy

View File

@@ -0,0 +1,23 @@
package io.quarkus.it.bootstrap.config;
import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.containsString;
import org.junit.jupiter.api.Test;
import io.quarkus.test.junit.QuarkusTest;
@QuarkusTest
class GreetingResourceTest {
@Test
void testEndpoint() {
given()
.when().get("/greeting")
.then()
.statusCode(200)
.body(containsString("hello"))
.body(containsString("dummy2"));
}
}

View File

@@ -0,0 +1,7 @@
package io.quarkus.it.bootstrap.config;
import io.quarkus.test.junit.NativeImageTest;
@NativeImageTest
class GreetingResourceTestIT extends GreetingResourceTest {
}

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-integration-test-bootstrap-config-extension-parent</artifactId>
<version>999-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
<artifactId>quarkus-integration-test-bootstrap-config-extension-deployment</artifactId>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-core-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-integration-test-bootstrap-config-extension</artifactId>
<version>999-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-extension-processor</artifactId>
<version>${project.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,26 @@
package io.quarkus.it.bootstrap.config.extension.deployment;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.ExecutionTime;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.RunTimeConfigurationSourceValueBuildItem;
import io.quarkus.it.bootstrap.config.extension.DummyBootstrapRecorder;
import io.quarkus.it.bootstrap.config.extension.DummyBootstrapRecorder2;
import io.quarkus.it.bootstrap.config.extension.DummyConfig;
import io.quarkus.runtime.ApplicationConfig;
public class DummyBootstrapConfigBuildStep {
@BuildStep
@Record(ExecutionTime.RUNTIME_INIT)
public RunTimeConfigurationSourceValueBuildItem dummyRecorder(DummyBootstrapRecorder recorder, DummyConfig dummyConfig,
ApplicationConfig applicationConfig) {
return new RunTimeConfigurationSourceValueBuildItem(recorder.create(dummyConfig, applicationConfig));
}
@BuildStep
@Record(ExecutionTime.RUNTIME_INIT)
public RunTimeConfigurationSourceValueBuildItem dummyRecorder2(DummyBootstrapRecorder2 recorder) {
return new RunTimeConfigurationSourceValueBuildItem(recorder.create());
}
}

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-build-parent</artifactId>
<version>999-SNAPSHOT</version>
<relativePath>../../../build-parent/pom.xml</relativePath>
</parent>
<artifactId>quarkus-integration-test-bootstrap-config-extension-parent</artifactId>
<packaging>pom</packaging>
<modules>
<module>deployment</module>
<module>runtime</module>
</modules>
</project>

View File

@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-integration-test-bootstrap-config-extension-parent</artifactId>
<version>999-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
<artifactId>quarkus-integration-test-bootstrap-config-extension</artifactId>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-core</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-bootstrap-maven-plugin</artifactId>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-extension-processor</artifactId>
<version>${project.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,15 @@
package io.quarkus.it.bootstrap.config.extension;
import org.eclipse.microprofile.config.spi.ConfigSourceProvider;
import io.quarkus.runtime.ApplicationConfig;
import io.quarkus.runtime.RuntimeValue;
import io.quarkus.runtime.annotations.Recorder;
@Recorder
public class DummyBootstrapRecorder {
public RuntimeValue<ConfigSourceProvider> create(DummyConfig dummyConfig, ApplicationConfig applicationConfig) {
return new RuntimeValue<>(new DummyConfigSourceProvider(dummyConfig, applicationConfig));
}
}

View File

@@ -0,0 +1,22 @@
package io.quarkus.it.bootstrap.config.extension;
import java.util.Collections;
import org.eclipse.microprofile.config.spi.ConfigSource;
import org.eclipse.microprofile.config.spi.ConfigSourceProvider;
import io.quarkus.runtime.RuntimeValue;
import io.quarkus.runtime.annotations.Recorder;
@Recorder
public class DummyBootstrapRecorder2 {
public RuntimeValue<ConfigSourceProvider> create() {
return new RuntimeValue<>(new ConfigSourceProvider() {
@Override
public Iterable<ConfigSource> getConfigSources(ClassLoader forClassLoader) {
return Collections.emptyList();
}
});
}
}

View File

@@ -0,0 +1,20 @@
package io.quarkus.it.bootstrap.config.extension;
import io.quarkus.runtime.annotations.ConfigItem;
import io.quarkus.runtime.annotations.ConfigPhase;
import io.quarkus.runtime.annotations.ConfigRoot;
@ConfigRoot(phase = ConfigPhase.BOOTSTRAP)
public class DummyConfig {
/**
* dummy name
*/
public String name;
/**
* dummy times
*/
@ConfigItem(defaultValue = "2")
public Integer times;
}

View File

@@ -0,0 +1,77 @@
package io.quarkus.it.bootstrap.config.extension;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.eclipse.microprofile.config.spi.ConfigSource;
import org.eclipse.microprofile.config.spi.ConfigSourceProvider;
import io.quarkus.runtime.ApplicationConfig;
/**
* A dummy provider that returns a single ConfigSource which contains
* as many properties as DummyConfig.times indicates and where the
* key depends on DummyConfig.name
*/
public class DummyConfigSourceProvider implements ConfigSourceProvider {
private final DummyConfig dummyConfig;
private final ApplicationConfig applicationConfig;
public DummyConfigSourceProvider(DummyConfig dummyConfig, ApplicationConfig applicationConfig) {
this.dummyConfig = dummyConfig;
this.applicationConfig = applicationConfig;
}
@Override
public Iterable<ConfigSource> getConfigSources(ClassLoader forClassLoader) {
InMemoryConfigSource configSource = new InMemoryConfigSource(Integer.MIN_VALUE, "dummy config source");
for (int i = 0; i < dummyConfig.times; i++) {
configSource.add(dummyConfig.name + ".key.i" + (i + 1), applicationConfig.name.get() + (i + 1));
}
return Collections.singletonList(configSource);
}
private static final class InMemoryConfigSource implements ConfigSource {
private final Map<String, String> values = new HashMap<>();
private final int ordinal;
private final String name;
private InMemoryConfigSource(int ordinal, String name) {
this.ordinal = ordinal;
this.name = name;
}
public void add(String key, String value) {
values.put(key, value);
}
@Override
public Map<String, String> getProperties() {
return values;
}
@Override
public Set<String> getPropertyNames() {
return values.keySet();
}
@Override
public int getOrdinal() {
return ordinal;
}
@Override
public String getValue(String propertyName) {
return values.get(propertyName);
}
@Override
public String getName() {
return name;
}
}
}

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-integration-tests-parent</artifactId>
<version>999-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
<artifactId>quarkus-integration-test-bootstrap-config</artifactId>
<name>Quarkus - Integration Tests - Bootstrap Config</name>
<packaging>pom</packaging>
<modules>
<module>extension</module>
<module>application</module>
</modules>
</project>

View File

@@ -94,6 +94,7 @@
<module>logging-gelf</module>
<module>cache</module>
<module>qute</module>
<module>bootstrap-config</module>
</modules>
<build>