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 13f43790799818..12fd8560ccf921 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,20 @@ package io.quarkus.maven; import java.io.BufferedReader; +import java.io.BufferedWriter; import java.io.File; +import java.io.FileWriter; 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 +26,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 +67,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.dependenciesFile", defaultValue = "quarkus-app-deps.txt") + String dependenciesFile; + @Override protected boolean beforeExecute() throws MojoExecutionException, MojoFailureException { if (skip) { @@ -103,13 +124,14 @@ protected void doExecute() throws MojoExecutionException, MojoFailureException { } final Properties compareProps = new Properties(); - if (Files.exists(compareFile)) { + boolean prevConfigExists = Files.exists(compareFile); + if (prevConfigExists) { try (BufferedReader reader = Files.newBufferedReader(compareFile)) { compareProps.load(reader); } catch (IOException e) { throw new RuntimeException("Failed to read " + compareFile, e); } - } else if (!dumpCurrentWhenRecordedUnavailable) { + } else if (!dumpCurrentWhenRecordedUnavailable && !dumpDependencies) { getLog().info(compareFile + " not found"); return; } @@ -117,19 +139,38 @@ protected void doExecute() throws MojoExecutionException, MojoFailureException { 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) { + 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) { + var dependenciesFile = new File(outputDirectory, this.dependenciesFile); + dependenciesFile.getParentFile().mkdirs(); + final List deps = new ArrayList<>(); + for (var d : curatedApplication.getApplicationModel().getDependencies(DependencyFlags.DEPLOYMENT_CP)) { + var adler32 = new Adler32(); + updateChecksum(adler32, d.getResolvedPaths()); + deps.add(d.toGACTVString() + " " + adler32.getValue()); + } + try (BufferedWriter writer = new BufferedWriter(new FileWriter(dependenciesFile))) { + for (var s : deps) { + writer.write(s); + writer.newLine(); + } + } + } } catch (Exception any) { throw new MojoExecutionException("Failed to bootstrap Quarkus application", any); } finally { @@ -142,4 +183,34 @@ protected void doExecute() throws MojoExecutionException, MojoFailureException { } } } + + 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; + } }