From d7bc0a80fa835d9efe4ba811baf47baf7cd00c91 Mon Sep 17 00:00:00 2001 From: Robert Stupp Date: Sat, 4 Feb 2023 23:04:35 +0100 Subject: [PATCH] Make `QuarkusBuild` not pollute Gradle's build cache Make `QuarkusBuild` not pollute Gradle's build cache Improve Quarkus build in Gradle, proper up-to-date mechanisms, right config-source priorities. Currently the `QuarkusBuild` task implementation adds even large build artifacts and unmodified dependency jars to Gradle's build cache. This pollutes the Gradle build cache quite a lot and therefore lets archived build caches become unnecessary huge. On top of that, "relicts" from previous runs of `QuarkusBuild` using a different configuration, like a different package-type, causes multiple permutations of the cached artifacts and leads to restoring unexpected artifacts from the build cache. This large change updates the Gradle logic to have "proper" up-to-date checks and in turn a reasonable setting of what is cached and what is not cached. Background on how I discovered the "caching and up-to-date issue" with `QuarkusBuild`: Quarkus 2.16 added the `@CacheableTask` annotation to `QuarkusBuild`. This means that _everything_ that is generated by `QuarkusBuild` (and "declared" as an output: fast-jar directory, uber-jar, native-runner) is cached. After having Quarkus 2.16.x in our "main" branch for about 1/2 day, nearly the whole "10 GB budget" for cache artifacts in GitHub was occupied by Gradle build cache artifacts - the build cache artifacts grew from build to build - this was not the case without the `@CacheableTask` annotation. The current inputs of the `QuarkusBuild` task are also incomplete. Those inputs do not include for example the configured `finalName`, the settings on the Gradle project and the settings on the Quarkus extension. This leads to situations that the wrong Gradle cache artifacts could be restored (think: you ask for a fast-jar, but get an uber-jar). Another issue is that repeated runs of `quarkusBuild` against the exact same code base and dependencies leads to different outputs, the contents of the generated jars and e.g. quarkus-application.dat differ. This causes different outputs of the `QuarkusBuild` task, which cause depending tasks and projects to unnecessarily run/work again. The initial goal of this effort was to make caching of `fast-jar` builds work nice in CI environments, I assume that's the most common package type used in CI environments for example for testing purposes. The idea here is to _not_ cache all the dependency jars, but leverage the mechanisms available in Gradle to "just collect" the dependencies. In other words (and simplified): for `fast-jar`, cache everthing "in `quarkus-app` except `lib/`. There is no change for users running a `./gradlew quarkusBuild`. `QuarkusBuild` is still the task that users should execute (or depend on in their builds). To achive the goal to make especially `fast-jar` builds "nicely cacheable" in CI, two new tasks had to be introduced: * `QuarkusBuildDependencies` uses the dependency information from the Quarkus `ApplicationModel` to tell Gradle to collect those. This task works for `fast-jar` and `legacy-jar` and is a "no op" for other package types. This task is _never_ cacheable, but has _up-to-date_ checks. * `QuarkusBuildCacheableAppParts` performs a Quarkus application build and collects the _cacheable_ parts of that build. This task works for `fast-jar` and `legacy-jar` and is a "no op" for other package types. This task is cacheable. * `QuarkusBuild`, for `fast-jar` and `legacy-jar`, assembles the outputs of the above two tasks to produce the expected output. Performs a Quarkus build for other package types. See below on when this task is cacheable. The `build/quarkus-build/` directory is used to properly separate the outputs of the above three tasks: * `build/quarkus-build/gen/` receives the full output of a Quarkus build * `build/quarkus-build/app/` receives the _cacheable_ parts from `build/quarkus-build/gen/` * `build/quarkus-build/dep/` receives the dependency jars The output of `QuarkusBuild` is, by default, cacheable in non-CI environments and _not_ cacheable in CI environments. The behavior can be explicitly overridden using the new property `cacheLargeArtifacts` property. As outlined above, caching huge artifacts is not beneficial in CI, either due to space limitations (GitHub's 10GB limit for example) or just the cost of network traffic/duration. On a developer's machine however, the cost of storing/retrieving even bigger artifacts is rather neglectible and lower than rebuilding a Quarkus application. Before this change, the "priority" of configuration settings was (in most cases...) effectively: System properties, environment variables, `application.properties`, configurations in Gradle build scripts & Gradle project properties. This order is unintuitive and was changed to: system properties, environment variables, Gradle project properties, Quarkus build configuration, application.properties. The previous code had several places in which Quarkus related configuration settings were retrieved with sometimes different priorities of the "config sources". The whole "machinery" to get the configuration has been rewritten and encapsulated in new classes `EffectiveConfig` (effective for a Quarkus build) and `BaseConfig` (available during Gradle task configuration phase). `EffectiveConfig` is not really different from `BaseConfig, but contains the "special" settings from e.g. `ImagePush` task. The new implementation also uses SmallRye Config and especially the existing Quarkus mechanisms to pull information out of a `PackageConfig` _object_ produced from a configuration. It turned out to be easier to reason about `PackageConfig` than "raw property values". Support for `application.yaml/yml` has also been added. All calls into the "inner workings" of a Quarkus build and Quarkus code generation have been moved to separate worker processes. The code in this change to support this is pretty dead simple. The primary reason to move that work into isolated worker processes is that all the `QuarkusBootstrap` and derived pieces know nothing about Gradle, so there is no "property like" mechanism to override settings from `application.properties/yaml/yml` with those from e.g. the `QuarkusPluginExtension`. The only option would have been to modify the system properties of the Gradle build process - but that's a no-go, especially considering other tasks running in parallel (think: two `QuarkusBuild` of different projects running at the same time). It would have been relatively easy to serialize all `QuarkusBuild` actions across a Gradle build, but why - and it would prevent using "beefy machines" to run many `QuarkusBuild`s in parallel. Another option would have been to implement a rather complex (and likely very racy) mechanism to track modifications to system properties. As a conclusion, it wasn't just very simple to leverage the process isolation using the Gradle worker API, but it's also not bad. Gradle does reuse already spawned and compatible worker instances. The "trick" implemented to "prioritize" configs from Gradle project properties and Quarkus extension settings is to pass the whole config as system properties to the worker. A bunch of hopefully useful logging has been implemented and added. With info-level loogging, the tasks emit at least some basic information about what they do and at least the package type being used. To be able to investigate which configuration settings were effectively used, there's another new task called `quarkusShowEffectiveConfig` that shows all the `quarkus.*` properties and some more information, including the loaded `application.(properties|yaml|yml)`. This task is intended to debug build issues. The task can optionally save the effecitve config properties as a properties file in the `build/` directory, if run with the command line option `./gradlew quarkusShowEffectiveConfig --save-config-properties`. None of the existing tests has changed, except the `BuildCOnfigurationTest` had to be adopted to reflect the updated order of config sources. Relates to: #30852 --- .../gradle-application-plugin/build.gradle | 1 + .../gradle/gradle-application-plugin/pom.xml | 4 + .../java/io/quarkus/gradle/QuarkusPlugin.java | 68 ++- .../extension/QuarkusPluginExtension.java | 111 +++-- .../tasks/AbstractQuarkusExtension.java | 179 +++++++ .../io/quarkus/gradle/tasks/BaseConfig.java | 53 +++ .../quarkus/gradle/tasks/EffectiveConfig.java | 219 +++++++++ .../io/quarkus/gradle/tasks/ImageBuild.java | 17 +- .../io/quarkus/gradle/tasks/ImagePush.java | 16 +- .../io/quarkus/gradle/tasks/ImageTask.java | 16 - .../gradle/tasks/QuarkusAddExtension.java | 2 +- .../io/quarkus/gradle/tasks/QuarkusBuild.java | 447 +++++++++++------- .../tasks/QuarkusBuildCacheableAppParts.java | 136 ++++++ .../tasks/QuarkusBuildConfiguration.java | 39 -- .../tasks/QuarkusBuildDependencies.java | 188 ++++++++ .../tasks/QuarkusBuildProviderTask.java | 26 - .../gradle/tasks/QuarkusBuildTask.java | 189 ++++++++ .../io/quarkus/gradle/tasks/QuarkusDev.java | 2 +- .../gradle/tasks/QuarkusGenerateCode.java | 130 +++-- .../gradle/tasks/QuarkusGoOffline.java | 2 +- .../io/quarkus/gradle/tasks/QuarkusInfo.java | 2 +- .../gradle/tasks/QuarkusListCategories.java | 2 +- .../gradle/tasks/QuarkusListExtensions.java | 2 +- .../gradle/tasks/QuarkusListPlatforms.java | 2 +- .../gradle/tasks/QuarkusRemoteDev.java | 2 +- .../gradle/tasks/QuarkusRemoveExtension.java | 2 +- .../tasks/QuarkusShowEffectiveConfig.java | 90 ++++ .../io/quarkus/gradle/tasks/QuarkusTask.java | 30 +- .../io/quarkus/gradle/tasks/QuarkusTest.java | 2 +- .../gradle/tasks/QuarkusTestConfig.java | 2 +- .../quarkus/gradle/tasks/QuarkusUpdate.java | 2 +- .../gradle/tasks/worker/BuildWorker.java | 80 ++++ .../tasks/worker/BuildWorkerParams.java | 4 + .../gradle/tasks/worker/CodeGenWorker.java | 87 ++++ .../tasks/worker/CodeGenWorkerParams.java | 18 + .../gradle/tasks/worker/QuarkusParams.java | 18 + .../gradle/tasks/worker/QuarkusWorker.java | 37 ++ .../io/quarkus/gradle/QuarkusPluginTest.java | 35 +- .../extension/QuarkusExtensionTest.java | 18 + .../io/quarkus/gradle/tasks/CachingTest.java | 180 +++++++ .../gradle/tasks/EffectiveConfigTest.java | 155 ++++++ .../tasks/caching/main/build.gradle.kts | 28 ++ .../tasks/caching/main/settings.gradle.kts | 1 + .../main/src/main/java/org/acme/Foo.java | 4 + .../application.properties | 2 + .../application.yaml | 5 + .../application.yml | 5 + .../app-props-and-yaml/application.properties | 1 + .../app-props-and-yaml/application.yaml | 3 + .../app-yaml-and-yml/application.yaml | 3 + .../app-yaml-and-yml/application.yml | 3 + .../overload/1/application.properties | 1 + .../overload/2/application.yaml | 3 + .../overload/3/application.properties | 1 + .../overload/4/application.properties | 1 + .../single-app-props/application.properties | 1 + .../single-app-yaml/application.yaml | 3 + .../single-app-yml/application.yml | 3 + devtools/gradle/gradle.properties | 1 + docs/src/main/asciidoc/gradle-tooling.adoc | 170 +++++++ .../with-application-properties/build.gradle | 2 - .../src/main/resources/application.properties | 2 + .../gradle/BuildConfigurationTest.java | 15 +- .../nativeimage/NativeIntegrationTestIT.java | 6 +- 64 files changed, 2400 insertions(+), 479 deletions(-) create mode 100644 devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/AbstractQuarkusExtension.java create mode 100644 devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/BaseConfig.java create mode 100644 devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/EffectiveConfig.java delete mode 100644 devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/ImageTask.java create mode 100644 devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildCacheableAppParts.java delete mode 100644 devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildConfiguration.java create mode 100644 devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildDependencies.java delete mode 100644 devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildProviderTask.java create mode 100644 devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildTask.java create mode 100644 devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusShowEffectiveConfig.java create mode 100644 devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/worker/BuildWorker.java create mode 100644 devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/worker/BuildWorkerParams.java create mode 100644 devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/worker/CodeGenWorker.java create mode 100644 devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/worker/CodeGenWorkerParams.java create mode 100644 devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/worker/QuarkusParams.java create mode 100644 devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/worker/QuarkusWorker.java create mode 100644 devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/extension/QuarkusExtensionTest.java create mode 100644 devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/tasks/CachingTest.java create mode 100644 devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/tasks/EffectiveConfigTest.java create mode 100644 devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/caching/main/build.gradle.kts create mode 100644 devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/caching/main/settings.gradle.kts create mode 100644 devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/caching/main/src/main/java/org/acme/Foo.java create mode 100644 devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/app-props-and-yaml-and-yml/application.properties create mode 100644 devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/app-props-and-yaml-and-yml/application.yaml create mode 100644 devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/app-props-and-yaml-and-yml/application.yml create mode 100644 devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/app-props-and-yaml/application.properties create mode 100644 devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/app-props-and-yaml/application.yaml create mode 100644 devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/app-yaml-and-yml/application.yaml create mode 100644 devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/app-yaml-and-yml/application.yml create mode 100644 devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/overload/1/application.properties create mode 100644 devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/overload/2/application.yaml create mode 100644 devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/overload/3/application.properties create mode 100644 devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/overload/4/application.properties create mode 100644 devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/single-app-props/application.properties create mode 100644 devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/single-app-yaml/application.yaml create mode 100644 devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/single-app-yml/application.yml diff --git a/devtools/gradle/gradle-application-plugin/build.gradle b/devtools/gradle/gradle-application-plugin/build.gradle index 46e83ebd27939..fc8b3ccfff735 100644 --- a/devtools/gradle/gradle-application-plugin/build.gradle +++ b/devtools/gradle/gradle-application-plugin/build.gradle @@ -7,6 +7,7 @@ dependencies { implementation "io.quarkus:quarkus-devtools-common:${version}" implementation "io.quarkus:quarkus-core-deployment:${version}" implementation "io.quarkus:quarkus-bootstrap-gradle-resolver:${version}" + implementation "io.smallrye.config:smallrye-config-source-yaml:${smallrye_config_version}" implementation project(":gradle-model") diff --git a/devtools/gradle/gradle-application-plugin/pom.xml b/devtools/gradle/gradle-application-plugin/pom.xml index fb682887162d3..9ade7029a0fd2 100644 --- a/devtools/gradle/gradle-application-plugin/pom.xml +++ b/devtools/gradle/gradle-application-plugin/pom.xml @@ -54,6 +54,10 @@ quarkus-devmode-test-utils test + + io.smallrye.config + smallrye-config-source-yaml + org.jetbrains.kotlin kotlin-gradle-plugin diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/QuarkusPlugin.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/QuarkusPlugin.java index 98e1db0958587..3106ad87a0e78 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/QuarkusPlugin.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/QuarkusPlugin.java @@ -36,7 +36,8 @@ import io.quarkus.gradle.tasks.ImagePush; import io.quarkus.gradle.tasks.QuarkusAddExtension; import io.quarkus.gradle.tasks.QuarkusBuild; -import io.quarkus.gradle.tasks.QuarkusBuildConfiguration; +import io.quarkus.gradle.tasks.QuarkusBuildCacheableAppParts; +import io.quarkus.gradle.tasks.QuarkusBuildDependencies; import io.quarkus.gradle.tasks.QuarkusDev; import io.quarkus.gradle.tasks.QuarkusGenerateCode; import io.quarkus.gradle.tasks.QuarkusGoOffline; @@ -46,6 +47,7 @@ import io.quarkus.gradle.tasks.QuarkusListPlatforms; import io.quarkus.gradle.tasks.QuarkusRemoteDev; import io.quarkus.gradle.tasks.QuarkusRemoveExtension; +import io.quarkus.gradle.tasks.QuarkusShowEffectiveConfig; import io.quarkus.gradle.tasks.QuarkusTest; import io.quarkus.gradle.tasks.QuarkusTestConfig; import io.quarkus.gradle.tasks.QuarkusUpdate; @@ -55,7 +57,10 @@ public class QuarkusPlugin implements Plugin { public static final String ID = "io.quarkus"; - public static final String QUARKUS_PACKAGE_TYPE = "quarkus.package.type"; + public static final String DEFAULT_OUTPUT_DIRECTORY = "quarkus-app"; + public static final String CLASS_LOADING_REMOVED_ARTIFACTS = "quarkus.class-loading.removed-artifacts"; + public static final String CLASS_LOADING_PARENT_FIRST_ARTIFACTS = "quarkus.class-loading.parent-first-artifacts"; + public static final String QUARKUS_ARTIFACT_PROPERTIES = "quarkus-artifact.properties"; public static final String EXTENSION_NAME = "quarkus"; public static final String LIST_EXTENSIONS_TASK_NAME = "listExtensions"; @@ -66,6 +71,9 @@ public class QuarkusPlugin implements Plugin { public static final String QUARKUS_GENERATE_CODE_TASK_NAME = "quarkusGenerateCode"; public static final String QUARKUS_GENERATE_CODE_DEV_TASK_NAME = "quarkusGenerateCodeDev"; public static final String QUARKUS_GENERATE_CODE_TESTS_TASK_NAME = "quarkusGenerateCodeTests"; + public static final String QUARKUS_BUILD_DEP_TASK_NAME = "quarkusDependenciesBuild"; + public static final String QUARKUS_BUILD_APP_PARTS_TASK_NAME = "quarkusAppPartsBuild"; + public static final String QUARKUS_SHOW_EFFECTIVE_CONFIG_TASK_NAME = "quarkusShowEffectiveConfig"; public static final String QUARKUS_BUILD_TASK_NAME = "quarkusBuild"; public static final String QUARKUS_DEV_TASK_NAME = "quarkusDev"; public static final String QUARKUS_REMOTE_DEV_TASK_NAME = "quarkusRemoteDev"; @@ -96,6 +104,7 @@ public class QuarkusPlugin implements Plugin { private final ToolingModelBuilderRegistry registry; + @SuppressWarnings("CdiInjectionPointsInspection") @Inject public QuarkusPlugin(ToolingModelBuilderRegistry registry) { this.registry = registry; @@ -148,22 +157,40 @@ private void registerTasks(Project project, QuarkusPluginExtension quarkusExt) { TaskProvider quarkusGenerateCodeTests = tasks.register(QUARKUS_GENERATE_CODE_TESTS_TASK_NAME, QuarkusGenerateCode.class, task -> task.setTest(true)); - QuarkusBuildConfiguration buildConfig = new QuarkusBuildConfiguration(project); + tasks.register(QUARKUS_SHOW_EFFECTIVE_CONFIG_TASK_NAME, + QuarkusShowEffectiveConfig.class, task -> { + task.setDescription("Show effective Quarkus build configuration."); + }); + + TaskProvider quarkusBuildDependencies = tasks.register(QUARKUS_BUILD_DEP_TASK_NAME, + QuarkusBuildDependencies.class, + task -> task.getOutputs().doNotCacheIf("Dependencies are never cached", t -> true)); + + TaskProvider quarkusBuildCacheableAppParts = tasks.register( + QUARKUS_BUILD_APP_PARTS_TASK_NAME, + QuarkusBuildCacheableAppParts.class, task -> { + task.dependsOn(quarkusGenerateCode); + task.getOutputs().doNotCacheIf( + "Not adding uber-jars, native binaries and mutable-jar package type to Gradle " + + "build cache by default. To allow caching of uber-jars, native binaries and mutable-jar " + + "package type, set 'cacheUberAndNativeRunners' in the 'quarkus' Gradle extension to 'true'.", + t -> !task.isCachedByDefault() && !quarkusExt.getCacheLargeArtifacts().get()); + }); TaskProvider quarkusBuild = tasks.register(QUARKUS_BUILD_TASK_NAME, QuarkusBuild.class, build -> { - build.dependsOn(quarkusGenerateCode); - build.getForcedProperties().set(buildConfig.getForcedProperties()); + build.dependsOn(quarkusBuildDependencies, quarkusBuildCacheableAppParts); + build.getOutputs().doNotCacheIf( + "Only collects and combines the outputs of " + QUARKUS_BUILD_APP_PARTS_TASK_NAME + " and " + + QUARKUS_BUILD_DEP_TASK_NAME + ", see 'cacheLargeArtifacts' in the 'quarkus' Gradle extension " + + "for details.", + t -> !quarkusExt.getCacheLargeArtifacts().get()); }); - TaskProvider imageBuild = tasks.register(IMAGE_BUILD_TASK_NAME, ImageBuild.class, buildConfig); - imageBuild.configure(task -> { - task.finalizedBy(quarkusBuild); - }); + tasks.register(IMAGE_BUILD_TASK_NAME, ImageBuild.class, + task -> task.finalizedBy(quarkusBuildCacheableAppParts, quarkusBuildDependencies, quarkusBuild)); - TaskProvider imagePush = tasks.register(IMAGE_PUSH_TASK_NAME, ImagePush.class, buildConfig); - imagePush.configure(task -> { - task.finalizedBy(quarkusBuild); - }); + tasks.register(IMAGE_PUSH_TASK_NAME, ImagePush.class, + task -> task.finalizedBy(quarkusBuildCacheableAppParts, quarkusBuildDependencies, quarkusBuild)); TaskProvider quarkusDev = tasks.register(QUARKUS_DEV_TASK_NAME, QuarkusDev.class, devRuntimeDependencies, quarkusExt); @@ -180,7 +207,6 @@ private void registerTasks(Project project, QuarkusPluginExtension quarkusExt) { }); - configureBuildNativeTask(project); project.getPlugins().withType( BasePlugin.class, basePlugin -> tasks.named(BasePlugin.ASSEMBLE_TASK_NAME, task -> task.dependsOn(quarkusBuild))); @@ -242,7 +268,7 @@ private void registerTasks(Project project, QuarkusPluginExtension quarkusExt) { quarkusGenerateCode, quarkusGenerateCodeTests); }); - quarkusBuild.configure( + quarkusBuildCacheableAppParts.configure( task -> task.dependsOn(classesTask, resourcesTask, tasks.named(JavaPlugin.JAR_TASK_NAME))); SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class); @@ -310,7 +336,7 @@ public void execute(Task task) { t.useJUnitPlatform(); }); // quarkusBuild is expected to run after the project has passed the tests - quarkusBuild.configure(task -> task.shouldRunAfter(tasks.withType(Test.class))); + quarkusBuildCacheableAppParts.configure(task -> task.shouldRunAfter(tasks.withType(Test.class))); SourceSet generatedSourceSet = sourceSets.create(QuarkusGenerateCode.QUARKUS_GENERATED_SOURCES); SourceSet generatedTestSourceSet = sourceSets.create(QuarkusGenerateCode.QUARKUS_TEST_GENERATED_SOURCES); @@ -378,16 +404,6 @@ private void verifyGradleVersion() { } } - private void configureBuildNativeTask(Project project) { - project.getGradle().getTaskGraph().whenReady(taskGraph -> { - if (taskGraph.hasTask(project.getPath() + BUILD_NATIVE_TASK_NAME) - || taskGraph.hasTask(project.getPath() + TEST_NATIVE_TASK_NAME)) { - project.getExtensions().getExtraProperties() - .set(QUARKUS_PACKAGE_TYPE, "native"); - } - }); - } - private void afterEvaluate(Project project) { visitProjectDependencies(project, project, new HashSet<>()); 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 499ca60d7a3cc..44b71022f9502 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 @@ -6,7 +6,6 @@ import java.util.LinkedHashSet; import java.util.Map; import java.util.Objects; -import java.util.Properties; import java.util.Set; import java.util.StringJoiner; import java.util.stream.Collectors; @@ -25,31 +24,44 @@ import org.gradle.api.tasks.SourceSetContainer; import org.gradle.api.tasks.testing.Test; import org.gradle.jvm.tasks.Jar; +import org.gradle.process.JavaForkOptions; import io.quarkus.bootstrap.BootstrapConstants; import io.quarkus.bootstrap.model.ApplicationModel; import io.quarkus.bootstrap.resolver.AppModelResolver; import io.quarkus.gradle.AppModelGradleResolver; +import io.quarkus.gradle.QuarkusPlugin; +import io.quarkus.gradle.dsl.Manifest; +import io.quarkus.gradle.tasks.AbstractQuarkusExtension; +import io.quarkus.gradle.tasks.QuarkusBuild; import io.quarkus.gradle.tasks.QuarkusGradleUtils; import io.quarkus.gradle.tooling.ToolingUtils; import io.quarkus.runtime.LaunchMode; -public class QuarkusPluginExtension { - private final Project project; - - private final Property finalName; - - private final MapProperty quarkusBuildProperties; +public abstract class QuarkusPluginExtension extends AbstractQuarkusExtension { private final SourceSetExtension sourceSetExtension; + private final Property cacheLargeArtifacts; + private final Property cleanupBuildOutput; + public QuarkusPluginExtension(Project project) { - this.project = project; + super(project); - finalName = project.getObjects().property(String.class); - finalName.convention(project.provider(() -> String.format("%s-%s", project.getName(), project.getVersion()))); + this.cleanupBuildOutput = project.getObjects().property(Boolean.class) + .convention(true); + this.cacheLargeArtifacts = project.getObjects().property(Boolean.class) + .convention(!System.getenv().containsKey("CI")); this.sourceSetExtension = new SourceSetExtension(); - this.quarkusBuildProperties = project.getObjects().mapProperty(String.class, String.class); + } + + public Manifest getManifest() { + return manifest(); + } + + @SuppressWarnings("unused") + public void manifest(Action action) { + action.execute(this.getManifest()); } public void beforeTest(Test task) { @@ -86,44 +98,43 @@ public void beforeTest(Test task) { task.environment(BootstrapConstants.TEST_TO_MAIN_MAPPINGS, fileList); project.getLogger().debug("test dir mapping - {}", fileList); - final String nativeRunner = task.getProject().getBuildDir().toPath() - .resolve(buildNativeRunnerName(props)) - .toAbsolutePath() - .toString(); + QuarkusBuild quarkusBuild = project.getTasks().named(QuarkusPlugin.QUARKUS_BUILD_TASK_NAME, QuarkusBuild.class) + .get(); + String nativeRunner = quarkusBuild.getNativeRunner().toPath().toAbsolutePath().toString(); props.put("native.image.path", nativeRunner); } catch (Exception e) { throw new IllegalStateException("Failed to resolve deployment classpath", e); } } - public String buildNativeRunnerName(final Map taskSystemProps) { - Properties properties = new Properties(taskSystemProps.size()); - properties.putAll(taskSystemProps); - quarkusBuildProperties.get().entrySet() - .forEach(buildEntry -> properties.putIfAbsent(buildEntry.getKey(), buildEntry.getValue())); - System.getProperties().entrySet() - .forEach(propEntry -> properties.putIfAbsent(propEntry.getKey(), propEntry.getValue())); - System.getenv().entrySet().forEach( - envEntry -> properties.putIfAbsent(envEntry.getKey(), envEntry.getValue())); - StringBuilder nativeRunnerName = new StringBuilder(); - - if (properties.containsKey("quarkus.package.output-name")) { - nativeRunnerName.append(properties.get("quarkus.package.output-name")); - } else { - nativeRunnerName.append(finalName()); - } - if (!properties.containsKey("quarkus.package.add-runner-suffix") - || (properties.containsKey("quarkus.package.add-runner-suffix") - && Boolean.parseBoolean((String) properties.get("quarkus.package.add-runner-suffix")))) { - nativeRunnerName.append("-runner"); - } - return nativeRunnerName.toString(); - } - public Property getFinalName() { return finalName; } + /** + * Whether the build output, build/*-runner[.jar] and build/quarkus-app, for other package types than the + * currently configured one are removed, default is 'true'. + */ + public Property getCleanupBuildOutput() { + return cleanupBuildOutput; + } + + public void setCleanupBuildOutput(boolean cleanupBuildOutput) { + this.cleanupBuildOutput.set(cleanupBuildOutput); + } + + /** + * Whether large build artifacts, like uber-jar and native runners, are cached. Defaults to 'false' if the 'CI' environment + * variable is set, otherwise defaults to 'true'. + */ + public Property getCacheLargeArtifacts() { + return cacheLargeArtifacts; + } + + public void setCacheLargeArtifacts(boolean cacheLargeArtifacts) { + this.cacheLargeArtifacts.set(cacheLargeArtifacts); + } + public String finalName() { return getFinalName().get(); } @@ -167,6 +178,26 @@ public ApplicationModel getApplicationModel(LaunchMode mode) { return ToolingUtils.create(project, mode); } + /** + * Adds an action to configure the {@code JavaForkOptions} to build a Quarkus application. + * + * @param action configuration action + */ + @SuppressWarnings("unused") + public void buildForkOptions(Action action) { + buildForkOptions.add(action); + } + + /** + * Adds an action to configure the {@code JavaForkOptions} to generate Quarkus code. + * + * @param action configuration action + */ + @SuppressWarnings("unused") + public void codeGenForkOptions(Action action) { + codeGenForkOptions.add(action); + } + /** * Returns the last file from the specified {@link FileCollection}. */ @@ -215,6 +246,7 @@ public Path appJarOrClasses() { return classesDir; } + @SuppressWarnings("unused") public MapProperty getQuarkusBuildProperties() { return quarkusBuildProperties; } @@ -226,5 +258,4 @@ public void set(String name, @Nullable String value) { public void set(String name, Property 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 new file mode 100644 index 0000000000000..145da82897746 --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/AbstractQuarkusExtension.java @@ -0,0 +1,179 @@ +package io.quarkus.gradle.tasks; + +import static io.quarkus.gradle.tasks.QuarkusGradleUtils.getSourceSet; +import static java.util.Collections.emptyList; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.gradle.api.Action; +import org.gradle.api.GradleException; +import org.gradle.api.Project; +import org.gradle.api.file.FileCollection; +import org.gradle.api.java.archives.Attributes; +import org.gradle.api.provider.ListProperty; +import org.gradle.api.provider.MapProperty; +import org.gradle.api.provider.Property; +import org.gradle.api.tasks.SourceSet; +import org.gradle.process.JavaForkOptions; + +import io.quarkus.gradle.dsl.Manifest; +import io.quarkus.maven.dependency.ResolvedDependency; + +/** + * This base class exists to hide internal properties, make those only available in the {@link io.quarkus.gradle.tasks} + * package and to the {@link io.quarkus.gradle.extension.QuarkusPluginExtension} class itself. + */ +public abstract class AbstractQuarkusExtension { + private static final String MANIFEST_SECTIONS_PROPERTY_PREFIX = "quarkus.package.manifest.manifest-sections"; + private static final String MANIFEST_ATTRIBUTES_PROPERTY_PREFIX = "quarkus.package.manifest.attributes"; + + private static final String QUARKUS_PROFILE = "quarkus.profile"; + protected final Project project; + protected final Property finalName; + private final MapProperty forcedPropertiesProperty; + protected final MapProperty quarkusBuildProperties; + private final ListProperty ignoredEntries; + private final SourceSet mainSourceSet; + private final FileCollection classpath; + private final Property baseConfig; + protected final List> codeGenForkOptions; + protected final List> buildForkOptions; + + protected AbstractQuarkusExtension(Project project) { + this.project = project; + this.finalName = project.getObjects().property(String.class); + 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.ignoredEntries = project.getObjects().listProperty(String.class); + this.ignoredEntries.convention( + project.provider(() -> baseConfig().packageConfig().userConfiguredIgnoredEntries.orElse(emptyList()))); + this.baseConfig = project.getObjects().property(BaseConfig.class).value(project.provider(this::buildBaseConfig)); + this.mainSourceSet = getSourceSet(project, SourceSet.MAIN_SOURCE_SET_NAME); + this.classpath = dependencyClasspath(mainSourceSet); + this.codeGenForkOptions = new ArrayList<>(); + this.buildForkOptions = new ArrayList<>(); + } + + private BaseConfig buildBaseConfig() { + // Using common code to construct the "base config", which is all the configuration (system properties, + // environment, application.properties/yaml/yml, project properties) that is available in a Gradle task's + // _configuration phase_. + EffectiveConfig effectiveConfig = buildEffectiveConfiguration(Collections.emptyMap()); + return new BaseConfig(effectiveConfig); + } + + protected BaseConfig baseConfig() { + this.baseConfig.finalizeValue(); + return this.baseConfig.get(); + } + + protected MapProperty forcedPropertiesProperty() { + return forcedPropertiesProperty; + } + + protected ListProperty ignoredEntriesProperty() { + return ignoredEntries; + } + + protected FileCollection classpath() { + return classpath; + } + + protected Manifest manifest() { + return baseConfig().manifest(); + } + + protected EffectiveConfig buildEffectiveConfiguration(ResolvedDependency appArtifact) { + Map properties = new HashMap<>(); + + exportCustomManifestProperties(properties); + + String userIgnoredEntries = String.join(",", ignoredEntries.get()); + if (!userIgnoredEntries.isEmpty()) { + properties.put("quarkus.package.user-configured-ignored-entries", userIgnoredEntries); + } + + properties.putIfAbsent("quarkus.application.name", appArtifact.getArtifactId()); + properties.putIfAbsent("quarkus.application.version", appArtifact.getVersion()); + + return buildEffectiveConfiguration(properties); + } + + private EffectiveConfig buildEffectiveConfiguration(Map properties) { + Set resourcesDirs = getSourceSet(project, SourceSet.MAIN_SOURCE_SET_NAME).getResources().getSourceDirectories() + .getFiles(); + return EffectiveConfig.builder() + .withForcedProperties(forcedPropertiesProperty.get()) + .withTaskProperties(properties) + .withBuildProperties(quarkusBuildProperties.get()) + .withProjectProperties(project.getProperties()) + .withSourceDirectories(resourcesDirs) + .withProfile(quarkusProfile()) + .build(); + } + + String quarkusProfile() { + String profile = System.getProperty(QUARKUS_PROFILE); + if (profile == null) { + profile = System.getenv("QUARKUS_PROFILE"); + } + if (profile == null) { + profile = quarkusBuildProperties.get().get(QUARKUS_PROFILE); + } + if (profile == null) { + Object p = project.getProperties().get(QUARKUS_PROFILE); + if (p != null) { + profile = p.toString(); + } + } + if (profile == null) { + profile = "prod"; + } + return profile; + } + + private static FileCollection dependencyClasspath(SourceSet mainSourceSet) { + return mainSourceSet.getCompileClasspath().plus(mainSourceSet.getRuntimeClasspath()) + .plus(mainSourceSet.getAnnotationProcessorPath()) + .plus(mainSourceSet.getResources()); + } + + private void exportCustomManifestProperties(Map properties) { + for (Map.Entry attribute : baseConfig().manifest().getAttributes().entrySet()) { + properties.put(toManifestAttributeKey(attribute.getKey()), + attribute.getValue()); + } + + for (Map.Entry section : baseConfig().manifest().getSections().entrySet()) { + for (Map.Entry attribute : section.getValue().entrySet()) { + properties + .put(toManifestSectionAttributeKey(section.getKey(), attribute.getKey()), attribute.getValue()); + } + } + } + + private String toManifestAttributeKey(String key) { + if (key.contains("\"")) { + throw new GradleException("Manifest entry name " + key + " is invalid. \" characters are not allowed."); + } + return String.format("%s.\"%s\"", MANIFEST_ATTRIBUTES_PROPERTY_PREFIX, key); + } + + private String toManifestSectionAttributeKey(String section, String key) { + if (section.contains("\"")) { + throw new GradleException("Manifest section name " + section + " is invalid. \" characters are not allowed."); + } + if (key.contains("\"")) { + throw new GradleException("Manifest entry name " + key + " is invalid. \" characters are not allowed."); + } + return String.format("%s.\"%s\".\"%s\"", MANIFEST_SECTIONS_PROPERTY_PREFIX, section, + key); + } +} 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 new file mode 100644 index 0000000000000..6c57a8590af4d --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/BaseConfig.java @@ -0,0 +1,53 @@ +package io.quarkus.gradle.tasks; + +import java.util.Map; +import java.util.stream.Collectors; + +import io.quarkus.deployment.pkg.PackageConfig; +import io.quarkus.gradle.dsl.Manifest; +import io.quarkus.runtime.configuration.ConfigInstantiator; + +/** + * Required parts of the configuration used to configure a Quarkus build task, does not contain settings + * via the {@link io.quarkus.gradle.extension.QuarkusPluginExtension} or any "forced properties". + * + *

+ * Configuration from system properties, environment, application.properties/yaml/yml, project properties is + * available in a Gradle task's configuration phase. + */ +final class BaseConfig { + private final Manifest manifest; + private final PackageConfig packageConfig; + private final Map quarkusProperties; + + // Note: EffectiveConfig has all the code to load the configurations from all the sources. + BaseConfig(EffectiveConfig config) { + manifest = new Manifest(); + packageConfig = new PackageConfig(); + + ConfigInstantiator.handleObject(packageConfig, config.config()); + + // populate the Gradle Manifest object + 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)); + } + + PackageConfig packageConfig() { + return packageConfig; + } + + PackageConfig.BuiltInType packageType() { + return PackageConfig.BuiltInType.fromString(packageConfig.type); + } + + Manifest manifest() { + return manifest; + } + + Map quarkusProperties() { + return quarkusProperties; + } +} 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 new file mode 100644 index 0000000000000..769cea14dfd4b --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/EffectiveConfig.java @@ -0,0 +1,219 @@ +package io.quarkus.gradle.tasks; + +import static java.util.Collections.*; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; + +import org.eclipse.microprofile.config.spi.ConfigSource; +import org.eclipse.microprofile.config.spi.ConfigSourceProvider; + +import com.google.common.annotations.VisibleForTesting; + +import io.quarkus.deployment.configuration.ClassLoadingConfig; +import io.quarkus.deployment.pkg.PackageConfig; +import io.quarkus.runtime.configuration.ConfigUtils; +import io.smallrye.config.AbstractLocationConfigSourceLoader; +import io.smallrye.config.EnvConfigSource; +import io.smallrye.config.PropertiesConfigSource; +import io.smallrye.config.SmallRyeConfig; +import io.smallrye.config.common.utils.ConfigSourceUtil; +import io.smallrye.config.source.yaml.YamlConfigSource; + +/** + * Helper that bundles the various sources of config options for the Gradle plugin: system environment, system properties, + * quarkus build properties (on the Quarkus extension), Gradle project properties, application properties and "forced/task" + * properties (on the Gradle task). + * + *

+ * Eventually used to construct a map with the effective config options from all the sources above and expose + * the Quarkus config objects like {@link PackageConfig}, {@link ClassLoadingConfig} and the underlying {@link SmallRyeConfig}. + */ +final class EffectiveConfig { + private final Map fullConfig; + + // URLs of all application.properties/yaml/yml files that were consulted (including those that do not exist) + private final List applicationPropsSources; + private final SmallRyeConfig config; + + private EffectiveConfig(Builder builder) { + List configSources = new ArrayList<>(); + // TODO add io.quarkus.runtime.configuration.DefaultsConfigSource ? + // TODO leverage io.quarkus.runtime.configuration.ProfileManager ? + + // Effective "ordinals" for the config sources: + // 800 -> forcedProperties + // 700 -> taskProperties + // 600 -> System.getProperties() + // 500 -> System.getenv() + // 480 -> quarkusBuildProperties + // 300 -> projectProperties + // 0-250 -> application.(properties|yaml|yml) + + applicationPropsSources = new ArrayList<>(); + + configSources.add(new PropertiesConfigSource(builder.forcedProperties, "forcedProperties", 800)); + configSources.add(new PropertiesConfigSource(asStringMap(builder.taskProperties), "taskProperties", 700)); + configSources.add(new PropertiesConfigSource(ConfigSourceUtil.propertiesToMap(System.getProperties()), + "System.getProperties()", 600)); + configSources.add(new EnvConfigSource(500) { + }); + configSources.add(new PropertiesConfigSource(builder.buildProperties, "quarkusBuildProperties", 400)); + configSources.add(new PropertiesConfigSource(asStringMap(builder.projectProperties), "projectProperties", 300)); + + configSourcesForApplicationProperties(builder.sourceDirectories, applicationPropsSources::add, configSources::add); + this.config = buildConfig(builder.profile, configSources); + + this.fullConfig = generateFullConfigMap(config); + } + + SmallRyeConfig config() { + return config; + } + + private Map asStringMap(Map map) { + Map target = new HashMap<>(); + map.forEach((k, v) -> { + if (v != null) { + target.put(k, v.toString()); + } + }); + return target; + } + + @VisibleForTesting + static Map generateFullConfigMap(SmallRyeConfig config) { + Map map = new HashMap<>(); + config.getPropertyNames().forEach(property -> { + String v = config.getConfigValue(property).getValue(); + if (v != null) { + map.put(property, v); + } + }); + return unmodifiableMap(map); + } + + @VisibleForTesting + static SmallRyeConfig buildConfig(String profile, List configSources) { + return ConfigUtils.emptyConfigBuilder() + .withSources(configSources) + .withProfile(profile) + .build(); + } + + static Builder builder() { + return new Builder(); + } + + Map configMap() { + return fullConfig; + } + + List applicationPropsSources() { + return applicationPropsSources; + } + + static void configSourcesForApplicationProperties(Set sourceDirectories, Consumer sourceUrls, + Consumer configSourceConsumer) { + URL[] resourceUrls = sourceDirectories.stream().map(File::toURI) + .map(u -> { + try { + return u.toURL(); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + }) + .toArray(URL[]::new); + + for (URL resourceUrl : resourceUrls) { + URLClassLoader classLoader = new URLClassLoader(new URL[] { resourceUrl }); + CombinedConfigSourceProvider configSourceProvider = new CombinedConfigSourceProvider(sourceUrls, 250); + configSourceProvider.getConfigSources(classLoader).forEach(configSourceConsumer); + } + } + + static final class Builder { + private Map buildProperties = emptyMap(); + private Map projectProperties = emptyMap(); + private Map taskProperties = emptyMap(); + private Map forcedProperties = emptyMap(); + private Set sourceDirectories = emptySet(); + private String profile = "prod"; + + EffectiveConfig build() { + return new EffectiveConfig(this); + } + + Builder withForcedProperties(Map forcedProperties) { + this.forcedProperties = forcedProperties; + return this; + } + + Builder withTaskProperties(Map taskProperties) { + this.taskProperties = taskProperties; + return this; + } + + Builder withBuildProperties(Map buildProperties) { + this.buildProperties = buildProperties; + return this; + } + + Builder withProjectProperties(Map projectProperties) { + this.projectProperties = projectProperties; + return this; + } + + Builder withSourceDirectories(Set sourceDirectories) { + this.sourceDirectories = sourceDirectories; + return this; + } + + Builder withProfile(String profile) { + this.profile = profile; + return this; + } + } + + static final class CombinedConfigSourceProvider extends AbstractLocationConfigSourceLoader implements ConfigSourceProvider { + private final Consumer sourceUrls; + private final int ordinal; + + CombinedConfigSourceProvider(Consumer sourceUrls, int ordinal) { + this.sourceUrls = sourceUrls; + this.ordinal = ordinal; + } + + @Override + protected String[] getFileExtensions() { + return new String[] { + "application.yaml", + "application.yml", + "application.properties" + }; + } + + @Override + protected ConfigSource loadConfigSource(final URL url, final int ordinal) throws IOException { + sourceUrls.accept(url); + return url.getPath().endsWith(".properties") ? new PropertiesConfigSource(url, ordinal) + : new YamlConfigSource(url, ordinal); + } + + @Override + public List getConfigSources(final ClassLoader classLoader) { + // Note: + return loadConfigSources(new String[] { "application.properties", "application.yaml", "application.yml" }, ordinal, + classLoader); + } + } +} diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/ImageBuild.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/ImageBuild.java index 54af7a114d870..fc15a93f9dd68 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/ImageBuild.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/ImageBuild.java @@ -2,15 +2,15 @@ package io.quarkus.gradle.tasks; import java.util.List; -import java.util.Map; import java.util.stream.Collectors; import javax.inject.Inject; +import org.gradle.api.provider.MapProperty; import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.options.Option; -public abstract class ImageBuild extends ImageTask { +public abstract class ImageBuild extends QuarkusBuildTask { public enum Builder { docker, @@ -27,14 +27,11 @@ public void setBuilder(Builder builder) { } @Inject - public ImageBuild(QuarkusBuildConfiguration buildConfiguration) { - super(buildConfiguration, "Perform an image build"); - } - - @Override - public Map forcedProperties() { - return Map.of("quarkus.container-image.build", "true", - "quarkus.container-image.builder", builder.name()); + public ImageBuild() { + super("Perform an image build"); + MapProperty forcedProperties = extension().forcedPropertiesProperty(); + forcedProperties.put("quarkus.container-image.build", "true"); + forcedProperties.put("quarkus.container-image.builder", getProject().provider(() -> builder.name())); } @TaskAction diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/ImagePush.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/ImagePush.java index e1fe6732a19bf..895c645b4c780 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/ImagePush.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/ImagePush.java @@ -3,23 +3,21 @@ import java.util.Arrays; import java.util.List; -import java.util.Map; import java.util.stream.Collectors; import javax.inject.Inject; +import org.gradle.api.provider.MapProperty; import org.gradle.api.tasks.TaskAction; -public abstract class ImagePush extends ImageTask { +public abstract class ImagePush extends QuarkusBuildTask { @Inject - public ImagePush(QuarkusBuildConfiguration buildConfiguration) { - super(buildConfiguration, "Perfom an image push"); - } - - @Override - public Map forcedProperties() { - return Map.of("quarkus.container-image.push", "true"); + public ImagePush() { + super("Perform an image push"); + MapProperty forcedProperties = extension().forcedPropertiesProperty(); + forcedProperties.put("quarkus.container-image.build", "true"); + forcedProperties.put("quarkus.container-image.push", "true"); } @TaskAction diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/ImageTask.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/ImageTask.java deleted file mode 100644 index 8f41049fe4515..0000000000000 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/ImageTask.java +++ /dev/null @@ -1,16 +0,0 @@ - -package io.quarkus.gradle.tasks; - -import java.util.Map; - -public abstract class ImageTask extends QuarkusBuildProviderTask { - - public ImageTask(QuarkusBuildConfiguration buildConfiguration, String description) { - super(buildConfiguration, description); - } - - @Override - public Map forcedProperties() { - return Map.of("quarkus.container-image.build", "true"); - } -} diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusAddExtension.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusAddExtension.java index 2639c3cdfbbc2..78dc1a7e4f72b 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusAddExtension.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusAddExtension.java @@ -13,7 +13,7 @@ import io.quarkus.devtools.commands.AddExtensions; -public class QuarkusAddExtension extends QuarkusPlatformTask { +public abstract class QuarkusAddExtension extends QuarkusPlatformTask { public QuarkusAddExtension() { super("Adds Quarkus extensions specified by the user to the project."); diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuild.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuild.java index 1b3698e4f668a..41896664c5e20 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuild.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuild.java @@ -1,228 +1,349 @@ package io.quarkus.gradle.tasks; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; -import java.io.Serializable; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; -import java.util.Collections; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Properties; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.inject.Inject; import org.gradle.api.Action; import org.gradle.api.GradleException; -import org.gradle.api.file.FileCollection; -import org.gradle.api.java.archives.Attributes; +import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.MapProperty; import org.gradle.api.tasks.CacheableTask; -import org.gradle.api.tasks.Classpath; -import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.Internal; -import org.gradle.api.tasks.Optional; -import org.gradle.api.tasks.OutputDirectory; -import org.gradle.api.tasks.OutputFile; -import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.OutputDirectories; +import org.gradle.api.tasks.OutputFiles; +import org.gradle.api.tasks.PathSensitive; +import org.gradle.api.tasks.PathSensitivity; import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.options.Option; -import io.quarkus.bootstrap.BootstrapException; -import io.quarkus.bootstrap.app.CuratedApplication; -import io.quarkus.bootstrap.app.QuarkusBootstrap; -import io.quarkus.bootstrap.model.ApplicationModel; -import io.quarkus.bootstrap.resolver.AppModelResolverException; +import io.quarkus.deployment.pkg.PackageConfig; import io.quarkus.gradle.dsl.Manifest; -import io.quarkus.maven.dependency.GACTV; import io.quarkus.runtime.util.StringUtil; @CacheableTask -public abstract class QuarkusBuild extends QuarkusTask { +public abstract class QuarkusBuild extends QuarkusBuildTask { private static final String NATIVE_PROPERTY_NAMESPACE = "quarkus.native"; - private static final String MANIFEST_SECTIONS_PROPERTY_PREFIX = "quarkus.package.manifest.manifest-sections"; - private static final String MANIFEST_ATTRIBUTES_PROPERTY_PREFIX = "quarkus.package.manifest.attributes"; - - private static final String OUTPUT_DIRECTORY = "quarkus.package.output-directory"; - - private List ignoredEntries = new ArrayList<>(); - private Manifest manifest = new Manifest(); - - private Properties applicationProperties = new Properties(); @Inject public QuarkusBuild() { - super("Quarkus builds a runner jar based on the build jar"); - } - - public QuarkusBuild(String description) { - super(description); + super("Builds a Quarkus application."); } + @SuppressWarnings("unused") public QuarkusBuild nativeArgs(Action> action) { Map nativeArgsMap = new HashMap<>(); action.execute(nativeArgsMap); for (Map.Entry nativeArg : nativeArgsMap.entrySet()) { - System.setProperty(expandConfigurationKey(nativeArg.getKey()), nativeArg.getValue().toString()); + getForcedProperties().put(expandConfigurationKey(nativeArg.getKey()), nativeArg.getValue().toString()); } return this; } - @Optional - @Input - public abstract MapProperty getForcedProperties(); + @Internal + public MapProperty getForcedProperties() { + return extension().forcedPropertiesProperty(); + } - @Optional - @Input - public List getIgnoredEntries() { - return ignoredEntries; + @Internal + public ListProperty getIgnoredEntries() { + return extension().ignoredEntriesProperty(); } @Option(description = "When using the uber-jar option, this option can be used to " + "specify one or more entries that should be excluded from the final jar", option = "ignored-entry") public void setIgnoredEntries(List ignoredEntries) { - this.ignoredEntries.addAll(ignoredEntries); - } - - @Classpath - public FileCollection getClasspath() { - SourceSet mainSourceSet = QuarkusGradleUtils.getSourceSet(getProject(), SourceSet.MAIN_SOURCE_SET_NAME); - return mainSourceSet.getCompileClasspath().plus(mainSourceSet.getRuntimeClasspath()) - .plus(mainSourceSet.getAnnotationProcessorPath()) - .plus(mainSourceSet.getResources()); - } - - @Input - public Map getQuarkusBuildSystemProperties() { - Map quarkusSystemProperties = new HashMap<>(); - for (Map.Entry systemProperty : System.getProperties().entrySet()) { - if (systemProperty.getKey().toString().startsWith("quarkus.") && - systemProperty.getValue() instanceof Serializable) { - quarkusSystemProperties.put(systemProperty.getKey(), systemProperty.getValue()); - } - } - return quarkusSystemProperties; - } - - @Input - public Map getQuarkusBuildEnvProperties() { - Map quarkusEnvProperties = new HashMap<>(); - for (Map.Entry systemProperty : System.getenv().entrySet()) { - if (systemProperty.getKey() != null && systemProperty.getKey().startsWith("QUARKUS_")) { - quarkusEnvProperties.put(systemProperty.getKey(), systemProperty.getValue()); - } - } - return quarkusEnvProperties; + getIgnoredEntries().addAll(ignoredEntries); } @Internal public Manifest getManifest() { - return this.manifest; + return extension().manifest(); } + @SuppressWarnings("unused") public QuarkusBuild manifest(Action action) { action.execute(this.getManifest()); return this; } - @OutputFile + @Internal public File getRunnerJar() { - return new File(getProject().getBuildDir(), String.format("%s.jar", extension().buildNativeRunnerName(Map.of()))); + return runnerJar(); } - @OutputFile + @Internal public File getNativeRunner() { - return new File(getProject().getBuildDir(), extension().buildNativeRunnerName(Map.of())); + return nativeRunner(); } - @OutputDirectory + @Internal public File getFastJar() { - return new File(getProject().getBuildDir(), - this.getPropValueWithPrecedence(OUTPUT_DIRECTORY, java.util.Optional.of("quarkus-app"))); + return fastJar(); } - @TaskAction - public void buildQuarkus() { - final ApplicationModel appModel; - final Map forcedProperties = getForcedProperties().getOrElse(Collections.emptyMap()); - - try { - appModel = extension().getAppModelResolver().resolveModel(new GACTV(getProject().getGroup().toString(), - getProject().getName(), getProject().getVersion().toString())); - } catch (AppModelResolverException e) { - throw new GradleException("Failed to resolve Quarkus application model for " + getProject().getPath(), e); - } + @Internal + public File getArtifactProperties() { + return artifactProperties(); + } - final Properties effectiveProperties = getBuildSystemProperties(appModel.getAppArtifact()); - effectiveProperties.putAll(forcedProperties); - if (ignoredEntries != null && ignoredEntries.size() > 0) { - String joinedEntries = String.join(",", ignoredEntries); - effectiveProperties.setProperty("quarkus.package.user-configured-ignored-entries", joinedEntries); + @OutputDirectories + protected Map getBuildOutputDirectories() { + Map outputs = new HashMap<>(); + PackageConfig.BuiltInType packageType = packageType(); + switch (packageType) { + case LEGACY_JAR: + case LEGACY: + outputs.put("fast-jar", fastJar()); + outputs.put("legacy-lib", getProject().getBuildDir().toPath().resolve("lib").toFile()); + break; + case JAR: + case FAST_JAR: + case NATIVE: + outputs.put("fast-jar", fastJar()); + break; + case MUTABLE_JAR: + case UBER_JAR: + outputs.put("fast-jar", fastJar()); + outputs.put("generated", genBuildDir().toFile()); + break; + case NATIVE_SOURCES: + outputs.put("fast-jar", fastJar()); + outputs.put("generated", genBuildDir().toFile()); + outputs.put("native-source", nativeSources()); + break; + default: + throw new GradleException("Unsupported package type " + packageType); } + return outputs; + } - exportCustomManifestProperties(effectiveProperties); - - try (CuratedApplication appCreationContext = QuarkusBootstrap.builder() - .setBaseClassLoader(getClass().getClassLoader()) - .setExistingModel(appModel) - .setTargetDirectory(getProject().getBuildDir().toPath()) - .setBaseName(extension().finalName()) - .setBuildSystemProperties(effectiveProperties) - .setAppArtifact(appModel.getAppArtifact()) - .setLocalProjectDiscovery(false) - .setIsolateDeployment(true) - .build().bootstrap()) { - - // Processes launched from within the build task of Gradle (daemon) lose content - // generated on STDOUT/STDERR by the process (see https://github.com/gradle/gradle/issues/13522). - // We overcome this by letting build steps know that the STDOUT/STDERR should be explicitly - // streamed, if they need to make available that generated data. - // The io.quarkus.deployment.pkg.builditem.ProcessInheritIODisabled$Factory - // does the necessary work to generate such a build item which the build step(s) can rely on - appCreationContext.createAugmentor("io.quarkus.deployment.pkg.builditem.ProcessInheritIODisabled$Factory", - Collections.emptyMap()).createProductionApplication(); - - } catch (BootstrapException e) { - throw new GradleException("Failed to build a runnable JAR", e); + @OutputFiles + protected Map getBuildOutputFiles() { + Map outputs = new HashMap<>(); + PackageConfig.BuiltInType packageType = packageType(); + switch (packageType) { + case LEGACY_JAR: + case LEGACY: + outputs.put("legacy-jar", runnerJar()); + outputs.put("artifact-properties", artifactProperties()); + break; + case JAR: + case FAST_JAR: + case MUTABLE_JAR: + case NATIVE_SOURCES: + outputs.put("artifact-properties", artifactProperties()); + break; + case NATIVE: + outputs.put("native-runner", nativeRunner()); + outputs.put("artifact-properties", artifactProperties()); + break; + case UBER_JAR: + outputs.put("uber-jar", runnerJar()); + outputs.put("artifact-properties", artifactProperties()); + break; + default: + throw new GradleException("Unsupported package type " + packageType); } + return outputs; } - private void exportCustomManifestProperties(Properties buildSystemProperties) { - if (this.manifest == null) { - return; + @InputFiles + @PathSensitive(PathSensitivity.RELATIVE) + protected Collection getBuildInputFiles() { + List inputs = new ArrayList<>(); + PackageConfig.BuiltInType packageType = packageType(); + switch (packageType) { + case JAR: + case FAST_JAR: + case NATIVE: + fastJarBuildInputs(inputs::add); + break; + case LEGACY_JAR: + case LEGACY: + legacyJarBuildInputs(inputs::add); + break; + case MUTABLE_JAR: + case NATIVE_SOURCES: + case UBER_JAR: + break; + default: + throw new GradleException("Unsupported package type " + packageType); } + return inputs; + } - for (Map.Entry attribute : manifest.getAttributes().entrySet()) { - buildSystemProperties.put(toManifestAttributeKey(attribute.getKey()), - attribute.getValue()); + @TaskAction + public void finalizeQuarkusBuild() { + cleanup(); + + PackageConfig.BuiltInType packageType = packageType(); + switch (packageType) { + case JAR: + case FAST_JAR: + case NATIVE: + assembleFastJar(); + break; + case LEGACY_JAR: + case LEGACY: + assembleLegacyJar(); + break; + case MUTABLE_JAR: + case UBER_JAR: + case NATIVE_SOURCES: + generateBuild(); + assembleFullBuild(); + break; + default: + throw new GradleException("Unsupported package type " + packageType); } + } - for (Map.Entry section : manifest.getSections().entrySet()) { - for (Map.Entry attribute : section.getValue().entrySet()) { - buildSystemProperties - .put(toManifestSectionAttributeKey(section.getKey(), attribute.getKey()), attribute.getValue()); - } + private void cleanup() { + // This 'cleanup' removes all _potential_ result artifacts (runners, fast-jar-directory), which is important, + // because if output artifacts for a 'fast-jar' build are left "behind" and the next run uses the 'uber-jar' + // package type, then the (developer machine) local cache artifact would contain the (potentially outdated) + // fast-jar directory _and_ the uber-jar. Gradle could restore the result with the "newest" uber-jar + // _including_ the potentially (quite outdated) fast-jar, which may likely confuse users. + // + // Previous versions of the Quarkus Gradle plugin left artifacts built with for example different + // package types, output directory, output name where they were. + if (extension().getCleanupBuildOutput().get()) { + getLogger().info("Removing potentially existing runner files and fast-jar directory."); + getFileSystemOperations().delete(delete -> delete.delete(getRunnerJar(), getNativeRunner(), fastJar())); } } - private String toManifestAttributeKey(String key) { - if (key.contains("\"")) { - throw new GradleException("Manifest entry name " + key + " is invalid. \" characters are not allowed."); - } - return String.format("%s.\"%s\"", MANIFEST_ATTRIBUTES_PROPERTY_PREFIX, key); + private void legacyJarBuildInputs(Consumer buildInputs) { + buildInputs.accept(depBuildDir().resolve("lib").toFile()); + buildInputs.accept(appBuildDir().resolve("lib").toFile()); + runnerAndArtifactsInputs(buildInputs, appBuildDir()); } - private String toManifestSectionAttributeKey(String section, String key) { - if (section.contains("\"")) { - throw new GradleException("Manifest section name " + section + " is invalid. \" characters are not allowed."); - } - if (key.contains("\"")) { - throw new GradleException("Manifest entry name " + key + " is invalid. \" characters are not allowed."); + private void assembleLegacyJar() { + getLogger().info("Finalizing Quarkus build for {} packaging", packageType()); + + Path buildDir = getProject().getBuildDir().toPath(); + Path libDir = buildDir.resolve("lib"); + Path depBuildDir = depBuildDir(); + Path appBuildDir = appBuildDir(); + + getLogger().info("Removing potentially existing legacy-jar lib/ directory."); + getFileSystemOperations().delete(delete -> delete.delete(libDir)); + + getLogger().info("Copying lib/ directory from {} into {}", depBuildDir, buildDir); + getFileSystemOperations().copy(copy -> { + copy.into(buildDir); + copy.from(depBuildDir); + copy.include("lib/**"); + }); + + getLogger().info("Copying lib/ directory from {} into {}", appBuildDir, buildDir); + getFileSystemOperations().copy(copy -> { + copy.into(buildDir); + copy.from(appBuildDir); + copy.include("lib/**"); + }); + + // Quarkus' 'legacy-jar' package type produces 'lib/modified-*.jar' files for some dependencies. + // The following code block removes the non-modified jars. + getLogger().info("Cleaning up lib/ directory in {}", buildDir); + try (Stream files = Files.walk(libDir)) { + files.filter(Files::isRegularFile).filter(f -> f.getFileName().toString().startsWith("modified-")) + .map(f -> f.getParent().resolve(f.getFileName().toString().substring("modified-".length()))) + .collect(Collectors.toList()) // necessary :( + .forEach(f -> { + try { + Files.deleteIfExists(f); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } catch (IOException e) { + throw new GradleException("Failed to clean up non-modified jars in lib/"); } - return String.format("%s.\"%s\".\"%s\"", MANIFEST_SECTIONS_PROPERTY_PREFIX, section, - key); + + copyRunnersAndArtifactProperties(appBuildDir); + } + + private void assembleFullBuild() { + File targetDir = getProject().getBuildDir(); + + // build/quarkus-build/gen + Path genBuildDir = genBuildDir(); + + getLogger().info("Copying Quarkus build for {} packaging from {} into {}", packageType(), + genBuildDir, targetDir); + getFileSystemOperations().copy(copy -> { + copy.into(targetDir); + copy.from(genBuildDir); + }); + + copyRunnersAndArtifactProperties(genBuildDir()); + } + + private void fastJarBuildInputs(Consumer buildInputs) { + Path appBuildBaseDir = appBuildDir(); + buildInputs.accept(genBuildDir().toFile()); + buildInputs.accept(appBuildBaseDir.resolve(outputDirectory()).toFile()); + + runnerAndArtifactsInputs(buildInputs, appBuildBaseDir); + } + + private void assembleFastJar() { + File appTargetDir = fastJar(); + + // build/quarkus-build/app + Path appBuildBaseDir = appBuildDir(); + // build/quarkus-build/app/quarkus-app + Path appBuildDir = appBuildBaseDir.resolve(outputDirectory()); + // build/quarkus-build/dep + Path depBuildDir = depBuildDir(); + + getLogger().info("Synchronizing Quarkus build for {} packaging from {} and {} into {}", packageType(), + appBuildDir, depBuildDir, appTargetDir); + getFileSystemOperations().sync(sync -> { + sync.into(appTargetDir); + sync.from(appBuildDir, depBuildDir); + }); + + copyRunnersAndArtifactProperties(appBuildBaseDir); + } + + private void runnerAndArtifactsInputs(Consumer buildInputs, Path sourceDir) { + buildInputs.accept(sourceDir.resolve(QUARKUS_ARTIFACT_PROPERTIES).toFile()); + buildInputs.accept(sourceDir.resolve(nativeRunnerFileName()).toFile()); + buildInputs.accept(sourceDir.resolve(runnerJarFileName()).toFile()); + // TODO jib-image* ?? + buildInputs.accept(sourceDir.resolve(runnerBaseName() + "-native-image-source-jar").toFile()); + } + + private void copyRunnersAndArtifactProperties(Path sourceDir) { + File buildDir = getProject().getBuildDir(); + + getLogger().info("Copying remaining Quarkus application artifacts for {} packaging from {} into {}", + packageType(), sourceDir, buildDir); + getFileSystemOperations().copy( + copy -> copy.into(buildDir).from(sourceDir) + .include(QUARKUS_ARTIFACT_PROPERTIES, + nativeRunnerFileName(), + runnerJarFileName(), + "jib-image*", + NATIVE_SOURCES, + runnerBaseName() + "-native-image-source-jar/**")); } private String expandConfigurationKey(String shortKey) { @@ -232,40 +353,4 @@ private String expandConfigurationKey(String shortKey) { } return String.format("%s.%s", NATIVE_PROPERTY_NAMESPACE, hyphenatedKey); } - - private String getPropValueWithPrecedence(final String propName, final java.util.Optional defaultValue) { - if (applicationProperties.isEmpty()) { - SourceSet mainSourceSet = QuarkusGradleUtils.getSourceSet(getProject(), SourceSet.MAIN_SOURCE_SET_NAME); - - FileCollection configFiles = mainSourceSet.getResources() - .filter(file -> "application.properties".equalsIgnoreCase(file.getName())); - configFiles.forEach(file -> { - FileInputStream appPropsIS = null; - try { - appPropsIS = new FileInputStream(file.getAbsoluteFile()); - applicationProperties.load(appPropsIS); - appPropsIS.close(); - } catch (IOException e) { - if (appPropsIS != null) { - try { - appPropsIS.close(); - } catch (IOException ex) { - // Ignore exception closing. - } - } - } - }); - } - Map quarkusBuildProperties = extension().getQuarkusBuildProperties().get(); - if (quarkusBuildProperties.containsKey(propName)) { - return quarkusBuildProperties.get(propName); - } else if (applicationProperties.contains(propName)) { - return applicationProperties.getProperty(propName); - } else if (getQuarkusBuildEnvProperties().containsKey(propName)) { - return getQuarkusBuildEnvProperties().get(propName); - } else if (defaultValue.isPresent()) { - return defaultValue.get(); - } - return null; - } } diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildCacheableAppParts.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildCacheableAppParts.java new file mode 100644 index 0000000000000..44eb98d61c9d3 --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildCacheableAppParts.java @@ -0,0 +1,136 @@ +package io.quarkus.gradle.tasks; + +import java.io.File; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; + +import javax.inject.Inject; + +import org.gradle.api.GradleException; +import org.gradle.api.tasks.CacheableTask; +import org.gradle.api.tasks.Internal; +import org.gradle.api.tasks.OutputDirectories; +import org.gradle.api.tasks.TaskAction; + +import io.quarkus.deployment.pkg.PackageConfig; +import io.quarkus.gradle.QuarkusPlugin; + +@CacheableTask +public abstract class QuarkusBuildCacheableAppParts extends QuarkusBuildTask { + + @Inject + public QuarkusBuildCacheableAppParts() { + super("Quarkus application build with the ability to cache the built artifacts, excluding dependencies." + + " Do not use this task directly, use '" + QuarkusPlugin.QUARKUS_BUILD_TASK_NAME + "'"); + } + + @Internal + public boolean isCachedByDefault() { + switch (packageType()) { + case JAR: + case FAST_JAR: + case LEGACY_JAR: + case LEGACY: + return true; + default: + return false; + } + } + + /** + * Points to {@code build/quarkus-build/app} and includes the uber-jar, native runner and "quarkus-app" directory + * w/o the `lib/` folder. + */ + @OutputDirectories + public Map getOutputDirectories() { + Map outputs = new HashMap<>(); + PackageConfig.BuiltInType packageType = packageType(); + switch (packageType) { + case JAR: + case FAST_JAR: + case NATIVE: + case LEGACY_JAR: + case LEGACY: + outputs.put("app-build-dir", appBuildDir().toFile()); + break; + case MUTABLE_JAR: + case UBER_JAR: + case NATIVE_SOURCES: + break; + default: + throw new GradleException("Unsupported package type " + packageType); + } + return outputs; + } + + @TaskAction + public void performQuarkusBuild() { + Path appDir = appBuildDir(); + + // Caching and "up-to-date" checks depend on the inputs, this 'delete()' should ensure that the up-to-date + // checks work against "clean" outputs, considering that the outputs depend on the package-type. + getFileSystemOperations().delete(delete -> delete.delete(appDir)); + + PackageConfig.BuiltInType packageType = packageType(); + switch (packageType) { + case JAR: + case FAST_JAR: + case NATIVE: + fastJarBuild(); + break; + case LEGACY_JAR: + case LEGACY: + legacyJarBuild(); + break; + case MUTABLE_JAR: + case UBER_JAR: + case NATIVE_SOURCES: + getLogger().info( + "Falling back to 'full quarkus application build' for package type {}, this task's output is empty for this package type", + packageType); + break; + default: + throw new GradleException("Unsupported package type " + packageType); + } + } + + private void legacyJarBuild() { + generateBuild(); + + Path genDir = genBuildDir(); + Path appDir = appBuildDir(); + + getLogger().info("Synchronizing Quarkus legacy-jar app for package type {} into {}", packageType(), + appDir); + + getFileSystemOperations().sync(sync -> { + sync.into(appDir); + sync.from(genDir); + sync.include("**", QuarkusPlugin.QUARKUS_ARTIFACT_PROPERTIES); + sync.exclude("lib/**"); + }); + getFileSystemOperations().copy(copy -> { + copy.into(appDir); + copy.from(genDir); + copy.include("lib/modified-*"); + }); + } + + private void fastJarBuild() { + generateBuild(); + + String outputDirectory = outputDirectory(); + Path genDir = genBuildDir(); + Path appDir = appBuildDir(); + + getLogger().info("Synchronizing Quarkus fast-jar-like app for package type {} into {}", packageType(), + appDir); + + getFileSystemOperations().sync(sync -> { + sync.into(appDir); + sync.from(genDir); + sync.exclude(outputDirectory + "/lib/**"); + }); + } +} diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildConfiguration.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildConfiguration.java deleted file mode 100644 index 844feb76619a2..0000000000000 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildConfiguration.java +++ /dev/null @@ -1,39 +0,0 @@ - -package io.quarkus.gradle.tasks; - -import java.util.List; -import java.util.Map; - -import org.gradle.api.Project; -import org.gradle.api.provider.ListProperty; -import org.gradle.api.provider.MapProperty; - -public class QuarkusBuildConfiguration { - - private final Project project; - - private ListProperty forcedDependencies; - private MapProperty forcedProperties; - - public QuarkusBuildConfiguration(Project project) { - this.project = project; - forcedDependencies = project.getObjects().listProperty(String.class); - forcedProperties = project.getObjects().mapProperty(String.class, String.class); - } - - public ListProperty getForcedDependencies() { - return forcedDependencies; - } - - public void setForcedDependencies(List forcedDependencies) { - this.forcedDependencies.addAll(forcedDependencies); - } - - public MapProperty getForcedProperties() { - return forcedProperties; - } - - public void setForcedProperties(Map forcedProperties) { - this.forcedProperties.putAll(forcedProperties); - } -} diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildDependencies.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildDependencies.java new file mode 100644 index 0000000000000..72a975ff844df --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildDependencies.java @@ -0,0 +1,188 @@ +package io.quarkus.gradle.tasks; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.inject.Inject; + +import org.gradle.api.GradleException; +import org.gradle.api.tasks.OutputDirectories; +import org.gradle.api.tasks.TaskAction; + +import io.quarkus.bootstrap.model.ApplicationModel; +import io.quarkus.deployment.pkg.PackageConfig; +import io.quarkus.gradle.QuarkusPlugin; +import io.quarkus.maven.dependency.ArtifactKey; +import io.quarkus.maven.dependency.DependencyFlags; +import io.quarkus.maven.dependency.ResolvedDependency; + +/** + * Collect the Quarkus app dependencies, the contents of the {@code quarkus-app/lib} folder, without making the task + * cache anything, but still provide up-to-date checks. + * + *

+ * Caching dependency jars is wasted effort and unnecessarily pollutes the Gradle build cache. + */ +public abstract class QuarkusBuildDependencies extends QuarkusBuildTask { + + @Inject + public QuarkusBuildDependencies() { + super("Collect dependencies for the Quarkus application to be built. " + + "Do not use this task directly, use '" + QuarkusPlugin.QUARKUS_BUILD_TASK_NAME + "'"); + } + + /** + * Points to {@code build/quarkus-build/dep}. + */ + @OutputDirectories + public Map getOutputDirectories() { + Map outputs = new HashMap<>(); + PackageConfig.BuiltInType packageType = packageType(); + switch (packageType) { + case JAR: + case FAST_JAR: + case NATIVE: + case LEGACY_JAR: + case LEGACY: + outputs.put("dependencies-dir", depBuildDir().toFile()); + break; + case MUTABLE_JAR: + case UBER_JAR: + case NATIVE_SOURCES: + break; + default: + throw new GradleException("Unsupported package type " + packageType); + } + return outputs; + } + + @TaskAction + public void collectDependencies() { + Path depDir = depBuildDir(); + + // Caching and "up-to-date" checks depend on the inputs, this 'delete()' should ensure that the up-to-date + // checks work against "clean" outputs, considering that the outputs depend on the package-type. + getFileSystemOperations().delete(delete -> delete.delete(depDir)); + + PackageConfig.BuiltInType packageType = packageType(); + switch (packageType) { + case JAR: + case FAST_JAR: + case NATIVE: + fastJarDependencies(); + break; + case LEGACY_JAR: + case LEGACY: + legacyJarDependencies(); + break; + case MUTABLE_JAR: + case UBER_JAR: + case NATIVE_SOURCES: + getLogger().info( + "Falling back to 'full quarkus application build' for package type {}, this task's output is empty for this package type", + packageType); + break; + default: + throw new GradleException("Unsupported package type " + packageType); + } + } + + /** + * Resolves and copies dependencies for the {@code jar}, {@code fast-jar} and {@code native} package types. + * Does not work for {@code mutable-jar} ({@code lib/deployment/} missing). + * Unnecessary for {@code uber-jar}. + */ + private void fastJarDependencies() { + Path depDir = depBuildDir(); + Path libBoot = depDir.resolve("lib/boot"); + Path libMain = depDir.resolve("lib/main"); + jarDependencies(libBoot, libMain); + } + + /** + * Resolves and copies the dependencies for the {@code legacy-jar} package type. + * + *

+ * Side node: Quarkus' {@code legacy-jar} package type produces {@code modified-*.jar} files for some + * dependencies, but this implementation has no knowledge of which dependencies will be modified. + */ + private void legacyJarDependencies() { + Path depDir = depBuildDir(); + Path lib = depDir.resolve("lib"); + jarDependencies(lib, lib); + } + + private void jarDependencies(Path libBoot, Path libMain) { + Path depDir = depBuildDir(); + + getLogger().info("Placing Quarkus application dependencies for package type {} in {}", packageType(), + depDir); + + try { + Files.createDirectories(libBoot); + Files.createDirectories(libMain); + } catch (IOException e) { + throw new GradleException(String.format("Failed to create directories in %s", depDir), e); + } + + ApplicationModel appModel = resolveAppModelForBuild(); + EffectiveConfig effectiveConfig = extension().buildEffectiveConfiguration(appModel.getAppArtifact()); + + // see https://quarkus.io/guides/class-loading-reference#configuring-class-loading + String removedArtifactsProp = effectiveConfig + .configMap().getOrDefault(QuarkusPlugin.CLASS_LOADING_PARENT_FIRST_ARTIFACTS, ""); + java.util.Optional> optionalDependencies = java.util.Optional.ofNullable( + effectiveConfig.configMap().getOrDefault(QuarkusPlugin.CLASS_LOADING_REMOVED_ARTIFACTS, null)) + .map(s -> Arrays.stream(s.split(",")) + .map(String::trim) + .filter(gact -> !gact.isEmpty()) + .map(ArtifactKey::fromString) + .collect(Collectors.toSet())); + Set removedArtifacts = Arrays.stream(removedArtifactsProp.split(",")) + .map(String::trim) + .filter(gact -> !gact.isEmpty()) + .map(ArtifactKey::fromString) + .collect(Collectors.toSet()); + + appModel.getRuntimeDependencies().stream() + .filter(appDep -> { + // copied from io.quarkus.deployment.pkg.steps.JarResultBuildStep.includeAppDep + if (!appDep.isJar()) { + return false; + } + if (appDep.isOptional()) { + return optionalDependencies.map(appArtifactKeys -> appArtifactKeys.contains(appDep.getKey())) + .orElse(true); + } + return !removedArtifacts.contains(appDep.getKey()); + }) + .map(dep -> Map.entry(dep.isFlagSet(DependencyFlags.CLASSLOADER_RUNNER_PARENT_FIRST) ? libBoot : libMain, dep)) + .peek(depAndTarget -> { + ResolvedDependency dep = depAndTarget.getValue(); + Path targetDir = depAndTarget.getKey(); + dep.getResolvedPaths().forEach(p -> { + String file = dep.getGroupId() + '.' + p.getFileName(); + Path target = targetDir.resolve(file); + if (!Files.exists(target)) { + getLogger().debug("Dependency {} : copying {} to {}", + dep.toGACTVString(), + p, target); + try { + Files.copy(p, target); + } catch (IOException e) { + throw new GradleException(String.format("Failed to copy %s to %s", p, target), e); + } + } + }); + }) + .collect(Collectors.toMap(Map.Entry::getKey, depAndTarget -> 1, Integer::sum)) + .forEach((path, count) -> getLogger().info("Copied {} files into {}", count, path)); + } +} diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildProviderTask.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildProviderTask.java deleted file mode 100644 index 615fa3d62e61e..0000000000000 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildProviderTask.java +++ /dev/null @@ -1,26 +0,0 @@ - -package io.quarkus.gradle.tasks; - -import java.util.Map; - -import javax.inject.Inject; - -import org.gradle.api.tasks.TaskAction; - -public abstract class QuarkusBuildProviderTask extends QuarkusTask { - - private final QuarkusBuildConfiguration buildConfiguration; - - @Inject - public QuarkusBuildProviderTask(QuarkusBuildConfiguration buildConfiguration, String description) { - super(description); - this.buildConfiguration = buildConfiguration; - } - - public abstract Map forcedProperties(); - - @TaskAction - public void configureBuild() { - buildConfiguration.setForcedProperties(forcedProperties()); - } -} 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 new file mode 100644 index 0000000000000..e2feed8f64b08 --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildTask.java @@ -0,0 +1,189 @@ +package io.quarkus.gradle.tasks; + +import java.io.File; +import java.nio.file.Path; +import java.util.Map; +import java.util.stream.Collectors; + +import javax.inject.Inject; + +import org.gradle.api.GradleException; +import org.gradle.api.file.FileCollection; +import org.gradle.api.file.FileSystemOperations; +import org.gradle.api.logging.LogLevel; +import org.gradle.api.tasks.Classpath; +import org.gradle.api.tasks.Input; +import org.gradle.process.JavaForkOptions; +import org.gradle.workers.WorkQueue; + +import io.quarkus.bootstrap.model.ApplicationModel; +import io.quarkus.bootstrap.resolver.AppModelResolverException; +import io.quarkus.deployment.pkg.PackageConfig; +import io.quarkus.gradle.QuarkusPlugin; +import io.quarkus.gradle.tasks.worker.BuildWorker; +import io.quarkus.maven.dependency.GACTV; + +/** + * Base class for the {@link QuarkusBuildDependencies}, {@link QuarkusBuildCacheableAppParts}, {@link QuarkusBuild} tasks + */ +abstract class QuarkusBuildTask extends QuarkusTask { + private static final String QUARKUS_BUILD_DIR = "quarkus-build"; + private static final String QUARKUS_BUILD_GEN_DIR = QUARKUS_BUILD_DIR + "/gen"; + private static final String QUARKUS_BUILD_APP_DIR = QUARKUS_BUILD_DIR + "/app"; + private static final String QUARKUS_BUILD_DEP_DIR = QUARKUS_BUILD_DIR + "/dep"; + static final String QUARKUS_ARTIFACT_PROPERTIES = "quarkus-artifact.properties"; + static final String NATIVE_SOURCES = "native-sources"; + + QuarkusBuildTask(String description) { + super(description); + } + + @Inject + protected abstract FileSystemOperations getFileSystemOperations(); + + @Classpath + public FileCollection getClasspath() { + return extension().classpath(); + } + + @Input + public Map getCachingRelevantInput() { + return extension().baseConfig().quarkusProperties(); + } + + PackageConfig.BuiltInType packageType() { + return extension().baseConfig().packageType(); + } + + Path genBuildDir() { + return getProject().getBuildDir().toPath().resolve(QUARKUS_BUILD_GEN_DIR); + } + + Path appBuildDir() { + return getProject().getBuildDir().toPath().resolve(QUARKUS_BUILD_APP_DIR); + } + + Path depBuildDir() { + return getProject().getBuildDir().toPath().resolve(QUARKUS_BUILD_DEP_DIR); + } + + File artifactProperties() { + return new File(getProject().getBuildDir(), QUARKUS_ARTIFACT_PROPERTIES); + } + + File nativeSources() { + return new File(getProject().getBuildDir(), NATIVE_SOURCES); + } + + /** + * "final" location of the "fast-jar". + */ + File fastJar() { + return new File(getProject().getBuildDir(), outputDirectory()); + } + + /** + * "final" location of the "uber-jar". + */ + File runnerJar() { + return new File(getProject().getBuildDir(), runnerJarFileName()); + } + + /** + * "final" location of the "native" runner. + */ + File nativeRunner() { + return new File(getProject().getBuildDir(), nativeRunnerFileName()); + } + + String runnerJarFileName() { + return runnerName() + ".jar"; + } + + String nativeRunnerFileName() { + return runnerName(); + } + + String runnerName() { + return runnerBaseName() + runnerSuffix(); + } + + String runnerBaseName() { + BaseConfig baseConfig = extension().baseConfig(); + return baseConfig.packageConfig().outputName.orElse(extension().finalName()); + } + + String outputDirectory() { + BaseConfig baseConfig = extension().baseConfig(); + return baseConfig.packageConfig().outputDirectory.orElse(QuarkusPlugin.DEFAULT_OUTPUT_DIRECTORY); + } + + private String runnerSuffix() { + BaseConfig baseConfig = extension().baseConfig(); + return baseConfig.packageConfig().getRunnerSuffix(); + } + + ApplicationModel resolveAppModelForBuild() { + ApplicationModel appModel; + try { + GACTV gactv = new GACTV(getProject().getGroup().toString(), getProject().getName(), + getProject().getVersion().toString()); + appModel = extension().getAppModelResolver().resolveModel(gactv); + } catch (AppModelResolverException e) { + throw new GradleException("Failed to resolve Quarkus application model for " + getProject().getPath(), e); + } + return appModel; + } + + void generateBuild() { + Path genDir = genBuildDir(); + PackageConfig.BuiltInType packageType = packageType(); + getLogger().info("Building Quarkus app for package type {} in {}", packageType, genDir); + + // Caching and "up-to-date" checks depend on the inputs, this 'delete()' should ensure that the up-to-date + // checks work against "clean" outputs, considering that the outputs depend on the package-type. + getFileSystemOperations().delete(delete -> delete.delete(genDir)); + + ApplicationModel appModel = resolveAppModelForBuild(); + EffectiveConfig effectiveConfig = extension().buildEffectiveConfiguration(appModel.getAppArtifact()); + + getLogger().info("Starting Quarkus application build for package type {}", packageType); + + if (getLogger().isEnabled(LogLevel.INFO)) { + getLogger().info("Effective properties: {}", + effectiveConfig.configMap().entrySet().stream() + .filter(e -> e.getKey().startsWith("quarkus.")).map(e -> "" + e) + .sorted() + .collect(Collectors.joining("\n ", "\n ", ""))); + } + + WorkQueue workQueue = getWorkerExecutor().processIsolation(processWorkerSpec -> { + JavaForkOptions forkOptions = processWorkerSpec.getForkOptions(); + extension().buildForkOptions.forEach(a -> a.execute(forkOptions)); + + // It's kind of a "very big hammer" here, but this way we ensure that all 'quarkus.*' properties from + // all configuration sources are (forcefully) used in the Quarkus build - even properties defined on the + // QuarkusPluginExtension. + // This prevents that settings from e.g. a application.properties takes precedence over an explicit + // setting in Gradle project properties, the Quarkus extension or even via the environment or system + // properties. + // Note that we MUST NOT mess with the system properties of the JVM running the build! And that is the + // main reason why build and code generation happen in a separate process. + effectiveConfig.configMap().entrySet().stream().filter(e -> e.getKey().startsWith("quarkus.")) + .forEach(e -> forkOptions.systemProperty(e.getKey(), e.getValue())); + + // populate worker classpath with additional content? + // or maybe remove some dependencies from the plugin and make those exclusively available to the worker? + // processWorkerSpec.getClasspath().from(); + }); + + workQueue.submit(BuildWorker.class, params -> { + params.getBuildSystemProperties().putAll(effectiveConfig.configMap()); + params.getBaseName().set(extension().finalName()); + params.getTargetDirectory().set(genDir.toFile()); + params.getAppModel().set(appModel); + }); + + workQueue.await(); + } +} diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java index 9a3ebcf051b5b..7cf78b780a576 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java @@ -72,7 +72,7 @@ import io.quarkus.paths.PathList; import io.quarkus.runtime.LaunchMode; -public class QuarkusDev extends QuarkusTask { +public abstract class QuarkusDev extends QuarkusTask { public static final String IO_QUARKUS_DEVMODE_ARGS = "io.quarkus.devmode-args"; 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 c10b0b814a4e6..1dbb6ab3da60e 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 @@ -1,37 +1,34 @@ package io.quarkus.gradle.tasks; import java.io.File; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.HashSet; import java.util.List; -import java.util.Properties; import java.util.Set; -import java.util.function.Consumer; +import java.util.stream.Collectors; import org.gradle.api.GradleException; import org.gradle.api.artifacts.Configuration; +import org.gradle.api.tasks.CacheableTask; import org.gradle.api.tasks.CompileClasspath; import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.OutputDirectory; +import org.gradle.api.tasks.PathSensitive; +import org.gradle.api.tasks.PathSensitivity; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.SourceSetContainer; import org.gradle.api.tasks.TaskAction; +import org.gradle.process.JavaForkOptions; +import org.gradle.workers.WorkQueue; -import io.quarkus.bootstrap.BootstrapException; -import io.quarkus.bootstrap.app.CuratedApplication; -import io.quarkus.bootstrap.app.QuarkusBootstrap; -import io.quarkus.bootstrap.classloading.QuarkusClassLoader; import io.quarkus.bootstrap.model.ApplicationModel; -import io.quarkus.deployment.CodeGenerator; -import io.quarkus.paths.PathCollection; -import io.quarkus.paths.PathList; +import io.quarkus.gradle.tasks.worker.CodeGenWorker; import io.quarkus.runtime.LaunchMode; -public class QuarkusGenerateCode extends QuarkusTask { +@CacheableTask +public abstract class QuarkusGenerateCode extends QuarkusTask { public static final String QUARKUS_GENERATED_SOURCES = "quarkus-generated-sources"; public static final String QUARKUS_TEST_GENERATED_SOURCES = "quarkus-test-generated-sources"; @@ -39,11 +36,8 @@ public class QuarkusGenerateCode extends QuarkusTask { public static final String[] CODE_GENERATION_PROVIDER = new String[] { "grpc", "avdl", "avpr", "avsc" }; public static final String[] CODE_GENERATION_INPUT = new String[] { "proto", "avro" }; - public static final String INIT_AND_RUN = "initAndRun"; private Set sourcesDirectories; private Configuration compileClasspath; - private Consumer sourceRegistrar = (p) -> { - }; private boolean test = false; private boolean devMode = false; @@ -66,6 +60,7 @@ public void setCompileClasspath(Configuration compileClasspath) { } @InputFiles + @PathSensitive(PathSensitivity.RELATIVE) public Set getInputDirectory() { Set inputDirectories = new HashSet<>(); @@ -90,62 +85,59 @@ public File getGeneratedOutputDirectory() { } @TaskAction - public void prepareQuarkus() { + public void generateCode() { LaunchMode launchMode = test ? LaunchMode.TEST : devMode ? LaunchMode.DEVELOPMENT : LaunchMode.NORMAL; - final ApplicationModel appModel = extension().getApplicationModel(launchMode); - final Properties realProperties = getBuildSystemProperties(appModel.getAppArtifact()); - - Path buildDir = getProject().getBuildDir().toPath(); - try (CuratedApplication appCreationContext = QuarkusBootstrap.builder() - .setBaseClassLoader(getClass().getClassLoader()) - .setExistingModel(appModel) - .setTargetDirectory(buildDir) - .setBaseName(extension().finalName()) - .setBuildSystemProperties(realProperties) - .setAppArtifact(appModel.getAppArtifact()) - .setLocalProjectDiscovery(false) - .setIsolateDeployment(true) - .build().bootstrap()) { - - SourceSetContainer sourceSets = getProject().getExtensions().getByType(SourceSetContainer.class); - final String generateSourcesDir = test ? QUARKUS_TEST_GENERATED_SOURCES : QUARKUS_GENERATED_SOURCES; - final SourceSet generatedSources = sourceSets.getByName(generateSourcesDir); - List paths = new ArrayList<>(); - generatedSources.getOutput() - .filter(f -> f.getName().equals(generateSourcesDir)) - .forEach(f -> paths.add(f.toPath())); - if (paths.isEmpty()) { - throw new GradleException("Failed to create quarkus-generated-sources"); - } - - getLogger().debug("Will trigger preparing sources for source directory: {} buildDir: {}", - sourcesDirectories, getProject().getBuildDir().getAbsolutePath()); - - QuarkusClassLoader deploymentClassLoader = appCreationContext.createDeploymentClassLoader(); - Class codeGenerator = deploymentClassLoader.loadClass(CodeGenerator.class.getName()); - - Method initAndRun; - try { - initAndRun = codeGenerator.getMethod(INIT_AND_RUN, QuarkusClassLoader.class, PathCollection.class, - Path.class, Path.class, - Consumer.class, ApplicationModel.class, Properties.class, String.class, - boolean.class); - } catch (Exception e) { - throw new GradleException("Quarkus code generation phase has failed", e); - } - - initAndRun.invoke(null, deploymentClassLoader, - PathList.from(sourcesDirectories), - paths.get(0), - buildDir, - sourceRegistrar, - appCreationContext.getApplicationModel(), - realProperties, - launchMode.name(), - test); - } catch (BootstrapException | IllegalAccessException | InvocationTargetException | ClassNotFoundException e) { - throw new GradleException("Failed to generate sources in the QuarkusPrepare task", e); + ApplicationModel appModel = extension().getApplicationModel(launchMode); + EffectiveConfig effectiveConfig = extension().buildEffectiveConfiguration(appModel.getAppArtifact()); + + SourceSetContainer sourceSets = getProject().getExtensions().getByType(SourceSetContainer.class); + final String generateSourcesDir = test ? QUARKUS_TEST_GENERATED_SOURCES : QUARKUS_GENERATED_SOURCES; + final SourceSet generatedSources = sourceSets.getByName(generateSourcesDir); + List paths = new ArrayList<>(); + generatedSources.getOutput() + .filter(f -> f.getName().equals(generateSourcesDir)) + .forEach(paths::add); + if (paths.isEmpty()) { + throw new GradleException("Failed to create quarkus-generated-sources"); } + File outputPath = paths.get(0); + + getLogger().debug("Will trigger preparing sources for source directory: {} buildDir: {}", + sourcesDirectories, getProject().getBuildDir().getAbsolutePath()); + + WorkQueue workQueue = getWorkerExecutor().processIsolation(processWorkerSpec -> { + JavaForkOptions forkOptions = processWorkerSpec.getForkOptions(); + extension().codeGenForkOptions.forEach(a -> a.execute(forkOptions)); + + // It's kind of a "very big hammer" here, but this way we ensure that all 'quarkus.*' properties from + // all configuration sources are (forcefully) used in the Quarkus build - even properties defined on the + // QuarkusPluginExtension. + // This prevents that settings from e.g. a application.properties takes precedence over an explicit + // setting in Gradle project properties, the Quarkus extension or even via the environment or system + // properties. + // Note that we MUST NOT mess with the system properties of the JVM running the build! And that is the + // main reason why build and code generation happen in a separate process. + effectiveConfig.configMap().entrySet().stream().filter(e -> e.getKey().startsWith("quarkus.")) + .forEach(e -> forkOptions.systemProperty(e.getKey(), e.getValue())); + + // populate worker classpath with additional content? + // or maybe remove some dependencies from the plugin and make those exclusively available to the worker? + // processWorkerSpec.getClasspath().from(); + }); + + workQueue.submit(CodeGenWorker.class, params -> { + params.getBuildSystemProperties().putAll(effectiveConfig.configMap()); + params.getBaseName().set(extension().finalName()); + params.getTargetDirectory().set(getProject().getBuildDir()); + params.getAppModel().set(appModel); + params.getSourceDirectories().setFrom(sourcesDirectories.stream().map(Path::toFile).collect(Collectors.toList())); + params.getOutputPath().set(outputPath); + params.getLaunchMode().set(launchMode); + params.getTest().set(test); + }); + + workQueue.await(); + } public void setSourcesDirectories(Set sourcesDirectories) { diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusGoOffline.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusGoOffline.java index b215002be3c39..71d3c7ed2c866 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusGoOffline.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusGoOffline.java @@ -8,7 +8,7 @@ import io.quarkus.runtime.LaunchMode; -public class QuarkusGoOffline extends QuarkusTask { +public abstract class QuarkusGoOffline extends QuarkusTask { private Configuration compileClasspath; private Configuration testCompileClasspath; diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusInfo.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusInfo.java index 35d6c402b0dcd..053c5aca16b14 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusInfo.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusInfo.java @@ -10,7 +10,7 @@ import io.quarkus.devtools.commands.handlers.ProjectInfoCommandHandler; import io.quarkus.devtools.project.QuarkusProject; -public class QuarkusInfo extends QuarkusPlatformTask { +public abstract class QuarkusInfo extends QuarkusPlatformTask { private boolean perModule = false; diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusListCategories.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusListCategories.java index aafd222fc9c73..894a7f2b8e1ef 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusListCategories.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusListCategories.java @@ -9,7 +9,7 @@ import io.quarkus.devtools.commands.ListCategories; import io.quarkus.devtools.project.QuarkusProject; -public class QuarkusListCategories extends QuarkusPlatformTask { +public abstract class QuarkusListCategories extends QuarkusPlatformTask { private static final String DEFAULT_FORMAT = "concise"; diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusListExtensions.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusListExtensions.java index bc52857b20a0a..28c26eda2eed5 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusListExtensions.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusListExtensions.java @@ -9,7 +9,7 @@ import io.quarkus.devtools.commands.ListExtensions; import io.quarkus.devtools.project.QuarkusProject; -public class QuarkusListExtensions extends QuarkusPlatformTask { +public abstract class QuarkusListExtensions extends QuarkusPlatformTask { private static final String DEFAULT_FORMAT = "concise"; diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusListPlatforms.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusListPlatforms.java index ae671f78c16c9..f5ee805f1e341 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusListPlatforms.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusListPlatforms.java @@ -8,7 +8,7 @@ import io.quarkus.devtools.commands.ListPlatforms; import io.quarkus.registry.Constants; -public class QuarkusListPlatforms extends QuarkusPlatformTask { +public abstract class QuarkusListPlatforms extends QuarkusPlatformTask { private boolean installed = false; diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusRemoteDev.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusRemoteDev.java index e63bbde779c39..9891ced032d1c 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusRemoteDev.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusRemoteDev.java @@ -6,7 +6,7 @@ import io.quarkus.gradle.extension.QuarkusPluginExtension; -public class QuarkusRemoteDev extends QuarkusDev { +public abstract class QuarkusRemoteDev extends QuarkusDev { @Inject public QuarkusRemoteDev(Configuration quarkusDevConfiguration, QuarkusPluginExtension extension) { diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusRemoveExtension.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusRemoveExtension.java index 0d11fa2f81c67..1dd92c3eadeb3 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusRemoveExtension.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusRemoveExtension.java @@ -13,7 +13,7 @@ import io.quarkus.devtools.commands.RemoveExtensions; -public class QuarkusRemoveExtension extends QuarkusPlatformTask { +public abstract class QuarkusRemoveExtension extends QuarkusPlatformTask { public QuarkusRemoveExtension() { super("Removes Quarkus extensions specified by the user to the project."); diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusShowEffectiveConfig.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusShowEffectiveConfig.java new file mode 100644 index 0000000000000..5126c05ed67c7 --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusShowEffectiveConfig.java @@ -0,0 +1,90 @@ +package io.quarkus.gradle.tasks; + +import static java.lang.String.format; +import static java.nio.file.Files.newBufferedWriter; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Properties; +import java.util.stream.Collectors; + +import javax.inject.Inject; + +import org.gradle.api.GradleException; +import org.gradle.api.provider.Property; +import org.gradle.api.tasks.Internal; +import org.gradle.api.tasks.TaskAction; +import org.gradle.api.tasks.options.Option; + +/** + * Just show the effective configuration and settings. + */ +public abstract class QuarkusShowEffectiveConfig extends QuarkusBuildTask { + + private final Property saveConfigProperties; + + @Inject + public QuarkusShowEffectiveConfig() { + super("Collect dependencies for the Quarkus application, prefer the 'quarkusBuild' task"); + this.saveConfigProperties = getProject().getObjects().property(Boolean.class).convention(Boolean.FALSE); + } + + @Option(option = "save-config-properties", description = "Save the effective Quarkus configuration properties to a file.") + @Internal + public Property getSaveConfigProperties() { + return saveConfigProperties; + } + + @TaskAction + public void dumpEffectiveConfiguration() { + try { + EffectiveConfig effective = extension() + .buildEffectiveConfiguration(extension().getApplicationModel().getAppArtifact()); + + String config = effective.configMap().entrySet().stream() + .filter(e -> e.getKey().startsWith("quarkus.")) + .map(e -> format("%s=%s", e.getKey(), e.getValue())).sorted() + .collect(Collectors.joining("\n ", "\n ", "\n")); + + getLogger().lifecycle("Effective Quarkus configuration options: {}", config); + + String finalName = extension().finalName(); + String packageType = effective.configMap().getOrDefault("quarkus.package.type", "fast-jar"); + File fastJar = fastJar(); + getLogger().lifecycle( + "" + + "Quarkus package type: {}\n" + + "Final name: {}\n" + + "Output directory: {}\n" + + "Fast jar directory (if built): {}\n" + + "Runner jar (if built): {}\n" + + "Native runner (if built): {}\n" + + "application.(properties|yaml|yml) sources: {}", + packageType, + finalName, + outputDirectory(), + fastJar, + runnerJar(), + nativeRunner(), + effective.applicationPropsSources().stream().map(Object::toString) + .collect(Collectors.joining("\n ", "\n ", "\n"))); + + if (getSaveConfigProperties().get()) { + Properties props = new Properties(); + props.putAll(effective.configMap()); + Path file = getProject().getBuildDir().toPath().resolve(finalName + ".quarkus-build.properties"); + try (BufferedWriter writer = newBufferedWriter(file)) { + props.store(writer, format("Quarkus build properties with package type %s", packageType)); + } catch (IOException e) { + throw new GradleException("Failed to write Quarkus build configuration settings", e); + } + getLogger().lifecycle("\nWrote configuration settings to {}", file); + } + } catch (Exception e) { + e.printStackTrace(); + throw new GradleException("WTF", e); + } + } +} diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusTask.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusTask.java index e16c737d6009f..4516ca924ce28 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusTask.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusTask.java @@ -1,12 +1,11 @@ package io.quarkus.gradle.tasks; -import java.util.Map; -import java.util.Properties; +import javax.inject.Inject; import org.gradle.api.DefaultTask; +import org.gradle.workers.WorkerExecutor; import io.quarkus.gradle.extension.QuarkusPluginExtension; -import io.quarkus.maven.dependency.ResolvedDependency; public abstract class QuarkusTask extends DefaultTask { @@ -17,32 +16,13 @@ public abstract class QuarkusTask extends DefaultTask { setGroup("quarkus"); } + @Inject + protected abstract WorkerExecutor getWorkerExecutor(); + QuarkusPluginExtension extension() { if (extension == null) { extension = getProject().getExtensions().findByType(QuarkusPluginExtension.class); } return extension; } - - protected Properties getBuildSystemProperties(ResolvedDependency appArtifact) { - final Map properties = getProject().getProperties(); - final Properties realProperties = new Properties(); - for (Map.Entry entry : properties.entrySet()) { - final String key = entry.getKey(); - final Object value = entry.getValue(); - if (key != null && value instanceof String && key.startsWith("quarkus.")) { - realProperties.setProperty(key, (String) value); - } - } - Map quarkusBuildProperties = extension().getQuarkusBuildProperties().get(); - if (!quarkusBuildProperties.isEmpty()) { - quarkusBuildProperties.entrySet().stream().filter(entry -> entry.getKey().startsWith("quarkus.")) - .forEach(entry -> { - realProperties.put(entry.getKey(), entry.getValue()); - }); - } - realProperties.putIfAbsent("quarkus.application.name", appArtifact.getArtifactId()); - realProperties.putIfAbsent("quarkus.application.version", appArtifact.getVersion()); - return realProperties; - } } diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusTest.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusTest.java index fc786d664e754..1c3632fb68e7a 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusTest.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusTest.java @@ -10,7 +10,7 @@ import io.quarkus.deployment.dev.IsolatedTestModeMain; import io.quarkus.gradle.extension.QuarkusPluginExtension; -public class QuarkusTest extends QuarkusDev { +public abstract class QuarkusTest extends QuarkusDev { @Inject public QuarkusTest(Configuration quarkusDevConfiguration, QuarkusPluginExtension extension) { diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusTestConfig.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusTestConfig.java index c0975eecccbfd..0d26297514463 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusTestConfig.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusTestConfig.java @@ -2,7 +2,7 @@ import org.gradle.api.tasks.TaskAction; -public class QuarkusTestConfig extends QuarkusTask { +public abstract class QuarkusTestConfig extends QuarkusTask { public QuarkusTestConfig() { super("Deprecated. Used to set the necessary system properties for the Quarkus tests to run. Replaced with an action configured for every test by the Quarkus plugin"); diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusUpdate.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusUpdate.java index 57420e002877d..613ce9b051e57 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusUpdate.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusUpdate.java @@ -15,7 +15,7 @@ import io.quarkus.registry.catalog.ExtensionCatalog; import io.quarkus.registry.catalog.PlatformStreamCoords; -public class QuarkusUpdate extends QuarkusPlatformTask { +public abstract class QuarkusUpdate extends QuarkusPlatformTask { private boolean perModule = false; private String targetStreamId; diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/worker/BuildWorker.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/worker/BuildWorker.java new file mode 100644 index 0000000000000..abeb3c37b36bb --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/worker/BuildWorker.java @@ -0,0 +1,80 @@ +package io.quarkus.gradle.tasks.worker; + +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.Properties; +import java.util.stream.Collectors; + +import org.gradle.api.GradleException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.quarkus.bootstrap.BootstrapException; +import io.quarkus.bootstrap.app.ArtifactResult; +import io.quarkus.bootstrap.app.AugmentAction; +import io.quarkus.bootstrap.app.AugmentResult; +import io.quarkus.bootstrap.app.CuratedApplication; +import io.quarkus.bootstrap.app.JarResult; +import io.quarkus.maven.dependency.ResolvedDependency; + +public abstract class BuildWorker extends QuarkusWorker { + private static final Logger LOGGER = LoggerFactory.getLogger(BuildWorker.class); + + @Override + public void execute() { + BuildWorkerParams params = getParameters(); + Properties props = buildSystemProperties(); + + ResolvedDependency appArtifact = params.getAppModel().get().getAppArtifact(); + String gav = appArtifact.getGroupId() + ":" + appArtifact.getArtifactId() + ":" + appArtifact.getVersion(); + LOGGER.info("Building Quarkus application {}", gav); + LOGGER.info(" base name: {}", params.getBaseName().get()); + LOGGER.info(" target directory: {}", params.getTargetDirectory().getAsFile().get()); + LOGGER.info(" configured package type: {}", props.getProperty("quarkus.package.type")); + LOGGER.info(" configured output directory: {}", props.getProperty("quarkus.package.output-directory")); + LOGGER.info(" configured output name: {}", props.getProperty("quarkus.package.output-name")); + + try (CuratedApplication appCreationContext = createAppCreationContext()) { + + // Processes launched from within the build task of Gradle (daemon) lose content + // generated on STDOUT/STDERR by the process (see https://github.com/gradle/gradle/issues/13522). + // We overcome this by letting build steps know that the STDOUT/STDERR should be explicitly + // streamed, if they need to make available that generated data. + // The io.quarkus.deployment.pkg.builditem.ProcessInheritIODisabled$Factory + // does the necessary work to generate such a build item which the build step(s) can rely on + AugmentAction augmentor = appCreationContext + .createAugmentor("io.quarkus.deployment.pkg.builditem.ProcessInheritIODisabled$Factory", + Collections.emptyMap()); + + AugmentResult result = augmentor.createProductionApplication(); + if (result == null) { + System.err.println("createProductionApplication() returned 'null' AugmentResult"); + } else { + Path nativeResult = result.getNativeResult(); + LOGGER.info("AugmentResult.nativeResult = {}", nativeResult); + List results = result.getResults(); + if (results == null) { + LOGGER.warn("AugmentResult.results = null"); + } else { + LOGGER.info("AugmentResult.results = {}", results.stream().map(ArtifactResult::getPath) + .map(Object::toString).collect(Collectors.joining("\n ", "\n ", ""))); + } + JarResult jar = result.getJar(); + LOGGER.info("AugmentResult:"); + if (jar == null) { + LOGGER.info(" .jar = null"); + } else { + LOGGER.info(" .jar.path = {}", jar.getPath()); + LOGGER.info(" .jar.libraryDir = {}", jar.getLibraryDir()); + LOGGER.info(" .jar.originalArtifact = {}", jar.getOriginalArtifact()); + LOGGER.info(" .jar.uberJar = {}", jar.isUberJar()); + } + } + + LOGGER.info("Quarkus application build was successful"); + } catch (BootstrapException e) { + throw new GradleException("Failed to build Quarkus application", e); + } + } +} diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/worker/BuildWorkerParams.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/worker/BuildWorkerParams.java new file mode 100644 index 0000000000000..430c728a9c2e0 --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/worker/BuildWorkerParams.java @@ -0,0 +1,4 @@ +package io.quarkus.gradle.tasks.worker; + +public interface BuildWorkerParams extends QuarkusParams { +} diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/worker/CodeGenWorker.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/worker/CodeGenWorker.java new file mode 100644 index 0000000000000..fa19eb3f54084 --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/worker/CodeGenWorker.java @@ -0,0 +1,87 @@ +package io.quarkus.gradle.tasks.worker; + +import java.io.File; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.file.Path; +import java.util.Properties; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +import org.gradle.api.GradleException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.quarkus.bootstrap.BootstrapException; +import io.quarkus.bootstrap.app.CuratedApplication; +import io.quarkus.bootstrap.classloading.QuarkusClassLoader; +import io.quarkus.bootstrap.model.ApplicationModel; +import io.quarkus.deployment.CodeGenerator; +import io.quarkus.maven.dependency.ResolvedDependency; +import io.quarkus.paths.PathCollection; +import io.quarkus.paths.PathList; + +public abstract class CodeGenWorker extends QuarkusWorker { + private static final Logger LOGGER = LoggerFactory.getLogger(CodeGenWorker.class); + + public static final String INIT_AND_RUN = "initAndRun"; + + @Override + public void execute() { + CodeGenWorkerParams params = getParameters(); + Properties props = buildSystemProperties(); + + ResolvedDependency appArtifact = params.getAppModel().get().getAppArtifact(); + Path buildDir = params.getTargetDirectory().getAsFile().get().toPath(); + Path generatedSourceDir = params.getOutputPath().get().getAsFile().toPath(); + + String gav = appArtifact.getGroupId() + ":" + appArtifact.getArtifactId() + ":" + appArtifact.getVersion(); + LOGGER.info("Generating Quarkus code for {}", gav); + LOGGER.info(" launch mode: {}", params.getLaunchMode().get()); + LOGGER.info(" base name: {}", params.getBaseName().get()); + LOGGER.info(" generated source directory: {}", generatedSourceDir); + LOGGER.info(" build directory: {}", buildDir); + + try (CuratedApplication appCreationContext = createAppCreationContext()) { + + QuarkusClassLoader deploymentClassLoader = appCreationContext.createDeploymentClassLoader(); + Class codeGenerator = deploymentClassLoader.loadClass(CodeGenerator.class.getName()); + + Method initAndRun; + try { + initAndRun = codeGenerator.getMethod(INIT_AND_RUN, QuarkusClassLoader.class, PathCollection.class, + Path.class, Path.class, + Consumer.class, ApplicationModel.class, Properties.class, String.class, + boolean.class); + } catch (Exception e) { + throw new GradleException("Quarkus code generation phase has failed", e); + } + + Consumer sourceRegistrar = (p) -> { + }; + + initAndRun.invoke(null, + // QuarkusClassLoader classLoader, + deploymentClassLoader, + // PathCollection sourceParentDirs, + PathList.from( + params.getSourceDirectories().getFiles().stream().map(File::toPath).collect(Collectors.toList())), + // Path generatedSourcesDir, + generatedSourceDir, + // Path buildDir, + buildDir, + // Consumer sourceRegistrar, + sourceRegistrar, + // ApplicationModel appModel, + appCreationContext.getApplicationModel(), + // Properties properties, + props, + // String launchMode, + params.getLaunchMode().get().name(), + // boolean test + params.getTest().get()); + } catch (BootstrapException | IllegalAccessException | InvocationTargetException | ClassNotFoundException e) { + throw new GradleException("Failed to generate sources in the QuarkusPrepare task", e); + } + } +} diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/worker/CodeGenWorkerParams.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/worker/CodeGenWorkerParams.java new file mode 100644 index 0000000000000..f64a7a090d659 --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/worker/CodeGenWorkerParams.java @@ -0,0 +1,18 @@ +package io.quarkus.gradle.tasks.worker; + +import org.gradle.api.file.ConfigurableFileCollection; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.provider.Property; + +import io.quarkus.runtime.LaunchMode; + +public interface CodeGenWorkerParams extends QuarkusParams { + + ConfigurableFileCollection getSourceDirectories(); + + DirectoryProperty getOutputPath(); + + Property getLaunchMode(); + + Property getTest(); +} diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/worker/QuarkusParams.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/worker/QuarkusParams.java new file mode 100644 index 0000000000000..534fe0e6d04fd --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/worker/QuarkusParams.java @@ -0,0 +1,18 @@ +package io.quarkus.gradle.tasks.worker; + +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.provider.MapProperty; +import org.gradle.api.provider.Property; +import org.gradle.workers.WorkParameters; + +import io.quarkus.bootstrap.model.ApplicationModel; + +public interface QuarkusParams extends WorkParameters { + DirectoryProperty getTargetDirectory(); + + MapProperty getBuildSystemProperties(); + + Property getBaseName(); + + Property getAppModel(); +} diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/worker/QuarkusWorker.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/worker/QuarkusWorker.java new file mode 100644 index 0000000000000..f366d7977f9cf --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/worker/QuarkusWorker.java @@ -0,0 +1,37 @@ +package io.quarkus.gradle.tasks.worker; + +import java.nio.file.Path; +import java.util.Properties; + +import org.gradle.workers.WorkAction; + +import io.quarkus.bootstrap.BootstrapException; +import io.quarkus.bootstrap.app.CuratedApplication; +import io.quarkus.bootstrap.app.QuarkusBootstrap; +import io.quarkus.bootstrap.model.ApplicationModel; + +public abstract class QuarkusWorker

implements WorkAction

{ + + Properties buildSystemProperties() { + Properties props = new Properties(); + props.putAll(getParameters().getBuildSystemProperties().get()); + return props; + } + + CuratedApplication createAppCreationContext() throws BootstrapException { + QuarkusParams params = getParameters(); + Path buildDir = params.getTargetDirectory().getAsFile().get().toPath(); + String baseName = params.getBaseName().get(); + ApplicationModel appModel = params.getAppModel().get(); + return QuarkusBootstrap.builder() + .setBaseClassLoader(getClass().getClassLoader()) + .setExistingModel(appModel) + .setTargetDirectory(buildDir) + .setBaseName(baseName) + .setBuildSystemProperties(buildSystemProperties()) + .setAppArtifact(appModel.getAppArtifact()) + .setLocalProjectDiscovery(false) + .setIsolateDeployment(true) + .build().bootstrap(); + } +} diff --git a/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/QuarkusPluginTest.java b/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/QuarkusPluginTest.java index 107f407dd6f6e..128d532844ef1 100644 --- a/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/QuarkusPluginTest.java +++ b/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/QuarkusPluginTest.java @@ -2,9 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.gradle.testkit.runner.TaskOutcome.SUCCESS; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; import java.io.File; import java.io.IOException; @@ -30,9 +28,6 @@ public class QuarkusPluginTest { - @TempDir - Path testProjectDir; - @Test public void shouldCreateTasks() { Project project = ProjectBuilder.builder().build(); @@ -41,6 +36,8 @@ public void shouldCreateTasks() { assertTrue(project.getPluginManager().hasPlugin(QuarkusPlugin.ID)); TaskContainer tasks = project.getTasks(); + assertNotNull(tasks.getByName(QuarkusPlugin.QUARKUS_BUILD_APP_PARTS_TASK_NAME)); + assertNotNull(tasks.getByName(QuarkusPlugin.QUARKUS_BUILD_DEP_TASK_NAME)); assertNotNull(tasks.getByName(QuarkusPlugin.QUARKUS_BUILD_TASK_NAME)); assertNotNull(tasks.getByName(QuarkusPlugin.QUARKUS_DEV_TASK_NAME)); assertNotNull(tasks.getByName(QuarkusPlugin.BUILD_NATIVE_TASK_NAME)); @@ -70,9 +67,19 @@ public void shouldMakeQuarkusDevAndQuarkusBuildDependOnClassesTask() { TaskContainer tasks = project.getTasks(); + Task quarkusAppPartsBuild = tasks.getByName(QuarkusPlugin.QUARKUS_BUILD_APP_PARTS_TASK_NAME); + assertThat(getDependantProvidedTaskName(quarkusAppPartsBuild)) + .contains(JavaPlugin.CLASSES_TASK_NAME) + .contains(QuarkusPlugin.QUARKUS_GENERATE_CODE_TASK_NAME); + + Task quarkusDepBuild = tasks.getByName(QuarkusPlugin.QUARKUS_BUILD_DEP_TASK_NAME); + assertThat(getDependantProvidedTaskName(quarkusDepBuild)) + .isEmpty(); + Task quarkusBuild = tasks.getByName(QuarkusPlugin.QUARKUS_BUILD_TASK_NAME); assertThat(getDependantProvidedTaskName(quarkusBuild)) - .contains(JavaPlugin.CLASSES_TASK_NAME); + .contains(QuarkusPlugin.QUARKUS_BUILD_APP_PARTS_TASK_NAME) + .contains(QuarkusPlugin.QUARKUS_BUILD_APP_PARTS_TASK_NAME); Task quarkusDev = tasks.getByName(QuarkusPlugin.QUARKUS_DEV_TASK_NAME); assertThat(getDependantProvidedTaskName(quarkusDev)) @@ -94,11 +101,10 @@ public void shouldReturnMultipleOutputSourceDirectories() { new File(project.getBuildDir(), "classes/java/test"), new File(project.getBuildDir(), "classes/scala/main"), new File(project.getBuildDir(), "classes/scala/test")); - } @Test - public void shouldNotFailOnProjectDependenciesWithoutMain() throws IOException { + public void shouldNotFailOnProjectDependenciesWithoutMain(@TempDir Path testProjectDir) throws IOException { var kotlinVersion = System.getProperty("kotlin_version", "1.8.10"); var settingFile = testProjectDir.resolve("settings.gradle.kts"); var mppProjectDir = testProjectDir.resolve("mpp"); @@ -158,7 +164,7 @@ public void shouldNotFailOnProjectDependenciesWithoutMain() throws IOException { assertEquals(SUCCESS, result.task(":quarkus:quarkusGenerateCode").getOutcome()); } - private static final List getDependantProvidedTaskName(Task task) { + private static List getDependantProvidedTaskName(Task task) { List dependantTaskNames = new ArrayList<>(); for (Object t : task.getDependsOn()) { try { @@ -169,13 +175,4 @@ private static final List getDependantProvidedTaskName(Task task) { } return dependantTaskNames; } - - private static final String getFinalizedByTaskName(Task task) { - try { - return ((Provider) task.getFinalizedBy()).get().getName(); - } catch (ClassCastException e) { - // Nothing to do here - return null; - } - } } diff --git a/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/extension/QuarkusExtensionTest.java b/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/extension/QuarkusExtensionTest.java new file mode 100644 index 0000000000000..922f37a7bd82b --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/extension/QuarkusExtensionTest.java @@ -0,0 +1,18 @@ +package io.quarkus.gradle.extension; + +import static io.quarkus.gradle.QuarkusPlugin.EXTENSION_NAME; + +import org.gradle.api.Project; +import org.gradle.testfixtures.ProjectBuilder; +import org.junit.jupiter.api.Test; + +public class QuarkusExtensionTest { + @Test + public void extensionInstantiates() { + Project project = ProjectBuilder.builder().build(); + project.getPluginManager().apply("java"); + + QuarkusPluginExtension extension = project.getExtensions().create(EXTENSION_NAME, QuarkusPluginExtension.class, + project); + } +} 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 new file mode 100644 index 0000000000000..254b9ba3ea29f --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/tasks/CachingTest.java @@ -0,0 +1,180 @@ +package io.quarkus.gradle.tasks; + +import static org.junit.jupiter.params.provider.Arguments.arguments; + +import java.io.File; +import java.io.IOException; +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.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.apache.commons.io.FileUtils; +import org.assertj.core.api.SoftAssertions; +import org.assertj.core.api.junit.jupiter.InjectSoftAssertions; +import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension; +import org.gradle.testkit.runner.BuildResult; +import org.gradle.testkit.runner.BuildTask; +import org.gradle.testkit.runner.GradleRunner; +import org.gradle.testkit.runner.TaskOutcome; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +@ExtendWith(SoftAssertionsExtension.class) +public class CachingTest { + @InjectSoftAssertions + SoftAssertions soft; + + @TempDir + Path testProjectDir; + @TempDir + Path saveDir; + + static Stream gradleCaching() { + return Stream.of("fast-jar", "uber-jar", "mutable-jar", "legacy-jar", "native-sources") + .flatMap(packageType -> Stream.of(arguments(packageType, true), arguments(packageType, false))) + .flatMap(args -> Stream.of(arguments(args.get()[0], args.get()[1], null), + arguments(args.get()[0], args.get()[1], "some-output-dir"))); + } + + @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()); + + Map env = simulateCI ? Map.of("CI", "yes") : Map.of(); + + List args = new ArrayList<>(); + Collections.addAll(args, "build", "--info", "--stacktrace", "--build-cache", "-Dquarkus.package.type=" + packageType); + if (outputDir != null) { + 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); + + // Purge the whole build/ directory + + Path buildDir = testProjectDir.resolve("build"); + + Path saveBuildDir = saveDir.resolve("build"); + FileUtils.moveDirectory(buildDir.toFile(), saveBuildDir.toFile()); + + soft.assertThat(buildDir).doesNotExist(); + + // 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); + + Path quarkusBuildGen = Paths.get("quarkus-build", "gen"); + boolean isFastJar = "fast-jar".equals(packageType); + boolean isFastOrLegacyJar = isFastJar || "legacy-jar".equals(packageType); + Predicate filter = isFastOrLegacyJar ? p -> !p.startsWith(quarkusBuildGen) : p -> true; + soft.assertThat(directoryContents(buildDir)) + .describedAs("output: %s", result.getOutput()) + .containsExactlyElementsOf(directoryContents(saveBuildDir, filter)); + + soft.assertThat(taskResults) + .describedAs("output: %s", result.getOutput()) + .containsEntry(":compileJava", TaskOutcome.FROM_CACHE) + .containsEntry(":quarkusGenerateCode", TaskOutcome.FROM_CACHE) + .containsEntry(":quarkusGenerateCodeDev", TaskOutcome.UP_TO_DATE) + .containsEntry(":quarkusAppPartsBuild", isFastOrLegacyJar ? TaskOutcome.FROM_CACHE : TaskOutcome.UP_TO_DATE) + .containsEntry(":quarkusDependenciesBuild", isFastOrLegacyJar ? TaskOutcome.SUCCESS : TaskOutcome.UP_TO_DATE) + .containsEntry(":quarkusBuild", simulateCI || isFastJar ? TaskOutcome.SUCCESS : TaskOutcome.FROM_CACHE); + + // 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); + } + + static Map taskResults(BuildResult result) { + return result.getTasks().stream().collect(Collectors.toMap(BuildTask::getPath, BuildTask::getOutcome)); + } + + static List directoryContents(Path dir) throws IOException { + return directoryContents(dir, p -> true); + } + + static List directoryContents(Path dir, Predicate include) throws IOException { + try (Stream saved = Files.walk(dir)) { + return saved.map(dir::relativize).filter(include).sorted(Comparator.comparing(Path::toString)) + .collect(Collectors.toList()); + } + } +} diff --git a/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/tasks/EffectiveConfigTest.java b/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/tasks/EffectiveConfigTest.java new file mode 100644 index 0000000000000..3f0a223d026af --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/tasks/EffectiveConfigTest.java @@ -0,0 +1,155 @@ +package io.quarkus.gradle.tasks; + +import static io.quarkus.gradle.tasks.EffectiveConfig.*; +import static java.util.Collections.singleton; + +import java.io.File; +import java.net.URL; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.assertj.core.api.SoftAssertions; +import org.assertj.core.api.junit.jupiter.InjectSoftAssertions; +import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension; +import org.eclipse.microprofile.config.spi.ConfigSource; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import io.smallrye.config.SmallRyeConfig; + +@ExtendWith(SoftAssertionsExtension.class) +public class EffectiveConfigTest { + @InjectSoftAssertions + protected SoftAssertions soft; + + @Test + void empty() { + EffectiveConfig effectiveConfig = EffectiveConfig.builder().build(); + + Map expect = new HashMap<>(); + System.getProperties().forEach((k, v) -> expect.put(k.toString(), v.toString())); + expect.putAll(System.getenv()); + + // Cannot do an exact match, because `map` contains both the "raw" environment variables AND the + // "property-key-ish" entries - i.e. environment appears "twice". + soft.assertThat(effectiveConfig.configMap()).containsAllEntriesOf(expect); + } + + @Test + void fromProjectProperties() { + EffectiveConfig effectiveConfig = EffectiveConfig.builder().withProjectProperties(Map.of("quarkus.foo", "bar")).build(); + + soft.assertThat(effectiveConfig.configMap()).containsEntry("quarkus.foo", "bar"); + } + + @Test + void fromForcedProperties() { + EffectiveConfig effectiveConfig = EffectiveConfig.builder().withTaskProperties(Map.of("quarkus.foo", "bar")).build(); + + soft.assertThat(effectiveConfig.configMap()).containsEntry("quarkus.foo", "bar"); + } + + @ParameterizedTest + @ValueSource(strings = { + "app-props-and-yaml", + "app-props-and-yaml-and-yml", + "app-yaml-and-yml", + "single-app-props", + "single-app-yaml", + "single-app-yml" + }) + void appProps(String variant) throws Exception { + URL url = getClass().getClassLoader().getResource("io/quarkus/gradle/tasks/effectiveConfig/" + variant); + List urls = new ArrayList<>(); + List configSources = new ArrayList<>(); + configSourcesForApplicationProperties(singleton(new File(url.toURI())), urls::add, configSources::add); + SmallRyeConfig config = buildConfig("prod", configSources); + + Map expected = new HashMap<>(); + if (variant.contains("-yml")) { + expected.put("quarkus.prop.yml", "yml"); + } + if (variant.contains("-yaml")) { + expected.put("quarkus.prop.yaml", "yaml"); + } + if (variant.contains("-props")) { + expected.put("quarkus.prop.properties", "hello"); + } + + soft.assertThat(urls).hasSize(expected.size() * 2); // "no profile" + "prod" profile + + soft.assertThat(generateFullConfigMap(config)).containsAllEntriesOf(expected); + } + + @Test + void appPropsOverload() throws Exception { + URL url1 = getClass().getClassLoader().getResource("io/quarkus/gradle/tasks/effectiveConfig/overload/1/"); + URL url2 = getClass().getClassLoader().getResource("io/quarkus/gradle/tasks/effectiveConfig/overload/2/"); + Set source = new LinkedHashSet<>(); + source.add(new File(url1.toURI())); + source.add(new File(url2.toURI())); + + EffectiveConfig effectiveConfig = EffectiveConfig.builder().withSourceDirectories(source).build(); + + soft.assertThat(effectiveConfig.applicationPropsSources()).containsExactly( + url1.toURI().resolve("application.properties").toURL(), + url2.toURI().resolve("application.yaml").toURL(), + url1.toURI().resolve("application-prod.properties").toURL(), + url2.toURI().resolve("application-prod.yaml").toURL()); + soft.assertThat(effectiveConfig.configMap()).containsEntry("quarkus.prop.overload", "overloaded"); + } + + @Test + void appPropsOverloadWrongProfile() throws Exception { + URL url1 = getClass().getClassLoader().getResource("io/quarkus/gradle/tasks/effectiveConfig/overload/1/"); + URL url2 = getClass().getClassLoader().getResource("io/quarkus/gradle/tasks/effectiveConfig/overload/2/"); + URL url3 = getClass().getClassLoader().getResource("io/quarkus/gradle/tasks/effectiveConfig/overload/3/"); + Set source = new LinkedHashSet<>(); + source.add(new File(url1.toURI())); + source.add(new File(url2.toURI())); + source.add(new File(url3.toURI())); + + EffectiveConfig effectiveConfig = EffectiveConfig.builder().withSourceDirectories(source).build(); + + soft.assertThat(effectiveConfig.applicationPropsSources()).containsExactly( + url1.toURI().resolve("application.properties").toURL(), + url2.toURI().resolve("application.yaml").toURL(), + url3.toURI().resolve("application.properties").toURL(), + url1.toURI().resolve("application-prod.properties").toURL(), + url2.toURI().resolve("application-prod.yaml").toURL(), + url3.toURI().resolve("application-prod.properties").toURL()); + soft.assertThat(effectiveConfig.configMap()).containsEntry("quarkus.prop.overload", "overloaded"); + } + + @Test + void appPropsOverloadProdProfile() throws Exception { + URL url1 = getClass().getClassLoader().getResource("io/quarkus/gradle/tasks/effectiveConfig/overload/1/"); + URL url2 = getClass().getClassLoader().getResource("io/quarkus/gradle/tasks/effectiveConfig/overload/2/"); + URL url3 = getClass().getClassLoader().getResource("io/quarkus/gradle/tasks/effectiveConfig/overload/3/"); + URL url4 = getClass().getClassLoader().getResource("io/quarkus/gradle/tasks/effectiveConfig/overload/4/"); + Set source = new LinkedHashSet<>(); + source.add(new File(url4.toURI())); + source.add(new File(url1.toURI())); + source.add(new File(url2.toURI())); + source.add(new File(url3.toURI())); + + EffectiveConfig effectiveConfig = EffectiveConfig.builder().withSourceDirectories(source).build(); + + soft.assertThat(effectiveConfig.applicationPropsSources()).containsExactly( + url4.toURI().resolve("application.properties").toURL(), + url1.toURI().resolve("application.properties").toURL(), + url2.toURI().resolve("application.yaml").toURL(), + url3.toURI().resolve("application.properties").toURL(), + url4.toURI().resolve("application-prod.properties").toURL(), + url1.toURI().resolve("application-prod.properties").toURL(), + url2.toURI().resolve("application-prod.yaml").toURL(), + url3.toURI().resolve("application-prod.properties").toURL()); + soft.assertThat(effectiveConfig.configMap()).containsEntry("quarkus.prop.overload", "but-this-one"); + } +} 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 new file mode 100644 index 0000000000000..8618797d16203 --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/caching/main/build.gradle.kts @@ -0,0 +1,28 @@ +plugins { + java + id("io.quarkus") +} + +buildscript { + repositories { + mavenLocal() + mavenCentral() + } +} + +repositories { + mavenLocal() + mavenCentral() +} + +dependencies { + implementation(enforcedPlatform("io.quarkus:quarkus-bom:${project.property("version")}")) + implementation("jakarta.inject:jakarta.inject-api:2.0.1") +} + +quarkus { + quarkusBuildProperties.put("quarkus.foo", "bar") + manifest { + attributes(mapOf("Manifest-Attribute" to "some-value")) + } +} diff --git a/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/caching/main/settings.gradle.kts b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/caching/main/settings.gradle.kts new file mode 100644 index 0000000000000..738e4e47476c9 --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/caching/main/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "gradle-build-caching" diff --git a/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/caching/main/src/main/java/org/acme/Foo.java b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/caching/main/src/main/java/org/acme/Foo.java new file mode 100644 index 0000000000000..8f4e8542598f4 --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/caching/main/src/main/java/org/acme/Foo.java @@ -0,0 +1,4 @@ +package org.acme; + +public class Foo { +} \ No newline at end of file diff --git a/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/app-props-and-yaml-and-yml/application.properties b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/app-props-and-yaml-and-yml/application.properties new file mode 100644 index 0000000000000..f6834e346f911 --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/app-props-and-yaml-and-yml/application.properties @@ -0,0 +1,2 @@ +quarkus.prop.properties=hello +quarkus.package.type=fast-jar diff --git a/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/app-props-and-yaml-and-yml/application.yaml b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/app-props-and-yaml-and-yml/application.yaml new file mode 100644 index 0000000000000..8f45de2459e8a --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/app-props-and-yaml-and-yml/application.yaml @@ -0,0 +1,5 @@ +quarkus: + prop: + yaml: 'yaml' + package: + type: legacy-jar diff --git a/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/app-props-and-yaml-and-yml/application.yml b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/app-props-and-yaml-and-yml/application.yml new file mode 100644 index 0000000000000..9c537d34e6b77 --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/app-props-and-yaml-and-yml/application.yml @@ -0,0 +1,5 @@ +quarkus: + prop: + yml: 'yml' + package: + type: legacy diff --git a/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/app-props-and-yaml/application.properties b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/app-props-and-yaml/application.properties new file mode 100644 index 0000000000000..cfe085a3e1b51 --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/app-props-and-yaml/application.properties @@ -0,0 +1 @@ +quarkus.prop.properties=hello diff --git a/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/app-props-and-yaml/application.yaml b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/app-props-and-yaml/application.yaml new file mode 100644 index 0000000000000..5cb44a30844f9 --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/app-props-and-yaml/application.yaml @@ -0,0 +1,3 @@ +quarkus: + prop: + yaml: 'yaml' diff --git a/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/app-yaml-and-yml/application.yaml b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/app-yaml-and-yml/application.yaml new file mode 100644 index 0000000000000..5cb44a30844f9 --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/app-yaml-and-yml/application.yaml @@ -0,0 +1,3 @@ +quarkus: + prop: + yaml: 'yaml' diff --git a/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/app-yaml-and-yml/application.yml b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/app-yaml-and-yml/application.yml new file mode 100644 index 0000000000000..df2aa80f08d2c --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/app-yaml-and-yml/application.yml @@ -0,0 +1,3 @@ +quarkus: + prop: + yml: 'yml' diff --git a/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/overload/1/application.properties b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/overload/1/application.properties new file mode 100644 index 0000000000000..7c111e47ae34c --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/overload/1/application.properties @@ -0,0 +1 @@ +quarkus.prop.overload=overloaded diff --git a/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/overload/2/application.yaml b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/overload/2/application.yaml new file mode 100644 index 0000000000000..4a0e14d38751d --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/overload/2/application.yaml @@ -0,0 +1,3 @@ +quarkus: + prop: + overload: 'not-this' diff --git a/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/overload/3/application.properties b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/overload/3/application.properties new file mode 100644 index 0000000000000..5c84cb28a2047 --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/overload/3/application.properties @@ -0,0 +1 @@ +%meep.quarkus.prop.overload=not-even-this \ No newline at end of file diff --git a/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/overload/4/application.properties b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/overload/4/application.properties new file mode 100644 index 0000000000000..8729402ad91a1 --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/overload/4/application.properties @@ -0,0 +1 @@ +%prod.quarkus.prop.overload=but-this-one diff --git a/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/single-app-props/application.properties b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/single-app-props/application.properties new file mode 100644 index 0000000000000..cfe085a3e1b51 --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/single-app-props/application.properties @@ -0,0 +1 @@ +quarkus.prop.properties=hello diff --git a/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/single-app-yaml/application.yaml b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/single-app-yaml/application.yaml new file mode 100644 index 0000000000000..5cb44a30844f9 --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/single-app-yaml/application.yaml @@ -0,0 +1,3 @@ +quarkus: + prop: + yaml: 'yaml' diff --git a/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/single-app-yml/application.yml b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/single-app-yml/application.yml new file mode 100644 index 0000000000000..df2aa80f08d2c --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/tasks/effectiveConfig/single-app-yml/application.yml @@ -0,0 +1,3 @@ +quarkus: + prop: + yml: 'yml' diff --git a/devtools/gradle/gradle.properties b/devtools/gradle/gradle.properties index f0de20a934820..fbf9bb9588a10 100644 --- a/devtools/gradle/gradle.properties +++ b/devtools/gradle/gradle.properties @@ -1,2 +1,3 @@ version = 999-SNAPSHOT kotlin_version = 1.8.10 +smallrye_config_version = 2.13.1 diff --git a/docs/src/main/asciidoc/gradle-tooling.adoc b/docs/src/main/asciidoc/gradle-tooling.adoc index a41da1d4d991f..2a946bd4e3256 100644 --- a/docs/src/main/asciidoc/gradle-tooling.adoc +++ b/docs/src/main/asciidoc/gradle-tooling.adoc @@ -584,3 +584,173 @@ tasks.withType().configureEach { } ---- **** + + +== Configuring Quarkus builds + +There are multiple configuration sources that influence Quarkus builds, mentioned in the order of their priority. +The Quarkus build uses the `prod` configuration profile: + +1. System properties (for example `./gradlew -Dquarkus.package.type=fast-jar ...`) +2. System environment (for example `QUARKUS_PACAKGE_TYPE=fast-jar ./gradlew ...`) +3. Configuration via the `quarkus` extensions's `quarkusBuildProperties` For example: + + quarkus { + properties { + set("package.type", "uber-jar") + } + } + +4. Configuration via Gradle project properties (for example `./gradlew -Pquarkus.package.type=fast-jar`) +5. Configuration from a project's `application.properties`, `application.yaml` and `application.yml` files, as well + as a project's `application-prod.properties`, `application-prod.yaml` and `application-prod.yml` files + +[WARNING] +The above priorities have changed in Quarkus plugin starting with 3.0. Older versions of the Quarkus Gradle plugin +preferred `application.properties` over settings in the Gradle build. + +[NOTE] +The Quarkus Gradle plugin uses the "standard" Quarkus mechanisms to load and parse configurations. Support for +`application.(yaml|yml)` has been added in Quarkus 3.0 in addition to `application.properties`. Also new in 3.0 +is that all mechanisms available via SmallRye Config, are implicitly also now available for the Quarkus Gradle +plugin. + + +[TIP] +Use the `quarkusShowEffectiveConfig` task to show the effective configuration options used for a Quarkus build. If you +specify the `--save-config-properties` command line option, the configuration properties are also store in the file +`build/.quarkus-build.properties`. + + +== Build workers + +Quarkus application builds are ran in isolated processes using Gradle's worker API. This includes the Quarkus +application build and Quarkus code generation. This is necessary to properly pass the configuration from the +`quarkus` extension and Gradle project properties to Quarkus' code generator / application builder. + +The JVM settings of the processes performing the code generation and/or Quarkus build can be configured as follows. +See link:https://docs.gradle.org/current/javadoc/org/gradle/process/JavaForkOptions.html[JavaForkOptions] +for details. + +[role="primary asciidoc-tabs-sync-cli"] +.build.gradle +**** +[source, groovy] +---- +plugins { + id 'java' + id 'io.quarkus' +} + +quarkus { + buildForkOptions { + maxHeapSize = '2g' + } + codeGenForkOptions { + maxHeapSize = '128m' + } +} +---- +**** + +[role="secondary asciidoc-tabs-sync-maven"] +.build.gradle.kts +**** +[source, kotlin] +---- +plugins { + java + id("io.quarkus") +} + +quarkus { + buildForkOptions { + maxHeapSize = '2g' + } + codeGenForkOptions { + maxHeapSize = '128m' + } +} +---- +**** + + +== Cached build artifacts + +link:https://docs.gradle.org/current/userguide/build_cache.html[Gradle's build cache] is a very efficient mechanism to +improve the overall build runtime, by reusing previously generated outputs (see +link:https://docs.gradle.org/current/userguide/incremental_build.html[Incremental build] for technical details). + +The Quarkus plugin leverages the Gradle mechanisms of up-to-date checks and the build cache. The build cache can be +local or local _plus_ a remote cache server or, if configured in CI environments, remote in the sense of retrieving +and storing the whole cache as an artifact, for example using +link:https://github.com/gradle/gradle-build-action[Gradle's GitHub action] or directly/manually GitHub's +link:https://github.com/actions/cache[GitHub's cache action]. + +The Quarkus Gradle plugin cares about _what_ is cached _in which environment_ (CI or local development). Big artifacts +like uber-jars and native binaries are not cached in CI, but are cached in local development environments. + +TIP: The Quarkus Gradle plugin detects a _CI environment_, if the `CI` environment variable is present. + +How the various Quarkus package types are cached in non-CI and CI environments is described in the following table. +Note that even if a task's output is not _cached_, the _up-to-date_ checks still apply. + +NOTE: The Quarkus application build is split across three tasks. The `quarkusBuild` taskl is responsible to _provide_ +a built Quarkus application. The tasks `quarkusDependenciesBuild` and `quarkusAppPartsBuild` are considered internal +tasks (may change at any time w/o prior notice). See below for details. + +[cols="2,5,1,1"] +|=== +|Quarkus package type|Notes|Caching (non-CI)|Caching in CI +|`fast-jar`, `jar` +|Dependency jars are stored unmodified as individual files in the `quarkus-app/lib/` directory. +All other files in the `quarkus-app/` directory are generated. + +The `quarkusAppPartsBuild` task builds `fast-jar` package-type applications and allows caching of the _generated_ +pieces, which is everything except the dependencies in the `quarkus-app/lib/` directory. The +`quarkusDependenciesBuild` task is used to collect the dependencies via Gradle mechanisms. The `quarkusBuild` task +then assembles the outputs of the `quarkusAppPartsBuild` and `quarkusDependenciesBuild` tasks. + +Note: `fast-jar` (or `-jar`) is the default if no package type has been explicitly configured. +|✅ +|✅ + +|`mutable-jar`, `uber-jar`, `native`, `native-sources` +|The `quarkusBuild` task is responsible for building the Quarkus application. + +The `quarkusAppPartsBuild` and `quarkusDependenciesBuild` tasks do nothing for `uber-jar`s. +|✅ +|❌ + +|`legacy-jar`, `legacy` +|`legacy-jar` builds work similar to `fast-jar` builds, except that the directory structure is different and the +`modified-*.jar` files are considered as generated. +|✅ +|✅ + +|=== + +[NOTE] +In a local development environment, the cost (think: time) of storing (and retrieving) even bigger cache artifacts is +lower than the cost of re-building a Quarkus application. This means, that The Quarkus Gradle plugin allows caching +even potentially big artifacts like uber-jars or native binaries in non-CI environments. In CI environments, which run +builds against varying states of a code base (think: running CI against every commit on a main branch), adding each +built (and big) artifact to the build cache would let the build cache become unnecessarily big, which becomes a +problem for example in GitHub, where the total amount of cached artifacts is limited to 10 GB. + + +[NOTE] +==== +Background information: There are two related mechanisms in Gradle at play to improve build performance: + +* Up-to-date checks allow a task's execution to be skipped, if the _inputs_ and _outputs_ of the tasks did not change. +For example, consider a Java compile tasks: if the library dependencies and the source files (the _inputs_) did not +change and the compiled class files (the _outputs_) are still available, compilation can be skipped. +* The build cache can store the outputs of (cacheable) tasks in the local build cache. The _output_ of a task can be +restored from the cache. + +The benefits of up-to-date checks and the interaction of the build cache come with the cost of modeling the _inputs_ +and _outputs_. Inputs are not only files or directories, but also the Java version used during the build, the operating +system, the working directory, configuration options, and so on. So everything that influences the output of a task +action must be declared as an input of the task. +==== diff --git a/integration-tests/gradle/src/main/resources/build-configuration/with-application-properties/build.gradle b/integration-tests/gradle/src/main/resources/build-configuration/with-application-properties/build.gradle index 81bcea39ef58e..b17ac5d54a05f 100644 --- a/integration-tests/gradle/src/main/resources/build-configuration/with-application-properties/build.gradle +++ b/integration-tests/gradle/src/main/resources/build-configuration/with-application-properties/build.gradle @@ -25,8 +25,6 @@ version '1.0.0-SNAPSHOT' quarkus { properties { - set("package.type", "fast-jar") - set("package.output-directory", "build-gradle-output-dir") } } diff --git a/integration-tests/gradle/src/main/resources/build-configuration/with-build-configuration/src/main/resources/application.properties b/integration-tests/gradle/src/main/resources/build-configuration/with-build-configuration/src/main/resources/application.properties index b18c72feda9df..8a17d7feeb16b 100644 --- a/integration-tests/gradle/src/main/resources/build-configuration/with-build-configuration/src/main/resources/application.properties +++ b/integration-tests/gradle/src/main/resources/build-configuration/with-build-configuration/src/main/resources/application.properties @@ -1,3 +1,5 @@ # Configuration file # key = value my-app-name=${quarkus.application.name} +quarkus.package.type=fast-jar +quarkus.package.output-directory=build-gradle-output-dir diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/BuildConfigurationTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/BuildConfigurationTest.java index 5b2a14e9669ea..ebee60b97985e 100644 --- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/BuildConfigurationTest.java +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/BuildConfigurationTest.java @@ -36,9 +36,11 @@ final class PrjPaths { final Path fastJar; final Path libDeploymentDir; final String project; + final String outputDir; PrjPaths(File rootDir, String project, String outputDir) { this.project = project; + this.outputDir = outputDir; this.projectDir = rootDir.toPath().resolve(project); this.buildDirPath = projectDir.resolve("build"); this.quarkusAppPath = buildDirPath.resolve(outputDir); @@ -49,12 +51,17 @@ final class PrjPaths { } void verify(String packageType) { - soft.assertThat(quarkusAppPath).describedAs("sub project '%s', package type '%s'", project, packageType) - .isDirectory(); soft.assertThat(buildDirPath).describedAs("sub project '%s', package type '%s'", project, packageType) .isDirectory(); switch (packageType) { case "uber-jar": + if (!DEFAULT_OUTPUT_DIR.equals(outputDir)) { + soft.assertThat(quarkusAppPath).describedAs("sub project '%s', package type '%s'", project, packageType) + .isDirectory(); + } else { + soft.assertThat(quarkusAppPath).describedAs("sub project '%s', package type '%s'", project, packageType) + .doesNotExist(); + } soft.assertThat(uberJar).describedAs("sub project '%s', package type '%s'", project, packageType) .isNotEmptyFile(); soft.assertThat(fastJar).describedAs("sub project '%s', package type '%s'", project, packageType) @@ -63,6 +70,8 @@ void verify(String packageType) { .doesNotExist(); break; case "fast-jar": + soft.assertThat(quarkusAppPath).describedAs("sub project '%s', package type '%s'", project, packageType) + .isDirectory(); soft.assertThat(uberJar).describedAs("sub project '%s', package type '%s'", project, packageType) .doesNotExist(); soft.assertThat(fastJar).describedAs("sub project '%s', package type '%s'", project, packageType) @@ -71,6 +80,8 @@ void verify(String packageType) { .doesNotExist(); break; case "mutable-jar": + soft.assertThat(quarkusAppPath).describedAs("sub project '%s', package type '%s'", project, packageType) + .isDirectory(); soft.assertThat(uberJar).describedAs("sub project '%s', package type '%s'", project, packageType) .doesNotExist(); soft.assertThat(fastJar).describedAs("sub project '%s', package type '%s'", project, packageType) diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/nativeimage/NativeIntegrationTestIT.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/nativeimage/NativeIntegrationTestIT.java index b22b4eab2df07..58c4b50c26057 100644 --- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/nativeimage/NativeIntegrationTestIT.java +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/nativeimage/NativeIntegrationTestIT.java @@ -16,7 +16,7 @@ public void nativeTestShouldRunIntegrationTest() throws Exception { BuildResult testResult = runGradleWrapper(projectDir, "clean", "testNative"); - assertThat(testResult.getTasks().get(":testNative")).isEqualTo(BuildResult.SUCCESS_OUTCOME); + assertThat(testResult.getTasks().get(":testNative")).isIn(BuildResult.SUCCESS_OUTCOME, BuildResult.FROM_CACHE); } @Test @@ -25,7 +25,7 @@ public void runNativeTestsWithOutputName() throws Exception { final BuildResult testResult = runGradleWrapper(projectDir, "clean", "testNative", "-Dquarkus.package.output-name=test"); - assertThat(testResult.getTasks().get(":testNative")).isEqualTo(BuildResult.SUCCESS_OUTCOME); + assertThat(testResult.getTasks().get(":testNative")).isIn(BuildResult.SUCCESS_OUTCOME, BuildResult.FROM_CACHE); } @Test @@ -34,7 +34,7 @@ public void runNativeTestsWithoutRunnerSuffix() throws Exception { final BuildResult testResult = runGradleWrapper(projectDir, "clean", "testNative", "-Dquarkus.package.add-runner-suffix=false"); - assertThat(testResult.getTasks().get(":testNative")).isEqualTo(BuildResult.SUCCESS_OUTCOME); + assertThat(testResult.getTasks().get(":testNative")).isIn(BuildResult.SUCCESS_OUTCOME, BuildResult.FROM_CACHE); } }