From 44b76fa9b9027bc5b68fecc8bbce14bc1e05dc34 Mon Sep 17 00:00:00 2001 From: Robert Stupp Date: Tue, 25 Jul 2023 15:08:56 +0200 Subject: [PATCH] Gradle plugin: optionally respect non-`quarkus.*` cache-relevant properties Currently the Gradle plugin considers only system properties starting with `quarkus.` (and env vars starting with `QUARKUS_`) as relevant for Gradle's cache key. This is on purpose to not cause unnecessary rebuilds, because especially system properties can contain entries that are rather "random-ish". But other environment variables or system properties cannot be specified as "relevant". This change introduces a new list-property `cachingRelevantProperties` on the Quarkus Gradle extension object to tell the Quarkus Gradle plugin to consider the specified properties/env-vars as input(s) for the tasks. This list-property accepts regex patterns, and by default contains `quarkus[.].*`, matching the current `.startsWith("quarkus.")`. Fixes #34869 --- .../extension/QuarkusPluginExtension.java | 5 + .../tasks/AbstractQuarkusExtension.java | 2 + .../io/quarkus/gradle/tasks/BaseConfig.java | 17 +- .../quarkus/gradle/tasks/EffectiveConfig.java | 3 + .../gradle/tasks/QuarkusBuildTask.java | 4 +- .../gradle/tasks/QuarkusGenerateCode.java | 13 +- .../io/quarkus/gradle/tasks/CachingTest.java | 217 ++++++++++++------ .../tasks/caching/main/build.gradle.kts | 3 + docs/src/main/asciidoc/gradle-tooling.adoc | 27 ++- 9 files changed, 219 insertions(+), 72 deletions(-) diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/extension/QuarkusPluginExtension.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/extension/QuarkusPluginExtension.java index 01598a936e4ca..a8d7fbcd40be5 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/extension/QuarkusPluginExtension.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/extension/QuarkusPluginExtension.java @@ -17,6 +17,7 @@ import org.gradle.api.file.FileCollection; import org.gradle.api.file.RegularFile; import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.MapProperty; import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; @@ -254,6 +255,10 @@ public MapProperty getQuarkusBuildProperties() { return quarkusBuildProperties; } + public ListProperty getCachingRelevantProperties() { + return cachingRelevantProperties; + } + public void set(String name, @Nullable String value) { quarkusBuildProperties.put(String.format("quarkus.%s", name), value); } diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/AbstractQuarkusExtension.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/AbstractQuarkusExtension.java index 2e97ca979576c..8d59abd2a90ef 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/AbstractQuarkusExtension.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/AbstractQuarkusExtension.java @@ -39,6 +39,7 @@ public abstract class AbstractQuarkusExtension { protected final Property finalName; private final MapProperty forcedPropertiesProperty; protected final MapProperty quarkusBuildProperties; + protected final ListProperty cachingRelevantProperties; private final ListProperty ignoredEntries; private final FileCollection classpath; private final Property baseConfig; @@ -52,6 +53,7 @@ protected AbstractQuarkusExtension(Project project) { this.finalName.convention(project.provider(() -> String.format("%s-%s", project.getName(), project.getVersion()))); this.forcedPropertiesProperty = project.getObjects().mapProperty(String.class, String.class); this.quarkusBuildProperties = project.getObjects().mapProperty(String.class, String.class); + this.cachingRelevantProperties = project.getObjects().listProperty(String.class).value(List.of("quarkus[.].*")); this.ignoredEntries = project.getObjects().listProperty(String.class); this.ignoredEntries.convention( project.provider(() -> baseConfig().packageConfig().userConfiguredIgnoredEntries.orElse(emptyList()))); diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/BaseConfig.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/BaseConfig.java index 6c57a8590af4d..90c229c043b9b 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/BaseConfig.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/BaseConfig.java @@ -1,6 +1,9 @@ package io.quarkus.gradle.tasks; +import java.util.List; import java.util.Map; +import java.util.function.Predicate; +import java.util.regex.Pattern; import java.util.stream.Collectors; import io.quarkus.deployment.pkg.PackageConfig; @@ -18,7 +21,7 @@ final class BaseConfig { private final Manifest manifest; private final PackageConfig packageConfig; - private final Map quarkusProperties; + private final Map configMap; // Note: EffectiveConfig has all the code to load the configurations from all the sources. BaseConfig(EffectiveConfig config) { @@ -31,8 +34,7 @@ final class BaseConfig { manifest.attributes(packageConfig.manifest.attributes); packageConfig.manifest.manifestSections.forEach((section, attribs) -> manifest.attributes(attribs, section)); - this.quarkusProperties = config.configMap().entrySet().stream().filter(e -> e.getKey().startsWith("quarkus.")) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + configMap = config.configMap(); } PackageConfig packageConfig() { @@ -47,7 +49,12 @@ Manifest manifest() { return manifest; } - Map quarkusProperties() { - return quarkusProperties; + Map cachingRelevantProperties(List propertyPatterns) { + List patterns = propertyPatterns.stream().map(s -> "^(" + s + ")$").map(Pattern::compile) + .collect(Collectors.toList()); + Predicate> keyPredicate = e -> patterns.stream().anyMatch(p -> p.matcher(e.getKey()).matches()); + return configMap.entrySet().stream() + .filter(keyPredicate) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } } diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/EffectiveConfig.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/EffectiveConfig.java index db3c8c3215723..3e03f70678474 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/EffectiveConfig.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/EffectiveConfig.java @@ -117,6 +117,9 @@ static Map generateFullConfigMap(SmallRyeConfig config) { static SmallRyeConfig buildConfig(String profile, List configSources) { return ConfigUtils.emptyConfigBuilder() .setAddDiscoveredSecretKeysHandlers(false) + // We add our own sources for environment, system-properties and microprofile-config.properties, + // no need to include those twice. + .setAddDefaultSources(false) .withSources(configSources) .withProfile(profile) .build(); diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildTask.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildTask.java index b4c664b88e2e1..106bca4941130 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildTask.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildTask.java @@ -15,6 +15,7 @@ import org.gradle.api.file.FileCopyDetails; import org.gradle.api.file.FileSystemOperations; import org.gradle.api.logging.LogLevel; +import org.gradle.api.provider.ListProperty; import org.gradle.api.tasks.Classpath; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.StopExecutionException; @@ -57,7 +58,8 @@ public FileCollection getClasspath() { @Input public Map getCachingRelevantInput() { - return extension().baseConfig().quarkusProperties(); + ListProperty vars = extension().getCachingRelevantProperties(); + return extension().baseConfig().cachingRelevantProperties(vars.get()); } PackageConfig.BuiltInType packageType() { diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusGenerateCode.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusGenerateCode.java index 83ccc825f07d2..3e9c6c02cf304 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusGenerateCode.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusGenerateCode.java @@ -13,6 +13,7 @@ import org.gradle.api.artifacts.Configuration; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.FileCollection; +import org.gradle.api.provider.ListProperty; import org.gradle.api.tasks.CacheableTask; import org.gradle.api.tasks.CompileClasspath; import org.gradle.api.tasks.Input; @@ -65,7 +66,17 @@ public void setCompileClasspath(Configuration compileClasspath) { @Input public Map getCachingRelevantInput() { - return extension().baseConfig().quarkusProperties(); + ListProperty vars = extension().getCachingRelevantProperties(); + return extension().baseConfig().cachingRelevantProperties(vars.get()); + } + + @Input + Map getInternalTaskConfig() { + // Necessary to distinguish the different `quarkusGenerateCode*` tasks, because the task path is _not_ + // an input to the cache key. We need to declare these properties as inputs, because those influence the + // execution. + // Documented here: https://docs.gradle.org/current/userguide/build_cache.html#sec:task_output_caching_inputs + return Map.of("launchMode", launchMode.name(), "inputSourceSetName", inputSourceSetName); } @InputFiles diff --git a/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/tasks/CachingTest.java b/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/tasks/CachingTest.java index 219f60d456a23..d1d27359e8215 100644 --- a/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/tasks/CachingTest.java +++ b/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/tasks/CachingTest.java @@ -1,18 +1,22 @@ package io.quarkus.gradle.tasks; +import static org.assertj.core.api.Assumptions.assumeThat; import static org.junit.jupiter.params.provider.Arguments.arguments; import java.io.File; import java.io.IOException; +import java.net.URISyntaxException; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; +import java.util.UUID; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -25,6 +29,7 @@ import org.gradle.testkit.runner.BuildTask; import org.gradle.testkit.runner.GradleRunner; import org.gradle.testkit.runner.TaskOutcome; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; @@ -33,13 +38,122 @@ @ExtendWith(SoftAssertionsExtension.class) public class CachingTest { + private static final Map ALL_SUCCESS = Map.of( + ":quarkusGenerateCode", TaskOutcome.SUCCESS, + ":quarkusGenerateCodeDev", TaskOutcome.SUCCESS, + ":quarkusGenerateCodeTests", TaskOutcome.SUCCESS, + ":quarkusAppPartsBuild", TaskOutcome.SUCCESS, + ":quarkusDependenciesBuild", TaskOutcome.SUCCESS, + ":quarkusBuild", TaskOutcome.SUCCESS, + ":build", TaskOutcome.SUCCESS); + private static final Map ALL_UP_TO_DATE = Map.of( + ":quarkusGenerateCode", TaskOutcome.UP_TO_DATE, + // intentionally omit ":quarkusGenerateCodeDev", it can be UP_TO_DATE or SUCCESS + ":quarkusGenerateCodeTests", TaskOutcome.UP_TO_DATE, + ":quarkusAppPartsBuild", TaskOutcome.UP_TO_DATE, + ":quarkusDependenciesBuild", TaskOutcome.UP_TO_DATE, + ":quarkusBuild", TaskOutcome.UP_TO_DATE, + ":build", TaskOutcome.UP_TO_DATE); + public static final Map FROM_CACHE = Map.of( + ":quarkusGenerateCode", TaskOutcome.FROM_CACHE, + ":quarkusGenerateCodeDev", TaskOutcome.SUCCESS, + ":quarkusGenerateCodeTests", TaskOutcome.FROM_CACHE, + ":quarkusAppPartsBuild", TaskOutcome.FROM_CACHE, + ":quarkusDependenciesBuild", TaskOutcome.SUCCESS, + ":quarkusBuild", TaskOutcome.SUCCESS, + ":build", TaskOutcome.SUCCESS); + @InjectSoftAssertions SoftAssertions soft; @TempDir Path testProjectDir; - @TempDir - Path saveDir; + + @Test + void envChangeInvalidatesBuild() throws Exception { + // Declare the environment variables FOO_ENV_VAR and FROM_DOT_ENV_FILE as relevant for the build. + prepareGradleBuildProject(String.join("\n", + "cachingRelevantProperties.add(\"FOO_ENV_VAR\")", + "cachingRelevantProperties.add(\"FROM_DOT_ENV_FILE\")")); + + String[] arguments = List.of("build", "--info", "--stacktrace", "--build-cache", "--configuration-cache", + "-Dquarkus.package.type=fast-jar", + "-Dquarkus.randomized.value=" + UUID.randomUUID()) + .toArray(new String[0]); + + Map env = Map.of(); + + assertBuildResult("initial", gradleBuild(rerunTasks(arguments), env), ALL_SUCCESS); + assertBuildResult("initial rebuild", gradleBuild(arguments, env), ALL_UP_TO_DATE); + + // Change the relevant environment, must rebuild + env = Map.of("FOO_ENV_VAR", "some-value"); + assertBuildResult("set FOO_ENV_VAR", gradleBuild(arguments, env), ALL_SUCCESS); + assertBuildResult("set FOO_ENV_VAR rebuild", gradleBuild(arguments, env), ALL_UP_TO_DATE); + + // Change the environment file again, must rebuild + env = Map.of("FOO_ENV_VAR", "some-other-value"); + assertBuildResult("change FOO_ENV_VAR", gradleBuild(arguments, env), ALL_SUCCESS); + assertBuildResult("change FOO_ENV_VAR rebuild", gradleBuild(arguments, env), ALL_UP_TO_DATE); + + // Change an unrelated environment variable, all up-to-date + env = Map.of("SOME_UNRELATED", "meep"); + assertBuildResult("SOME_UNRELATED", gradleBuild(arguments, env), FROM_CACHE); + } + + @Test + void dotEnvChangeInvalidatesBuild() throws Exception { + var dotEnvFile = Paths.get(System.getProperty("user.dir"), ".env"); + // If the local environment has a ~/.env file, then skip this test - do not mess up a user's environment. + assumeThat(dotEnvFile) + .describedAs("Gradle plugin CachingTest.dotEnvChangeInvalidatesBuild requires missing ~/.env file"); + + try { + // Declare the environment variables FOO_ENV_VAR and FROM_DOT_ENV_FILE as relevant for the build. + prepareGradleBuildProject(String.join("\n", + "cachingRelevantProperties.add(\"FOO_ENV_VAR\")", + "cachingRelevantProperties.add(\"FROM_DOT_ENV_FILE\")")); + + String[] arguments = List.of("build", "--info", "--stacktrace", "--build-cache", "--configuration-cache", + "-Dquarkus.package.type=fast-jar", + "-Dquarkus.randomized.value=" + UUID.randomUUID()) + .toArray(new String[0]); + + Map env = Map.of(); + + assertBuildResult("initial", gradleBuild(rerunTasks(arguments), env), ALL_SUCCESS); + assertBuildResult("initial rebuild", gradleBuild(arguments, env), ALL_UP_TO_DATE); + + // Change the .env file, must rebuild + + Files.write(dotEnvFile, List.of("FROM_DOT_ENV_FILE=env file value")); + assertBuildResult("set FROM_DOT_ENV_FILE", gradleBuild(arguments, env), ALL_SUCCESS); + assertBuildResult("set FROM_DOT_ENV_FILE rebuild", gradleBuild(arguments, env), ALL_UP_TO_DATE); + + // Change the .env file again, must rebuild + + Files.write(dotEnvFile, List.of("FROM_DOT_ENV_FILE=new value")); + assertBuildResult("change FROM_DOT_ENV_FILE", gradleBuild(arguments, env), ALL_SUCCESS); + assertBuildResult("change FROM_DOT_ENV_FILE rebuild", gradleBuild(arguments, env), ALL_UP_TO_DATE); + + // OTHER_ENV_VAR is not declared as relevant for the build, skipping its check + Files.write(dotEnvFile, List.of("FROM_DOT_ENV_FILE=new value", "OTHER_ENV_VAR=hello")); + assertBuildResult("OTHER_ENV_VAR", gradleBuild(arguments, env), ALL_UP_TO_DATE); + + // remove relevant var from .env file + Files.write(dotEnvFile, List.of("OTHER_ENV_VAR=hello")); + assertBuildResult("remove FROM_DOT_ENV_FILE", gradleBuild(arguments, env), FROM_CACHE); + + // Delete the .env file, must rebuild + + Files.deleteIfExists(dotEnvFile); + + BuildResult result = gradleBuild(arguments, env); + assertBuildResult("delete .env file", result, ALL_UP_TO_DATE); + } finally { + Files.deleteIfExists(dotEnvFile); + } + } static Stream gradleCaching() { return Stream.of("fast-jar", "uber-jar", "mutable-jar", "legacy-jar", "native-sources") @@ -50,12 +164,8 @@ static Stream gradleCaching() { @ParameterizedTest @MethodSource - void gradleCaching(String packageType, boolean simulateCI, String outputDir) throws Exception { - URL url = getClass().getClassLoader().getResource("io/quarkus/gradle/tasks/caching/main"); - - FileUtils.copyDirectory(new File(url.toURI()), testProjectDir.toFile()); - - FileUtils.copyFile(new File("../gradle.properties"), testProjectDir.resolve("gradle.properties").toFile()); + void gradleCaching(String packageType, boolean simulateCI, String outputDir, @TempDir Path saveDir) throws Exception { + prepareGradleBuildProject(""); Map env = simulateCI ? Map.of("CI", "yes") : Map.of(); @@ -66,46 +176,9 @@ void gradleCaching(String packageType, boolean simulateCI, String outputDir) thr args.add("-Dquarkus.package.outputDirectory=" + outputDir); } String[] arguments = args.toArray(new String[0]); - args.add("--rerun-tasks"); - String[] initialArguments = args.toArray(new String[0]); - BuildResult result = GradleRunner.create() - .withPluginClasspath() - .withProjectDir(testProjectDir.toFile()) - .withArguments(initialArguments) - .withEnvironment(env) - .build(); - Map taskResults = taskResults(result); - - soft.assertThat(taskResults) - .describedAs("output: %s", result.getOutput()) - .containsEntry(":quarkusGenerateCode", TaskOutcome.SUCCESS) - .containsEntry(":quarkusGenerateCodeDev", TaskOutcome.SUCCESS) - .containsEntry(":quarkusGenerateCodeTests", TaskOutcome.SUCCESS) - .containsEntry(":quarkusAppPartsBuild", TaskOutcome.SUCCESS) - .containsEntry(":quarkusDependenciesBuild", TaskOutcome.SUCCESS) - .containsEntry(":quarkusBuild", TaskOutcome.SUCCESS) - .containsEntry(":build", TaskOutcome.SUCCESS); - - // A follow-up 'build' does nothing, everything's up-to-date - - result = GradleRunner.create() - .withPluginClasspath() - .withProjectDir(testProjectDir.toFile()) - .withArguments(arguments) - .withEnvironment(env) - .build(); - taskResults = taskResults(result); - - soft.assertThat(taskResults) - .describedAs("output: %s", result.getOutput()) - .containsEntry(":quarkusGenerateCode", TaskOutcome.UP_TO_DATE) - .containsEntry(":quarkusGenerateCodeDev", TaskOutcome.UP_TO_DATE) - .containsEntry(":quarkusGenerateCodeTests", TaskOutcome.UP_TO_DATE) - .containsEntry(":quarkusAppPartsBuild", TaskOutcome.UP_TO_DATE) - .containsEntry(":quarkusDependenciesBuild", TaskOutcome.UP_TO_DATE) - .containsEntry(":quarkusBuild", TaskOutcome.UP_TO_DATE) - .containsEntry(":build", TaskOutcome.UP_TO_DATE); + assertBuildResult("initial", gradleBuild(rerunTasks(arguments), env), ALL_SUCCESS); + assertBuildResult("initial rebuild", gradleBuild(arguments, env), ALL_UP_TO_DATE); // Purge the whole build/ directory @@ -118,13 +191,8 @@ void gradleCaching(String packageType, boolean simulateCI, String outputDir) thr // A follow-up 'build', without a build/ directory should fetch everything from the cache / pull the dependencies - result = GradleRunner.create() - .withPluginClasspath() - .withProjectDir(testProjectDir.toFile()) - .withArguments(arguments) - .withEnvironment(env) - .build(); - taskResults = taskResults(result); + BuildResult result = gradleBuild(arguments, env); + Map taskResults = taskResults(result); Path quarkusBuildGen = Paths.get("quarkus-build", "gen"); boolean isFastJar = "fast-jar".equals(packageType); @@ -145,23 +213,44 @@ void gradleCaching(String packageType, boolean simulateCI, String outputDir) thr // A follow-up 'build' does nothing, everything's up-to-date - result = GradleRunner.create() + result = gradleBuild(arguments, env); + assertBuildResult("follow-up", result, ALL_UP_TO_DATE); + } + + private static String[] rerunTasks(String[] arguments) { + String[] args = Arrays.copyOf(arguments, arguments.length + 1); + args[arguments.length] = "--rerun-tasks"; + return args; + } + + private BuildResult gradleBuild(String[] arguments, Map env) { + return GradleRunner.create() .withPluginClasspath() .withProjectDir(testProjectDir.toFile()) .withArguments(arguments) .withEnvironment(env) .build(); - taskResults = taskResults(result); + } + private void assertBuildResult(String step, BuildResult result, + Map expected) { + Map taskResults = taskResults(result); soft.assertThat(taskResults) - .describedAs("output: %s", result.getOutput()) - .containsEntry(":quarkusGenerateCode", TaskOutcome.UP_TO_DATE) - .containsEntry(":quarkusGenerateCodeDev", TaskOutcome.UP_TO_DATE) - .containsEntry(":quarkusGenerateCodeTests", TaskOutcome.UP_TO_DATE) - .containsEntry(":quarkusAppPartsBuild", TaskOutcome.UP_TO_DATE) - .containsEntry(":quarkusDependenciesBuild", TaskOutcome.UP_TO_DATE) - .containsEntry(":quarkusBuild", TaskOutcome.UP_TO_DATE) - .containsEntry(":build", TaskOutcome.UP_TO_DATE); + .describedAs("output: %s\n\nSTEP: %s", result.getOutput(), step) + .containsAllEntriesOf(expected); + } + + private void prepareGradleBuildProject(String additionalQuarkusConfig) throws IOException, URISyntaxException { + URL url = getClass().getClassLoader().getResource("io/quarkus/gradle/tasks/caching/main"); + + FileUtils.copyDirectory(new File(url.toURI()), testProjectDir.toFile()); + + // Randomize the build script + String buildScript = Files.readString(testProjectDir.resolve("build.gradle.kts")); + buildScript = buildScript.replace("// ADDITIONAL_CONFIG", additionalQuarkusConfig); + Files.writeString(testProjectDir.resolve("build.gradle.kts"), buildScript); + + FileUtils.copyFile(new File("../gradle.properties"), testProjectDir.resolve("gradle.properties").toFile()); } static Map taskResults(BuildResult result) { diff --git a/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/caching/main/build.gradle.kts b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/caching/main/build.gradle.kts index 8618797d16203..8fb484c22a651 100644 --- a/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/caching/main/build.gradle.kts +++ b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/caching/main/build.gradle.kts @@ -25,4 +25,7 @@ quarkus { manifest { attributes(mapOf("Manifest-Attribute" to "some-value")) } + + // The following line is replaced by the tests in `CachingTest` + // ADDITIONAL_CONFIG } diff --git a/docs/src/main/asciidoc/gradle-tooling.adoc b/docs/src/main/asciidoc/gradle-tooling.adoc index 1a01ce58980bd..8d58477cb0ef5 100644 --- a/docs/src/main/asciidoc/gradle-tooling.adoc +++ b/docs/src/main/asciidoc/gradle-tooling.adoc @@ -624,7 +624,7 @@ The Quarkus build uses the `prod` configuration profile: 3. Configuration via the `quarkus` extensions's `quarkusBuildProperties` For example: quarkus { - properties { + quarkusBuildProperties { set("package.type", "uber-jar") } } @@ -650,6 +650,31 @@ specify the `--save-config-properties` command line option, the configuration pr `build/.quarkus-build.properties`. +=== Gradle caching / task inputs + +By default, system properties starting with `quarkus.` and environment variables, including those from `~/.env`, +starting with `QUARKUS_`, are considered as inputs for the Gradle tasks. This means that only changes to those system +properties or environment variables will cause Gradle's up-to-date to trigger a rebuild. Changes to other system +properties or environment variables do not change Quarkus' Gradle task inputs and do not trigger an unnecessary rebuild. + +Configuration properties specified via `quarkus.quarkusBuildProperties` or via the Quarkus `application.*` +configuration files are all considered as Gradle task inputs, in other words: every change in these files causes +a rebuild. + +If your Quarkus build references system properties that do not start with `quarkus.` (or environment variables that +do not start with `QUARKUS_`), you must reference those via the Quarkus build extension. For example, if your +`application.properties` file references an environment variable like this: + + greeting.message=${FOO_MESSAGE:Hello!} + +it must be explicitly declared as "caching relevant": + + quarkus { + cachingRelevantProperties.add("FOO_MESSAGE") + // Note: `cachingRelevantProperties` accepts regular expressions + } + + == Build workers Quarkus application builds are ran in isolated processes using Gradle's worker API. This includes the Quarkus