diff --git a/.github/workflows/ci-actions-incremental.yml b/.github/workflows/ci-actions-incremental.yml
index 11c9a43357c29..f23cbc7c23ac1 100644
--- a/.github/workflows/ci-actions-incremental.yml
+++ b/.github/workflows/ci-actions-incremental.yml
@@ -1072,6 +1072,11 @@ jobs:
build-scan-capture-strategy: ON_DEMAND
job-name: "Native Tests - ${{matrix.category}}"
wrapper-init: true
+ - name: Cache Quarkus metadata
+ uses: actions/cache@v3
+ with:
+ path: '**/.quarkus/quarkus-prod-config-dump'
+ key: ${{ runner.os }}-quarkus-metadata
- name: Build
env:
TEST_MODULES: ${{matrix.test-modules}}
diff --git a/.mvn/extensions.xml b/.mvn/extensions.xml
index 279f59a0f501c..a48dbdff4d4f1 100644
--- a/.mvn/extensions.xml
+++ b/.mvn/extensions.xml
@@ -9,4 +9,9 @@
common-custom-user-data-maven-extension
1.12.5
+
+ com.gradle
+ quarkus-build-caching-extension
+ 0.10
+
diff --git a/devtools/maven/src/main/java/io/quarkus/maven/TrackConfigChangesMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/TrackConfigChangesMojo.java
index 13f4379079981..b023983db78b2 100644
--- a/devtools/maven/src/main/java/io/quarkus/maven/TrackConfigChangesMojo.java
+++ b/devtools/maven/src/main/java/io/quarkus/maven/TrackConfigChangesMojo.java
@@ -1,12 +1,19 @@
package io.quarkus.maven;
import java.io.BufferedReader;
+import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
+import java.nio.file.DirectoryStream;
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 java.util.zip.Adler32;
+import java.util.zip.Checksum;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
@@ -18,6 +25,7 @@
import io.quarkus.bootstrap.app.CuratedApplication;
import io.quarkus.bootstrap.classloading.QuarkusClassLoader;
import io.quarkus.bootstrap.model.ApplicationModel;
+import io.quarkus.maven.dependency.DependencyFlags;
import io.quarkus.runtime.LaunchMode;
/**
@@ -58,6 +66,18 @@ public class TrackConfigChangesMojo extends QuarkusBootstrapMojo {
@Parameter(defaultValue = "false", property = "quarkus.track-config-changes.dump-current-when-recorded-unavailable")
boolean dumpCurrentWhenRecordedUnavailable;
+ /**
+ * Whether to dump Quarkus application dependencies along with their checksums
+ */
+ @Parameter(defaultValue = "true", property = "quarkus.track-config-changes.dump-dependencies")
+ boolean dumpDependencies;
+
+ /**
+ * Dependency dump file
+ */
+ @Parameter(property = "quarkus.track-config-changes.dependencies-file")
+ File dependenciesFile;
+
@Override
protected boolean beforeExecute() throws MojoExecutionException, MojoFailureException {
if (skip) {
@@ -82,16 +102,6 @@ protected void doExecute() throws MojoExecutionException, MojoFailureException {
getLog().debug("Bootstrapping Quarkus application in mode " + launchMode);
}
- Path targetFile;
- if (outputFile == null) {
- targetFile = outputDirectory.toPath()
- .resolve("quarkus-" + launchMode.getDefaultProfile() + "-config-check");
- } else if (outputFile.isAbsolute()) {
- targetFile = outputFile.toPath();
- } else {
- targetFile = outputDirectory.toPath().resolve(outputFile.toPath());
- }
-
Path compareFile;
if (this.recordedBuildConfigFile == null) {
compareFile = recordedBuildConfigDirectory.toPath()
@@ -102,34 +112,64 @@ protected void doExecute() throws MojoExecutionException, MojoFailureException {
compareFile = recordedBuildConfigDirectory.toPath().resolve(this.recordedBuildConfigFile.toPath());
}
- 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");
+ final boolean prevConfigExists = Files.exists(compareFile);
+ if (!prevConfigExists && !dumpCurrentWhenRecordedUnavailable && !dumpDependencies) {
+ getLog().info("Config dump from the previous build does not exist at " + compareFile);
return;
}
CuratedApplication curatedApplication = null;
QuarkusClassLoader deploymentClassLoader = null;
final ClassLoader originalCl = Thread.currentThread().getContextClassLoader();
- Properties actualProps;
final boolean clearPackageTypeSystemProperty = setPackageTypeSystemPropertyIfNativeProfileEnabled();
try {
curatedApplication = bootstrapApplication(launchMode);
- deploymentClassLoader = curatedApplication.createDeploymentClassLoader();
- Thread.currentThread().setContextClassLoader(deploymentClassLoader);
-
- final Class> codeGenerator = deploymentClassLoader.loadClass("io.quarkus.deployment.CodeGenerator");
- 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, targetFile);
+ if (prevConfigExists || dumpCurrentWhenRecordedUnavailable) {
+ final Path targetFile = getOutputFile(outputFile, launchMode.getDefaultProfile(), "-config-check");
+ Properties compareProps = new Properties();
+ if (prevConfigExists) {
+ try (BufferedReader reader = Files.newBufferedReader(compareFile)) {
+ compareProps.load(reader);
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to read " + compareFile, e);
+ }
+ }
+
+ deploymentClassLoader = curatedApplication.createDeploymentClassLoader();
+ Thread.currentThread().setContextClassLoader(deploymentClassLoader);
+
+ final Class> codeGenerator = deploymentClassLoader.loadClass("io.quarkus.deployment.CodeGenerator");
+ 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, targetFile);
+ }
+
+ if (dumpDependencies) {
+ final List deps = new ArrayList<>();
+ for (var d : curatedApplication.getApplicationModel().getDependencies(DependencyFlags.DEPLOYMENT_CP)) {
+ StringBuilder entry = new StringBuilder(d.toGACTVString());
+ if (d.isSnapshot()) {
+ var adler32 = new Adler32();
+ updateChecksum(adler32, d.getResolvedPaths());
+ entry.append(" ").append(adler32.getValue());
+ }
+
+ deps.add(entry.toString());
+ }
+ Collections.sort(deps);
+ final Path targetFile = getOutputFile(dependenciesFile, launchMode.getDefaultProfile(),
+ "-dependency-checksums.txt");
+ Files.createDirectories(targetFile.getParent());
+ try (BufferedWriter writer = Files.newBufferedWriter(targetFile)) {
+ for (var s : deps) {
+ writer.write(s);
+ writer.newLine();
+ }
+ }
+ }
} catch (Exception any) {
throw new MojoExecutionException("Failed to bootstrap Quarkus application", any);
} finally {
@@ -142,4 +182,44 @@ protected void doExecute() throws MojoExecutionException, MojoFailureException {
}
}
}
+
+ private Path getOutputFile(File outputFile, String profile, String fileNameSuffix) {
+ if (outputFile == null) {
+ return outputDirectory.toPath().resolve("quarkus-" + profile + fileNameSuffix);
+ }
+ if (outputFile.isAbsolute()) {
+ return outputFile.toPath();
+ }
+ return outputDirectory.toPath().resolve(outputFile.toPath());
+ }
+
+ private static void updateChecksum(Checksum checksum, Iterable pc) throws IOException {
+ for (var path : sort(pc)) {
+ if (Files.isDirectory(path)) {
+ try (DirectoryStream stream = Files.newDirectoryStream(path)) {
+ updateChecksum(checksum, stream);
+ }
+ } else {
+ checksum.update(Files.readAllBytes(path));
+ }
+ }
+ }
+
+ private static Iterable sort(Iterable original) {
+ var i = original.iterator();
+ if (!i.hasNext()) {
+ return List.of();
+ }
+ var o = i.next();
+ if (!i.hasNext()) {
+ return List.of(o);
+ }
+ final List sorted = new ArrayList<>();
+ sorted.add(o);
+ while (i.hasNext()) {
+ sorted.add(i.next());
+ }
+ Collections.sort(sorted);
+ return sorted;
+ }
}
diff --git a/docs/src/main/asciidoc/config-reference.adoc b/docs/src/main/asciidoc/config-reference.adoc
index d46012b9d3700..bfd359c43ae38 100644
--- a/docs/src/main/asciidoc/config-reference.adoc
+++ b/docs/src/main/asciidoc/config-reference.adoc
@@ -738,6 +738,11 @@ 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 Quarkus application dependencies
+
+In addition to dumping configuration values, `track-config-changes` goal also dumps all the Quarkus application dependencies, including Quarkus build time dependencies, along with their checksums (Adler32). This file could be used to check whether Quarkus build classpath has changed since the previous run.
+By default, the dependency checksums will be stored under `target/quarkus-prod-dependency-checksums.txt` file. A different location could be configured using plugin parameters.
+
==== 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
diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/ArtifactCoords.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/ArtifactCoords.java
index a0b099e1e9277..946a7cb785bda 100644
--- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/ArtifactCoords.java
+++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/ArtifactCoords.java
@@ -42,6 +42,10 @@ default boolean isJar() {
return TYPE_JAR.equals(getType());
}
+ default boolean isSnapshot() {
+ return getVersion() != null && getVersion().endsWith("-SNAPSHOT");
+ }
+
default String toGACTVString() {
return getGroupId() + ":" + getArtifactId() + ":" + getClassifier() + ":" + getType() + ":" + getVersion();
}
diff --git a/independent-projects/parent/pom.xml b/independent-projects/parent/pom.xml
index 2df6e460321f0..7aebd7d6a21b8 100644
--- a/independent-projects/parent/pom.xml
+++ b/independent-projects/parent/pom.xml
@@ -424,6 +424,63 @@
+
+ maven-surefire-plugin
+
+
+
+ dependency-checksums
+
+ ${project.build.directory}
+
+
+ quarkus-*-dependency-checksums.txt
+
+ RELATIVE_PATH
+
+
+
+
+
+ maven-failsafe-plugin
+
+
+
+ dependency-checksums
+
+ ${project.build.directory}
+
+
+ quarkus-*-dependency-checksums.txt
+
+ RELATIVE_PATH
+
+
+
+
+
+ io.quarkus
+ quarkus-maven-plugin
+
+
+ default
+
+
+
+ dependency-checksums
+
+ ${project.build.directory}
+
+
+ quarkus-*-dependency-checksums.txt
+
+ RELATIVE_PATH
+
+
+
+
+
+
org.jetbrains.kotlin
kotlin-maven-plugin
diff --git a/integration-tests/main/pom.xml b/integration-tests/main/pom.xml
index 5f9e1a8821a7c..152e4f371975d 100644
--- a/integration-tests/main/pom.xml
+++ b/integration-tests/main/pom.xml
@@ -23,6 +23,9 @@
1.13.0
+
+ true
+ true
@@ -507,6 +510,16 @@
io.quarkus
quarkus-maven-plugin
+
+ track-config-changes
+ process-resources
+
+ track-config-changes
+
+
+ true
+
+
build
diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml
index e0b6a8a90dd0f..f2ab116a3cbd2 100644
--- a/integration-tests/pom.xml
+++ b/integration-tests/pom.xml
@@ -70,6 +70,17 @@
io.quarkus
quarkus-maven-plugin
${project.version}
+
+
+
+ track-config-changes
+ process-resources
+
+ track-config-changes
+
+
+
true
${quarkus.build.skip}
@@ -304,7 +315,7 @@
amazon-lambda-rest-resteasy-reactive
amazon-lambda-rest-reactive-routes
amazon-lambda-rest-funqy
- amazon-lambda-rest-servlet
+ amazon-lambda-rest-servlet
amazon-lambda-http-resteasy
amazon-lambda-http-resteasy-reactive
container-image