From 856031dbe33c51c93b80b7f63804182fd4615524 Mon Sep 17 00:00:00 2001 From: Robert Stupp Date: Sat, 4 Feb 2023 23:04:35 +0100 Subject: [PATCH] WIP: Make `QuarkusBuild` not pollute Gradle's build cache 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. This change updates the build logic to fix this behavior by not adding dependencies and large build artifacts, like uber-jar and native binary, to the Gradle build cache. The `QuarkusBuild` task has been split into three tasks: 1. a new `QuarkusBuildDependencies` task that only collects the contents for `build/quarkus-app/lib` 2. a new `QuarkusBuildApp` task that builds the Quarkus app, which was previously done by the `QuarkusBuild` task 3. the `QuarkusBuild` task now combines the outputs of the above two tasks `QuarkusBuildDependencies` (named 'quarkusDependenciesBuild`) is not cacheable, because it only collects dependencies, which come either from a repository (and are already available locally elsewhere) or are built by other Gradle tasks. This task is only executed if the configured Quarkus package type requires the "quarkus-app" directory (`fast-jar` + `native`). It's "build working directory" is `build/quarkus-build/dep`. `QuarkusBuildApp` (named `quarkusAppBuild`) builds the Quarkus application. The contents of the "quarkus-app" directory _excluding_ the `lib/` directory are cacheable, which is the default for CI environments. Non-CI environments still cache all outputs, even uber-jars and native binaries to retain the existing behavior for developers and keep build turn-around times low. CI environments can opt-in to add even huge artifacts to Gradle's build cache by explicitly setting the `cacheUberAndNativeRunners` property in the Quarkus extension to `true`. It's "build working directory" is `build/quarkus-build/app`. Since `QuarkusBuild` only combines the outputs of the above two tasks, the same "CI vs local" caching behavior as for the `QuarkusBuildApp` task applies. To make "up to date" checks (kind of) more reliable, all outputs are removed first. This means, that for example an existing uber-jar in `build/` will disappear, when the build's package type is "fast-jar". This behavior can be disabled by setting the `cleanupBuildOutput` property on the Quarkus extension to `false`. The task names `quarkusDependenciesBuild` and `quarkusAppBuild` are intentionally "that way around". Letting the names of these tasks begin with `quarkusBuild...` could confuse users, who use abbreviated task names on the command line (for example `./gradlew qB` is automagically expanded to `./gradlew quarkusBuild`, which would become ambiguous with `quarkusBuildDependencies` and `quarkusBuildApp`). Relates to: #30852 --- .../java/io/quarkus/gradle/QuarkusPlugin.java | 30 ++- .../extension/QuarkusPluginExtension.java | 109 +++++++++- .../io/quarkus/gradle/tasks/QuarkusBuild.java | 200 ++++------------- .../quarkus/gradle/tasks/QuarkusBuildApp.java | 205 ++++++++++++++++++ .../tasks/QuarkusBuildDependencies.java | 130 +++++++++++ .../gradle/tasks/QuarkusBuildInternal.java | 122 +++++++++++ .../gradle/tasks/QuarkusBuildTask.java | 64 ++++++ .../gradle/tasks/QuarkusGenerateCode.java | 5 + .../io/quarkus/gradle/tasks/QuarkusTask.java | 17 +- 9 files changed, 712 insertions(+), 170 deletions(-) create mode 100644 devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildApp.java create mode 100644 devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildDependencies.java create mode 100644 devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildInternal.java create mode 100644 devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildTask.java 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 aa33e61067787..4053608a7d7af 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,9 @@ import io.quarkus.gradle.tasks.ImagePush; import io.quarkus.gradle.tasks.QuarkusAddExtension; import io.quarkus.gradle.tasks.QuarkusBuild; +import io.quarkus.gradle.tasks.QuarkusBuildApp; import io.quarkus.gradle.tasks.QuarkusBuildConfiguration; +import io.quarkus.gradle.tasks.QuarkusBuildDependencies; import io.quarkus.gradle.tasks.QuarkusDev; import io.quarkus.gradle.tasks.QuarkusGenerateCode; import io.quarkus.gradle.tasks.QuarkusGoOffline; @@ -56,6 +58,11 @@ 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 OUTPUT_DIRECTORY = "quarkus.package.output-directory"; + public static final String CONTAINER_BUILD = "quarkus.native.container-build"; + public static final String BUILDER_IMAGE = "quarkus.native.builder-image"; + 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 EXTENSION_NAME = "quarkus"; public static final String LIST_EXTENSIONS_TASK_NAME = "listExtensions"; @@ -66,6 +73,8 @@ 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_TASK_NAME = "quarkusAppBuild"; 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"; @@ -150,9 +159,28 @@ private void registerTasks(Project project, QuarkusPluginExtension quarkusExt) { QuarkusBuildConfiguration buildConfig = new QuarkusBuildConfiguration(project); + TaskProvider quarkusBuildLibs = tasks.register(QUARKUS_BUILD_DEP_TASK_NAME, + QuarkusBuildDependencies.class); + quarkusBuildLibs.configure(task -> { + task.getOutputs().doNotCacheIf("Dependencies are never cached", t -> true); + }); + + TaskProvider quarkusBuildApp = tasks.register(QUARKUS_BUILD_APP_TASK_NAME, + QuarkusBuildApp.class); + quarkusBuildApp.configure(task -> { + task.getOutputs().doNotCacheIf( + "Not adding uber-jars and native binaries to Gradle build cache by default. " + + "To allow caching of uber-jars and native binaries set 'cacheUberAndNativeRunners' to 'true' in the 'quarkus' extension.", + t -> !quarkusExt.getBuildInternal().get().isFastJarLike() + && !quarkusExt.getCacheUberAndNativeRunners().get()); + }); + TaskProvider quarkusBuild = tasks.register(QUARKUS_BUILD_TASK_NAME, QuarkusBuild.class, build -> { - build.dependsOn(quarkusGenerateCode); + build.dependsOn(quarkusGenerateCode, quarkusBuildLibs, quarkusBuildApp); build.getForcedProperties().set(buildConfig.getForcedProperties()); + build.getOutputs().doNotCacheIf( + "Only collects outputs of " + QUARKUS_BUILD_APP_TASK_NAME + " and " + QUARKUS_BUILD_DEP_TASK_NAME, + t -> !quarkusExt.getCacheUberAndNativeRunners().get()); }); TaskProvider imageBuild = tasks.register(IMAGE_BUILD_TASK_NAME, ImageBuild.class, buildConfig); 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..e0bb814b8ae61 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 @@ -1,8 +1,12 @@ package io.quarkus.gradle.extension; import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.EnumMap; +import java.util.HashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Objects; @@ -30,6 +34,7 @@ import io.quarkus.bootstrap.model.ApplicationModel; import io.quarkus.bootstrap.resolver.AppModelResolver; import io.quarkus.gradle.AppModelGradleResolver; +import io.quarkus.gradle.tasks.QuarkusBuildInternal; import io.quarkus.gradle.tasks.QuarkusGradleUtils; import io.quarkus.gradle.tooling.ToolingUtils; import io.quarkus.runtime.LaunchMode; @@ -39,17 +44,37 @@ public class QuarkusPluginExtension { private final Property finalName; + private final MapProperty applicationProperties; private final MapProperty quarkusBuildProperties; + private final MapProperty buildSystemProperties; private final SourceSetExtension sourceSetExtension; + private final Property cacheUberAndNativeRunners; + private final Property cleanupBuildOutput; + private final Property buildInternal; + + private final Map applicationModels = new EnumMap<>(LaunchMode.class); + public QuarkusPluginExtension(Project project) { this.project = 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.cacheUberAndNativeRunners = project.getObjects().property(Boolean.class) + .convention(!System.getenv().containsKey("CI")); + this.sourceSetExtension = new SourceSetExtension(); + this.applicationProperties = project.getObjects().mapProperty(String.class, String.class) + .convention(project.provider(() -> loadApplicationProperties(project))); this.quarkusBuildProperties = project.getObjects().mapProperty(String.class, String.class); + this.buildSystemProperties = project.getObjects().mapProperty(String.class, String.class) + .convention(project.provider(() -> collectBuildSystemProperties())); + + this.buildInternal = project.getObjects().property(QuarkusBuildInternal.class) + .convention(project.provider(() -> new QuarkusBuildInternal(project, this))); } public void beforeTest(Test task) { @@ -124,6 +149,30 @@ 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, uber-jar and native, are cached. Defaults to 'false' if the 'CI' environment + * variable is set, otherwise defaults to 'true'. + */ + public Property getCacheUberAndNativeRunners() { + return cacheUberAndNativeRunners; + } + + public void setCacheUberAndNativeRunners(boolean cacheUberAndNativeRunners) { + this.cacheUberAndNativeRunners.set(cacheUberAndNativeRunners); + } + public String finalName() { return getFinalName().get(); } @@ -164,7 +213,8 @@ public ApplicationModel getApplicationModel() { } public ApplicationModel getApplicationModel(LaunchMode mode) { - return ToolingUtils.create(project, mode); + // Prevent duplicate computation of ApplicationModel(s), same model's needed by multiple tasks. + return applicationModels.computeIfAbsent(mode, m -> ToolingUtils.create(project, m)); } /** @@ -215,10 +265,22 @@ public Path appJarOrClasses() { return classesDir; } + public MapProperty getApplicationProperties() { + return applicationProperties; + } + public MapProperty getQuarkusBuildProperties() { return quarkusBuildProperties; } + public MapProperty getBuildSystemProperties() { + return buildSystemProperties; + } + + public Property getBuildInternal() { + return buildInternal; + } + public void set(String name, @Nullable String value) { quarkusBuildProperties.put(String.format("quarkus.%s", name), value); } @@ -227,4 +289,49 @@ public void set(String name, Property value) { quarkusBuildProperties.put(String.format("quarkus.%s", name), value); } + private Map collectBuildSystemProperties() { + final Map properties = project.getProperties(); + final Map realProperties = new HashMap<>(); + 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.put(key, (String) value); + } + } + Map quarkusBuildProperties = getQuarkusBuildProperties().get(); + if (!quarkusBuildProperties.isEmpty()) { + quarkusBuildProperties.entrySet().stream().filter(entry -> entry.getKey().startsWith("quarkus.")) + .forEach(entry -> { + realProperties.put(entry.getKey(), entry.getValue()); + }); + } + return realProperties; + } + + private static Map loadApplicationProperties(Project project) { + SourceSet mainSourceSet = QuarkusGradleUtils.getSourceSet(project, SourceSet.MAIN_SOURCE_SET_NAME); + FileCollection configFiles = mainSourceSet.getResources() + .filter(file -> "application.properties".equalsIgnoreCase(file.getName())); + Properties properties = new Properties(); + configFiles.forEach(file -> { + FileInputStream appPropsIS = null; + try { + appPropsIS = new FileInputStream(file.getAbsoluteFile()); + properties.load(appPropsIS); + appPropsIS.close(); + } catch (IOException e) { + if (appPropsIS != null) { + try { + appPropsIS.close(); + } catch (IOException ex) { + // Ignore exception closing. + } + } + } + }); + Map result = new HashMap<>(); + properties.forEach((k, v) -> result.put(k.toString(), v.toString())); + return result; + } } 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..33c97178c7938 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,11 +1,7 @@ package io.quarkus.gradle.tasks; import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.Serializable; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -14,9 +10,7 @@ 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.MapProperty; import org.gradle.api.tasks.CacheableTask; import org.gradle.api.tasks.Classpath; @@ -25,27 +19,14 @@ 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.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.gradle.dsl.Manifest; -import io.quarkus.maven.dependency.GACTV; import io.quarkus.runtime.util.StringUtil; @CacheableTask -public abstract class QuarkusBuild extends QuarkusTask { - - 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"; +public abstract class QuarkusBuild extends QuarkusBuildTask { private List ignoredEntries = new ArrayList<>(); private Manifest manifest = new Manifest(); @@ -70,9 +51,16 @@ public QuarkusBuild nativeArgs(Action> action) { return this; } + @Override + File buildDir() { + return getProject().getBuildDir(); + } + @Optional @Input - public abstract MapProperty getForcedProperties(); + public MapProperty getForcedProperties() { + return buildInternal().forcedProperties; + } @Optional @Input @@ -88,33 +76,17 @@ public void setIgnoredEntries(List 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()); + return buildInternal().dependencyClasspath; } @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; + return buildInternal().buildSystemProperties.get(); } @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; + return buildInternal().buildEnvProperties.get(); } @Internal @@ -122,6 +94,11 @@ public Manifest getManifest() { return this.manifest; } + @Input + public Map getCachingRelevantInput() { + return buildInternal().gradleCachingRelevantInput(); + } + public QuarkusBuild manifest(Action action) { action.execute(this.getManifest()); return this; @@ -129,100 +106,55 @@ public QuarkusBuild manifest(Action action) { @OutputFile public File getRunnerJar() { - return new File(getProject().getBuildDir(), String.format("%s.jar", extension().buildNativeRunnerName(Map.of()))); + return runnerJar(); } @OutputFile public File getNativeRunner() { - return new File(getProject().getBuildDir(), extension().buildNativeRunnerName(Map.of())); + return nativeRunner(); + } + + @OutputFile + public File getArtifactProperties() { + return new File(buildDir(), QUARKUS_ARTIFACT_PROPERTIES); + } + + @Internal + public String getOutputDirectory() { + return buildInternal().getOutputDirectory(); } @OutputDirectory 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); - } + File buildDir = buildDir(); + File fastJar = getFastJar(); - 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); + if (extension().getCleanupBuildOutput().get()) { + getFileSystemOperations().delete(delete -> delete.delete(getRunnerJar(), getNativeRunner(), fastJar)); } - 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); - } - } - - private void exportCustomManifestProperties(Properties buildSystemProperties) { - if (this.manifest == null) { - return; - } + // build/quarkus-build/app + File appBuildBaseDir = buildAppBaseDir(); + // build/quarkus-build/app/quarkus-app + File appBuildDir = new File(appBuildBaseDir, getOutputDirectory()); + // build/quarkus-build/dep + File depBuildDir = super.buildDepDir(); - for (Map.Entry attribute : manifest.getAttributes().entrySet()) { - buildSystemProperties.put(toManifestAttributeKey(attribute.getKey()), - attribute.getValue()); - } - - for (Map.Entry section : manifest.getSections().entrySet()) { - for (Map.Entry attribute : section.getValue().entrySet()) { - buildSystemProperties - .put(toManifestSectionAttributeKey(section.getKey(), attribute.getKey()), attribute.getValue()); - } - } - } + getLogger().info("Finalizing Quarkus build"); - 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); - } + getFileSystemOperations().sync(sync -> { + sync.into(fastJar); + sync.from(appBuildDir, depBuildDir); + }); - 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); + getFileSystemOperations().copy( + copy -> copy.into(buildDir).from(appBuildBaseDir).include(QUARKUS_ARTIFACT_PROPERTIES, nativeRunnerFileName(), + runnerFileName())); } private String expandConfigurationKey(String shortKey) { @@ -232,40 +164,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/QuarkusBuildApp.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildApp.java new file mode 100644 index 0000000000000..ebf79a8d35632 --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildApp.java @@ -0,0 +1,205 @@ +package io.quarkus.gradle.tasks; + +import java.io.File; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import javax.inject.Inject; + +import org.gradle.api.GradleException; +import org.gradle.api.file.FileCollection; +import org.gradle.api.java.archives.Attributes; +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.Optional; +import org.gradle.api.tasks.OutputDirectory; +import org.gradle.api.tasks.OutputFile; +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.gradle.dsl.Manifest; +import io.quarkus.maven.dependency.GACTV; + +@CacheableTask +public abstract class QuarkusBuildApp extends QuarkusBuildTask { + + private List ignoredEntries = new ArrayList<>(); + private Manifest manifest = new Manifest(); + + @Inject + public QuarkusBuildApp() { + super("Quarkus builds a runner jar based on the build jar"); + } + + public QuarkusBuildApp(String description) { + super(description); + } + + @Optional + @Input + public MapProperty getForcedProperties() { + return buildInternal().forcedProperties; + } + + @Optional + @Input + public List getIgnoredEntries() { + return ignoredEntries; + } + + @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() { + return buildInternal().dependencyClasspath; + } + + @Input + public MapProperty getQuarkusBuildSystemProperties() { + return buildInternal().buildSystemProperties; + } + + @Input + public MapProperty getQuarkusBuildEnvProperties() { + return buildInternal().buildEnvProperties; + } + + @Input + public Map getCachingRelevantInput() { + return buildInternal().gradleCachingRelevantInput(); + } + + @OutputFile + public File getRunnerJar() { + return runnerJar(); + } + + @OutputFile + public File getNativeRunner() { + return nativeRunner(); + } + + /** + * Contains the Quarkus app, but without the {@code lib/} folder. Contents of this directory are not too big and + * should be cacheable. + */ + @OutputDirectory + public File getBuildDir() { + return buildDir(); + } + + @Override + File buildDir() { + return buildAppBaseDir(); + } + + @TaskAction + public void buildQuarkus() { + ApplicationModel appModel; + Map forcedProperties = getForcedProperties().getOrElse(Collections.emptyMap()); + File buildDir = getBuildDir(); + + // 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(buildDir)); + + 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); + } + + 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); + } + + exportCustomManifestProperties(effectiveProperties); + + Path appTargetDir = buildDir().toPath(); + + getLogger().info("Building in target directory {}", appTargetDir); + + try (CuratedApplication appCreationContext = QuarkusBootstrap.builder() + .setBaseClassLoader(getClass().getClassLoader()) + .setExistingModel(appModel) + .setTargetDirectory(appTargetDir) + .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(); + + if (buildInternal().isFastJarLike()) { + getFileSystemOperations().delete( + delete -> delete.delete(appTargetDir.resolve(buildInternal().getOutputDirectory()).resolve("lib"))); + } + } catch (BootstrapException e) { + throw new GradleException("Failed to build a runnable JAR", e); + } + } + + private void exportCustomManifestProperties(Properties buildSystemProperties) { + if (this.manifest == null) { + return; + } + + for (Map.Entry attribute : manifest.getAttributes().entrySet()) { + buildSystemProperties.put(toManifestAttributeKey(attribute.getKey()), + attribute.getValue()); + } + + for (Map.Entry section : manifest.getSections().entrySet()) { + for (Map.Entry attribute : section.getValue().entrySet()) { + buildSystemProperties + .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/QuarkusBuildDependencies.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildDependencies.java new file mode 100644 index 0000000000000..3b0c2c826de25 --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildDependencies.java @@ -0,0 +1,130 @@ +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.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.inject.Inject; + +import org.gradle.api.GradleException; +import org.gradle.api.file.FileCollection; +import org.gradle.api.tasks.Classpath; +import org.gradle.api.tasks.OutputDirectory; +import org.gradle.api.tasks.TaskAction; + +import io.quarkus.bootstrap.model.ApplicationModel; +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, prefer the 'quarkusBuild' task"); + } + + @Classpath + public FileCollection getClasspath() { + return buildInternal().dependencyClasspath; + } + + @Override + File buildDir() { + return buildDepDir(); + } + + @OutputDirectory + public File getBuildDir() { + return buildDir(); + } + + @TaskAction + public void run() { + Path quarkusPackageOutputDir = getBuildDir().toPath(); + + getFileSystemOperations().delete(delete -> delete.delete(quarkusPackageOutputDir)); + + if (!buildInternal().isFastJarLike()) { + getLogger().info("Dependencies not needed for packaging type {}", buildInternal().getPackageType()); + return; + } + + getLogger().info("Placing Quarkus application dependencies in {}", quarkusPackageOutputDir); + + Path libBoot = quarkusPackageOutputDir.resolve("lib/boot"); + Path libMain = quarkusPackageOutputDir.resolve("lib/main"); + try { + Files.createDirectories(libBoot); + Files.createDirectories(libMain); + } catch (IOException e) { + throw new GradleException(String.format("Failed to create directories in %s", quarkusPackageOutputDir), e); + } + + ApplicationModel appModel = extension().getApplicationModel(); + + // see https://quarkus.io/guides/class-loading-reference#configuring-class-loading + String removedArtifactsProp = buildInternal() + .getPropValueWithPrecedence(QuarkusPlugin.CLASS_LOADING_PARENT_FIRST_ARTIFACTS, ""); + Optional> optionalDependencies = Optional.ofNullable( + buildInternal().getPropValueWithPrecedence(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/QuarkusBuildInternal.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildInternal.java new file mode 100644 index 0000000000000..7ceb9b00c2e34 --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildInternal.java @@ -0,0 +1,122 @@ +package io.quarkus.gradle.tasks; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +import org.gradle.api.Project; +import org.gradle.api.file.FileCollection; +import org.gradle.api.provider.MapProperty; +import org.gradle.api.tasks.SourceSet; + +import io.quarkus.gradle.QuarkusPlugin; +import io.quarkus.gradle.extension.QuarkusPluginExtension; + +public class QuarkusBuildInternal { + + final MapProperty buildSystemProperties; + final MapProperty buildEnvProperties; + final MapProperty forcedProperties; + final FileCollection dependencyClasspath; + private final QuarkusPluginExtension extension; + + public QuarkusBuildInternal(Project project, QuarkusPluginExtension extension) { + this.buildSystemProperties = project.getObjects().mapProperty(Object.class, Object.class) + .convention(project.provider(() -> collectBuildSystemProperties())); + this.buildEnvProperties = project.getObjects().mapProperty(String.class, String.class) + .convention(project.provider(() -> collectBuildEnvProperties())); + this.forcedProperties = project.getObjects().mapProperty(String.class, String.class); + + this.extension = extension; + + SourceSet mainSourceSet = QuarkusGradleUtils.getSourceSet(project, SourceSet.MAIN_SOURCE_SET_NAME); + this.dependencyClasspath = collectDependencyClasspath(mainSourceSet); + } + + public boolean isFastJarLike() { + switch (getPackageType()) { + case "fast-jar": + case "native": + return true; + default: + return false; + } + } + + String getPackageType() { + Map buildSystemProperties = extension.getBuildSystemProperties().get(); + return buildSystemProperties.getOrDefault(QuarkusPlugin.QUARKUS_PACKAGE_TYPE, "fast-jar"); + } + + Map gradleCachingRelevantInput() { + Map cachingRelevant = new HashMap<>(); + + cachingRelevant.put("final-name", extension.finalName()); + String packageType = getPackageType(); + System.err.println(); + System.err.println("PACKAGE_TYPE = " + packageType); + System.err.println(); + cachingRelevant.put(QuarkusPlugin.QUARKUS_PACKAGE_TYPE, packageType); + cachingRelevant.put(QuarkusPlugin.OUTPUT_DIRECTORY, getOutputDirectory()); + + Map buildSystemProperties = extension.getBuildSystemProperties().get(); + cachingRelevant.put(QuarkusPlugin.CONTAINER_BUILD, + buildSystemProperties.getOrDefault(QuarkusPlugin.CONTAINER_BUILD, "false")); + cachingRelevant.put(QuarkusPlugin.BUILDER_IMAGE, + buildSystemProperties.getOrDefault(QuarkusPlugin.BUILDER_IMAGE, "")); + + cachingRelevant.put(QuarkusPlugin.CLASS_LOADING_PARENT_FIRST_ARTIFACTS, + getPropValueWithPrecedence(QuarkusPlugin.CLASS_LOADING_PARENT_FIRST_ARTIFACTS, "")); + cachingRelevant.put(QuarkusPlugin.CLASS_LOADING_REMOVED_ARTIFACTS, + getPropValueWithPrecedence(QuarkusPlugin.CLASS_LOADING_REMOVED_ARTIFACTS, "")); + + return cachingRelevant; + } + + String getOutputDirectory() { + return getPropValueWithPrecedence(QuarkusPlugin.OUTPUT_DIRECTORY, "quarkus-app"); + } + + String getPropValueWithPrecedence(String propName, String defaultValue) { + Object v = buildSystemProperties.get().get(propName); + if (v != null) { + return v.toString(); + } + String s = extension.getApplicationProperties().get().get(propName); + if (s != null) { + return s; + } + s = buildEnvProperties.get().get(propName); + if (s != null) { + return s; + } + return defaultValue; + } + + private static FileCollection collectDependencyClasspath(SourceSet mainSourceSet) { + return mainSourceSet.getCompileClasspath().plus(mainSourceSet.getRuntimeClasspath()) + .plus(mainSourceSet.getAnnotationProcessorPath()) + .plus(mainSourceSet.getResources()); + } + + private static Map collectBuildSystemProperties() { + 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; + } + + private static Map collectBuildEnvProperties() { + 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; + } +} 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..d1aac6386c48b --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildTask.java @@ -0,0 +1,64 @@ +package io.quarkus.gradle.tasks; + +import java.io.File; +import java.util.Map; + +import javax.inject.Inject; + +import org.gradle.api.file.FileSystemOperations; + +abstract class QuarkusBuildTask extends QuarkusTask { + + static final String NATIVE_PROPERTY_NAMESPACE = "quarkus.native"; + static final String MANIFEST_SECTIONS_PROPERTY_PREFIX = "quarkus.package.manifest.manifest-sections"; + static final String MANIFEST_ATTRIBUTES_PROPERTY_PREFIX = "quarkus.package.manifest.attributes"; + + static final String QUARKUS_BUILD_APP_DIR = "quarkus-build/app"; + static final String QUARKUS_BUILD_DEP_DIR = "quarkus-build/dep"; + static final String QUARKUS_ARTIFACT_PROPERTIES = "quarkus-artifact.properties"; + + QuarkusBuildTask(String description) { + super(description); + } + + @Inject + public abstract FileSystemOperations getFileSystemOperations(); + + QuarkusBuildInternal buildInternal() { + return extension().getBuildInternal().get(); + } + + abstract File buildDir(); + + File runnerJar() { + return new File(buildDir(), runnerFileName()); + } + + File nativeRunner() { + return new File(buildDir(), nativeRunnerFileName()); + } + + String runnerFileName() { + return String.format("%s.jar", runnerBaseName()); + } + + String nativeRunnerFileName() { + return runnerBaseName(); + } + + private String runnerBaseName() { + return extension().buildNativeRunnerName(Map.of()); + } + + File fastJar() { + return new File(buildDir(), buildInternal().getOutputDirectory()); + } + + File buildAppBaseDir() { + return new File(getProject().getBuildDir(), QUARKUS_BUILD_APP_DIR); + } + + File buildDepDir() { + return new File(getProject().getBuildDir(), QUARKUS_BUILD_DEP_DIR); + } +} 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..9d647ed21853a 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 @@ -14,9 +14,12 @@ 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; @@ -31,6 +34,7 @@ import io.quarkus.paths.PathList; import io.quarkus.runtime.LaunchMode; +@CacheableTask public class QuarkusGenerateCode extends QuarkusTask { public static final String QUARKUS_GENERATED_SOURCES = "quarkus-generated-sources"; @@ -66,6 +70,7 @@ public void setCompileClasspath(Configuration compileClasspath) { } @InputFiles + @PathSensitive(PathSensitivity.RELATIVE) public Set getInputDirectory() { Set inputDirectories = new HashSet<>(); 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..b571c96e66ce1 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,6 +1,5 @@ package io.quarkus.gradle.tasks; -import java.util.Map; import java.util.Properties; import org.gradle.api.DefaultTask; @@ -25,22 +24,8 @@ QuarkusPluginExtension 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.putAll(extension().getBuildSystemProperties().get()); realProperties.putIfAbsent("quarkus.application.name", appArtifact.getArtifactId()); realProperties.putIfAbsent("quarkus.application.version", appArtifact.getVersion()); return realProperties;