Skip to content

Commit

Permalink
Merge pull request #35566 from aloubyansky/dump-config-when-recorded-…
Browse files Browse the repository at this point in the history
…unavailable

Added an option to dump current build config when the config recorded during the previous build isn't available
  • Loading branch information
gsmet committed Aug 26, 2023
2 parents 186ed0b + 4a072b1 commit b25bd38
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 55 deletions.
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package io.quarkus.deployment;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
Expand All @@ -14,6 +16,7 @@
import java.util.ServiceLoader;
import java.util.StringJoiner;
import java.util.function.Consumer;
import java.util.function.Function;

import org.eclipse.microprofile.config.Config;
import org.jboss.logging.Logger;
Expand All @@ -24,14 +27,17 @@
import io.quarkus.bootstrap.prebuild.CodeGenException;
import io.quarkus.deployment.codegen.CodeGenData;
import io.quarkus.deployment.configuration.BuildTimeConfigurationReader;
import io.quarkus.deployment.configuration.tracker.ConfigTrackingConfig;
import io.quarkus.deployment.configuration.tracker.ConfigTrackingValueTransformer;
import io.quarkus.deployment.configuration.tracker.ConfigTrackingWriter;
import io.quarkus.deployment.dev.DevModeContext;
import io.quarkus.deployment.dev.DevModeContext.ModuleInfo;
import io.quarkus.maven.dependency.ResolvedDependency;
import io.quarkus.paths.OpenPathTree;
import io.quarkus.paths.PathCollection;
import io.quarkus.runtime.LaunchMode;
import io.quarkus.runtime.util.ClassPathUtils;
import io.smallrye.config.SmallRyeConfig;

/**
* A set of methods to initialize and execute {@link CodeGenProvider}s.
Expand Down Expand Up @@ -187,47 +193,104 @@ public static boolean trigger(ClassLoader deploymentClassLoader,
}

/**
* Initializes an application build time configuration and returns current values of properties
* passed in as {@code originalProperties}.
* Initializes an application build time configuration and dumps current values of properties
* passed in as {@code previouslyRecordedProperties} to a file.
*
* @param appModel application model
* @param launchMode launch mode
* @param buildSystemProps build system (or project) properties
* @param deploymentClassLoader build classloader
* @param originalProperties properties to read from the initialized configuration
* @return current values of the passed in original properties
* @param previouslyRecordedProperties properties to read from the initialized configuration
* @param outputFile output file
*/
public static Properties readCurrentConfigValues(ApplicationModel appModel, String launchMode,
Properties buildSystemProps,
QuarkusClassLoader deploymentClassLoader, Properties originalProperties) {
public static void dumpCurrentConfigValues(ApplicationModel appModel, String launchMode, Properties buildSystemProps,
QuarkusClassLoader deploymentClassLoader, Properties previouslyRecordedProperties,
Path outputFile) {
final LaunchMode mode = LaunchMode.valueOf(launchMode);
if (previouslyRecordedProperties.isEmpty()) {
try {
readConfig(appModel, mode, buildSystemProps, deploymentClassLoader, configReader -> {
var config = configReader.initConfiguration(mode, buildSystemProps, appModel.getPlatformProperties());
final Map<String, String> allProps = new HashMap<>();
for (String name : config.getPropertyNames()) {
allProps.put(name, ConfigTrackingValueTransformer.asString(config.getConfigValue(name)));
}
ConfigTrackingWriter.write(allProps,
config.unwrap(SmallRyeConfig.class).getConfigMapping(ConfigTrackingConfig.class),
configReader.readConfiguration(config),
outputFile);
return null;
});
} catch (CodeGenException e) {
throw new RuntimeException("Failed to load application configuration", e);
}
return;
}
Config config = null;
try {
config = getConfig(appModel, LaunchMode.valueOf(launchMode), buildSystemProps, deploymentClassLoader);
config = getConfig(appModel, mode, buildSystemProps, deploymentClassLoader);
} catch (CodeGenException e) {
throw new RuntimeException("Failed to load application configuration", e);
}
var valueTransformer = ConfigTrackingValueTransformer.newInstance(config);
final Properties currentValues = new Properties(originalProperties.size());
for (var originalProp : originalProperties.entrySet()) {
var name = originalProp.getKey().toString();
final Properties currentValues = new Properties(previouslyRecordedProperties.size());
for (var prevProp : previouslyRecordedProperties.entrySet()) {
var name = prevProp.getKey().toString();
var currentValue = config.getConfigValue(name);
final String current = valueTransformer.transform(name, currentValue);
if (!originalProp.getValue().equals(current)) {
log.info("Option " + name + " has changed since the last build from "
+ originalProp.getValue() + " to " + current);
var originalValue = prevProp.getValue();
if (!originalValue.equals(current)) {
log.info("Option " + name + " has changed since the last build from " + originalValue + " to " + current);
}
if (current != null) {
currentValues.put(name, current);
}
}
return currentValues;

final List<String> names = new ArrayList<>(currentValues.stringPropertyNames());
Collections.sort(names);

final Path outputDir = outputFile.getParent();
if (outputDir != null && !Files.exists(outputDir)) {
try {
Files.createDirectories(outputDir);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
try (BufferedWriter writer = Files.newBufferedWriter(outputFile)) {
for (var name : names) {
ConfigTrackingWriter.write(writer, name, currentValues.getProperty(name));
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

public static Config getConfig(ApplicationModel appModel, LaunchMode launchMode, Properties buildSystemProps,
QuarkusClassLoader deploymentClassLoader) throws CodeGenException {
return readConfig(appModel, launchMode, buildSystemProps, deploymentClassLoader,
configReader -> configReader.initConfiguration(launchMode, buildSystemProps, appModel.getPlatformProperties()));
}

public static <T> T readConfig(ApplicationModel appModel, LaunchMode launchMode, Properties buildSystemProps,
QuarkusClassLoader deploymentClassLoader, Function<BuildTimeConfigurationReader, T> function)
throws CodeGenException {
final Map<String, List<String>> unavailableConfigServices = getUnavailableConfigServices(appModel.getAppArtifact(),
deploymentClassLoader);
final ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
if (!unavailableConfigServices.isEmpty()) {
var sb = new StringBuilder();
sb.append(
"The following services are not (yet) available and will be disabled during configuration initialization at the current build phase:");
for (Map.Entry<String, List<String>> missingService : unavailableConfigServices.entrySet()) {
sb.append(System.lineSeparator());
for (String s : missingService.getValue()) {
sb.append("- ").append(s);
}
}
log.warn(sb.toString());

final Map<String, List<String>> allConfigServices = new HashMap<>(unavailableConfigServices.size());
final Map<String, byte[]> allowedConfigServices = new HashMap<>(unavailableConfigServices.size());
final Map<String, byte[]> bannedConfigServices = new HashMap<>(unavailableConfigServices.size());
Expand Down Expand Up @@ -266,14 +329,15 @@ public static Config getConfig(ApplicationModel appModel, LaunchMode launchMode,
configClBuilder.addBannedElement(new MemoryClassPathElement(bannedConfigServices, true));
}
deploymentClassLoader = configClBuilder.build();
Thread.currentThread().setContextClassLoader(deploymentClassLoader);
}
try {
return new BuildTimeConfigurationReader(deploymentClassLoader).initConfiguration(launchMode, buildSystemProps,
appModel.getPlatformProperties());
return function.apply(new BuildTimeConfigurationReader(deploymentClassLoader));
} catch (Exception e) {
throw new CodeGenException("Failed to initialize application configuration", e);
} finally {
if (!unavailableConfigServices.isEmpty()) {
Thread.currentThread().setContextClassLoader(originalClassLoader);
deploymentClassLoader.close();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,11 @@ public static ConfigTrackingValueTransformer newInstance(ConfigTrackingConfig co
* @return non-null string value for a given {@link org.eclipse.microprofile.config.ConfigValue} instance
*/
public static String asString(ConfigValue value) {
return value == null ? NOT_CONFIGURED : value.getValue();
if (value == null) {
return NOT_CONFIGURED;
}
var strValue = value.getValue();
return strValue == null ? NOT_CONFIGURED : strValue;
}

private final String userHomeDir;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ private static boolean matches(String name, List<Pattern> patterns) {

/**
* Configuration writer that will persist collected configuration options and their values
* to a file.
* to a file derived from the config.
*/
public static void write(Map<String, String> readOptions, ConfigTrackingConfig config,
BuildTimeConfigurationReader.ReadResult configReadResult,
Expand Down Expand Up @@ -64,6 +64,15 @@ public static void write(Map<String, String> readOptions, ConfigTrackingConfig c
}
}

write(readOptions, config, configReadResult, file);
}

/**
* Configuration writer that will persist collected configuration options and their values
* to a file.
*/
public static void write(Map<String, String> readOptions, ConfigTrackingConfig config,
BuildTimeConfigurationReader.ReadResult configReadResult, Path file) {
final List<Pattern> excludePatterns = config.getExcludePatterns();
final ConfigTrackingValueTransformer valueTransformer = ConfigTrackingValueTransformer.newInstance(config);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
package io.quarkus.maven;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Properties;

import org.apache.maven.plugin.MojoExecutionException;
Expand All @@ -23,7 +18,6 @@
import io.quarkus.bootstrap.app.CuratedApplication;
import io.quarkus.bootstrap.classloading.QuarkusClassLoader;
import io.quarkus.bootstrap.model.ApplicationModel;
import io.quarkus.deployment.configuration.tracker.ConfigTrackingWriter;
import io.quarkus.runtime.LaunchMode;

/**
Expand Down Expand Up @@ -58,6 +52,12 @@ public class TrackConfigChangesMojo extends QuarkusBootstrapMojo {
@Parameter(property = "quarkus.recorded-build-config.file", required = false)
File recordedBuildConfigFile;

/**
* Whether to dump the current build configuration in case the configuration from the previous build isn't found
*/
@Parameter(defaultValue = "false", property = "quarkus.track-config-changes.dump-current-when-recorded-unavailable")
boolean dumpCurrentWhenRecordedUnavailable;

@Override
protected boolean beforeExecute() throws MojoExecutionException, MojoFailureException {
if (skip) {
Expand Down Expand Up @@ -102,16 +102,17 @@ protected void doExecute() throws MojoExecutionException, MojoFailureException {
compareFile = recordedBuildConfigDirectory.toPath().resolve(this.recordedBuildConfigFile.toPath());
}

if (!Files.exists(compareFile)) {
final Properties compareProps = new Properties();
if (Files.exists(compareFile)) {
try (BufferedReader reader = Files.newBufferedReader(compareFile)) {
compareProps.load(reader);
} catch (IOException e) {
throw new RuntimeException("Failed to read " + compareFile, e);
}
} else if (!dumpCurrentWhenRecordedUnavailable) {
getLog().info(compareFile + " not found");
return;
}
final Properties compareProps = new Properties();
try (BufferedReader reader = Files.newBufferedReader(compareFile)) {
compareProps.load(reader);
} catch (IOException e) {
throw new RuntimeException("Failed to read " + compareFile, e);
}

CuratedApplication curatedApplication = null;
QuarkusClassLoader deploymentClassLoader = null;
Expand All @@ -124,11 +125,11 @@ protected void doExecute() throws MojoExecutionException, MojoFailureException {
Thread.currentThread().setContextClassLoader(deploymentClassLoader);

final Class<?> codeGenerator = deploymentClassLoader.loadClass("io.quarkus.deployment.CodeGenerator");
final Method dumpConfig = codeGenerator.getMethod("readCurrentConfigValues", ApplicationModel.class, String.class,
Properties.class, QuarkusClassLoader.class, Properties.class);
actualProps = (Properties) dumpConfig.invoke(null, curatedApplication.getApplicationModel(),
final Method dumpConfig = codeGenerator.getMethod("dumpCurrentConfigValues", ApplicationModel.class, String.class,
Properties.class, QuarkusClassLoader.class, Properties.class, Path.class);
dumpConfig.invoke(null, curatedApplication.getApplicationModel(),
launchMode.name(), getBuildSystemProperties(true),
deploymentClassLoader, compareProps);
deploymentClassLoader, compareProps, targetFile);
} catch (Exception any) {
throw new MojoExecutionException("Failed to bootstrap Quarkus application", any);
} finally {
Expand All @@ -140,24 +141,5 @@ protected void doExecute() throws MojoExecutionException, MojoFailureException {
deploymentClassLoader.close();
}
}

final List<String> names = new ArrayList<>(actualProps.stringPropertyNames());
Collections.sort(names);

final Path outputDir = targetFile.getParent();
if (outputDir != null && !Files.exists(outputDir)) {
try {
Files.createDirectories(outputDir);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
try (BufferedWriter writer = Files.newBufferedWriter(targetFile)) {
for (var name : names) {
ConfigTrackingWriter.write(writer, name, actualProps.getProperty(name));
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}
12 changes: 12 additions & 0 deletions docs/src/main/asciidoc/config-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,18 @@ Maven projects could add the following goal to their `quarkus-maven-plugin` conf
The `track-config-changes` goal looks for `${project.basedir}/.quarkus/quarkus-prod-config-dump` (file name and directory are configurable) and, if the file already exists, checks whether the values stored in the config dump have changed.
It will log the changed options and save the current values of each of the options present in `${project.basedir}/.quarkus/quarkus-prod-config-dump` in `${project.basedir}/target/quarkus-prod-config.check` (the target file name and location can be configured). If the build time configuration has not changed since the last build both `${project.basedir}/.quarkus/quarkus-prod-config-dump` and `${project.basedir}/.quarkus/quarkus-prod-config-dump` will be identical.

==== Dump current build configuration when the recorded configuration isn't found

By default, `track-config-changes` looks for the configuration recorded during previous build and does nothing if it's not found. Enabling `dumpCurrentWhenRecordedUnavailable` parameter will make it dump the current build configuration
options taking into account `quarkus.config-tracking.*` configuration.

[IMPORTANT]
====
Unlike the build configuration options recorded during the `quarkus:build` goal, configuration options saved by `quarkus:track-config-changes` with `dumpCurrentWhenRecordedUnavailable` enabled will include all the build configuration
options exposed by a `org.eclipse.microprofile.config.Config` instance. Which means this report may include some build configuration options that will not be used by the Quarkus application build process but also may be missing some
build configuration options since MicroProfile Config specification allows configuration sources not to expose all the property names they provide to users.
====

[[additional-information]]
== Additional Information

Expand Down

0 comments on commit b25bd38

Please sign in to comment.