diff --git a/devtools/gradle/build.gradle b/devtools/gradle/build.gradle index 4df54bbdd616d8..9b0f0c0da3eee0 100644 --- a/devtools/gradle/build.gradle +++ b/devtools/gradle/build.gradle @@ -48,6 +48,7 @@ subprojects { javadoc { options.addStringOption('encoding', 'UTF-8') + options.addStringOption("Xdoclint:-reference", "-quiet") } } diff --git a/devtools/gradle/gradle-application-plugin/build.gradle b/devtools/gradle/gradle-application-plugin/build.gradle index 57cd56b24de053..525e3b118852f2 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 fb682887162d3c..9ade7029a0fd2b 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 aa33e610677870..b23027df185b5c 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; @@ -46,6 +48,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 +58,17 @@ public class QuarkusPlugin implements Plugin { public static final String ID = "io.quarkus"; + public static final String QUARKUS_PROFILE = "quarkus.profile"; + public static final String DEFAULT_PROFILE = "prod"; + public static final String QUARKUS_PACKAGE_OUTPUT_NAME = "quarkus.package.output-name"; + public static final String QUARKUS_PACKAGE_ADD_RUNNER_SUFFIX = "quarkus.package.add-runner-suffix"; public static final String QUARKUS_PACKAGE_TYPE = "quarkus.package.type"; + public static final String DEFAULT_PACKAGE_TYPE = "jar"; + public static final String OUTPUT_DIRECTORY = "quarkus.package.output-directory"; + 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 +79,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_TASK_NAME = "quarkusAppBuild"; + 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 +112,7 @@ public class QuarkusPlugin implements Plugin { private final ToolingModelBuilderRegistry registry; + @SuppressWarnings("CdiInjectionPointsInspection") @Inject public QuarkusPlugin(ToolingModelBuilderRegistry registry) { this.registry = registry; @@ -148,11 +165,38 @@ 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); + QuarkusBuildConfiguration buildConfig = new QuarkusBuildConfiguration(project, quarkusExt); - TaskProvider quarkusBuild = tasks.register(QUARKUS_BUILD_TASK_NAME, QuarkusBuild.class, build -> { - build.dependsOn(quarkusGenerateCode); - build.getForcedProperties().set(buildConfig.getForcedProperties()); + TaskProvider quarkusBuildDep = tasks.register(QUARKUS_BUILD_DEP_TASK_NAME, + QuarkusBuildDependencies.class, buildConfig); + quarkusBuildDep.configure(task -> { + task.getOutputs().doNotCacheIf("Dependencies are never cached", t -> true); + }); + + TaskProvider quarkusShowConfig = tasks.register(QUARKUS_SHOW_EFFECTIVE_CONFIG_TASK_NAME, + QuarkusShowEffectiveConfig.class, buildConfig); + quarkusShowConfig.configure(task -> { + task.setDescription("Show effective Quarkus build configuration."); + }); + + TaskProvider quarkusBuildApp = tasks.register(QUARKUS_BUILD_APP_TASK_NAME, + QuarkusBuildApp.class, buildConfig); + quarkusBuildApp.configure(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, buildConfig); + quarkusBuild.configure(build -> { + build.dependsOn(quarkusBuildDep, quarkusBuildApp); + build.getOutputs().doNotCacheIf( + "Collects and combines the outputs of " + QUARKUS_BUILD_APP_TASK_NAME + " and " + + QUARKUS_BUILD_DEP_TASK_NAME, + t -> !quarkusExt.getCacheLargeArtifacts().get()); }); TaskProvider imageBuild = tasks.register(IMAGE_BUILD_TASK_NAME, ImageBuild.class, buildConfig); @@ -242,7 +286,7 @@ private void registerTasks(Project project, QuarkusPluginExtension quarkusExt) { quarkusGenerateCode, quarkusGenerateCodeTests); }); - quarkusBuild.configure( + quarkusBuildApp.configure( task -> task.dependsOn(classesTask, resourcesTask, tasks.named(JavaPlugin.JAR_TASK_NAME))); SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class); @@ -310,7 +354,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))); + quarkusBuildApp.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); 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 499ca60d7a3cc2..01ef7f9a0734ce 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,12 +1,15 @@ package io.quarkus.gradle.extension; +import static io.quarkus.gradle.QuarkusPlugin.QUARKUS_PACKAGE_ADD_RUNNER_SUFFIX; +import static io.quarkus.gradle.QuarkusPlugin.QUARKUS_PACKAGE_OUTPUT_NAME; + import java.io.File; import java.nio.file.Path; import java.nio.file.Paths; import java.util.LinkedHashSet; +import java.util.Locale; 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; @@ -42,12 +45,20 @@ public class QuarkusPluginExtension { private final MapProperty quarkusBuildProperties; private final SourceSetExtension sourceSetExtension; + private final Property cacheLargeArtifacts; + private final Property cleanupBuildOutput; + 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.cacheLargeArtifacts = project.getObjects().property(Boolean.class) + .convention(!System.getenv().containsKey("CI")); + this.sourceSetExtension = new SourceSetExtension(); this.quarkusBuildProperties = project.getObjects().mapProperty(String.class, String.class); } @@ -96,34 +107,66 @@ public void beforeTest(Test task) { } } - 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()); + public String resolveBuildProperty(String propertyKey, Map taskSystemProps, String defaultValue) { + Object v = taskSystemProps.get(propertyKey); + if (v instanceof String) { + return v.toString(); } - 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"); + String s = quarkusBuildProperties.get().get(propertyKey); + if (s != null) { + return s; } - return nativeRunnerName.toString(); + s = System.getProperty(propertyKey); + if (s != null) { + return s; + } + s = System.getenv(propertyKey.toUpperCase(Locale.ROOT).replace('.', '_')); + if (s != null) { + return s; + } + return defaultValue; + } + + public String buildNativeRunnerBaseName(Map taskSystemProps) { + return resolveBuildProperty(QUARKUS_PACKAGE_OUTPUT_NAME, taskSystemProps, finalName()); + } + + public String buildNativeRunnerName(Map taskSystemProps) { + String outputName = buildNativeRunnerBaseName(taskSystemProps); + if (Boolean.parseBoolean(resolveBuildProperty(QUARKUS_PACKAGE_ADD_RUNNER_SUFFIX, taskSystemProps, "true"))) { + return outputName + "-runner"; + } + return outputName; } 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(); } diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/EffectiveConfigHelper.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/EffectiveConfigHelper.java new file mode 100644 index 00000000000000..4dcde2df3ba922 --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/EffectiveConfigHelper.java @@ -0,0 +1,225 @@ +package io.quarkus.gradle.tasks; + +import static java.util.Collections.unmodifiableMap; + +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.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.TreeMap; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +import org.eclipse.microprofile.config.spi.ConfigSource; +import org.eclipse.microprofile.config.spi.ConfigSourceProvider; +import org.gradle.api.logging.Logger; + +import io.quarkus.gradle.QuarkusPlugin; +import io.smallrye.config.AbstractLocationConfigSourceLoader; +import io.smallrye.config.PropertiesConfigSource; +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" + * properties (on the Gradle task). + * + *

+ * Eventually used to construct a map with the effective config options from all the sources above. + */ +final class EffectiveConfigHelper { + // System properties starting with 'quarkus.' + final Map quarkusSystemProperties; + // Environment properties starting with 'QUARKUS_' + final Map quarkusEnvProperties; + // Gradle project + extension properties starting with 'quarkus.' + private Map quarkusBuildProperties; + private Map projectProperties; + private Map applicationProperties; + private Map forcedProperties; + final List applicationPropertiesSourceUrls = new ArrayList<>(); + + EffectiveConfigHelper() { + this(collectQuarkusSystemProperties(), collectQuarkusEnvProperties()); + } + + EffectiveConfigHelper(Map quarkusSystemProperties, Map quarkusEnvProperties) { + this.quarkusSystemProperties = quarkusSystemProperties; + this.quarkusEnvProperties = quarkusEnvProperties; + } + + EffectiveConfigHelper applyBuildProperties(Map buildProperties) { + quarkusBuildProperties = unmodifiableMap(collectProperties(buildProperties)); + return this; + } + + EffectiveConfigHelper applyProjectProperties(Map buildProperties) { + projectProperties = collectProperties(buildProperties); + return this; + } + + EffectiveConfigHelper applyApplicationProperties(Set sourceDirectories, Logger logger) { + applicationProperties = loadApplicationProperties(sourceDirectories, logger, applicationPropertiesSourceUrls::add); + return this; + } + + EffectiveConfigHelper applyForcedProperties(Map forcedProperties) { + this.forcedProperties = forcedProperties; + return this; + } + + Map buildEffectiveConfiguration() { + Map map = new HashMap<>(); + + // Add non-"profile-prefixed" configuration options (aka all that do not start with '%') + addNonProfileToEffectiveConfig(projectProperties, map); + addNonProfileToEffectiveConfig(quarkusBuildProperties, map); + addNonProfileToEffectiveConfig(applicationProperties, map); + addNonProfileToEffectiveConfig(quarkusEnvProperties, map); + addNonProfileToEffectiveConfig(quarkusSystemProperties, map); + addNonProfileToEffectiveConfig(forcedProperties, map); + + String quarkusProfile = map.getOrDefault(QuarkusPlugin.QUARKUS_PROFILE, QuarkusPlugin.DEFAULT_PROFILE); + String profilePrefix = "%" + quarkusProfile + "."; + + // Add the configuration options for the selected profile (filtering on the key + truncating the key) + addProfileToEffectiveConfig(projectProperties, profilePrefix, map); + addProfileToEffectiveConfig(quarkusBuildProperties, profilePrefix, map); + addProfileToEffectiveConfig(applicationProperties, profilePrefix, map); + addProfileToEffectiveConfig(quarkusEnvProperties, profilePrefix, map); + addProfileToEffectiveConfig(quarkusSystemProperties, profilePrefix, map); + addProfileToEffectiveConfig(forcedProperties, profilePrefix, map); + + return unmodifiableMap(map); + } + + static Map loadApplicationProperties(Set sourceDirectories, Logger logger, Consumer sourceUrls) { + URL[] resourceUrls = sourceDirectories.stream().map(File::toURI) + .map(u -> { + try { + return u.toURL(); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + }) + .toArray(URL[]::new); + + Map config = new HashMap<>(); + + if (logger.isInfoEnabled()) { + logger.info("Loading Quarkus application config from resource URLs {}", + Arrays.stream(resourceUrls).map(Object::toString).collect(Collectors.joining(", "))); + } + + CombinedConfigSourceProvider configSourceProvider = new CombinedConfigSourceProvider(sourceUrls); + logger.debug("Loading Quarkus application config"); + for (URL resourceUrl : resourceUrls) { + URLClassLoader classLoader = new URLClassLoader(new URL[] { resourceUrl }); + for (ConfigSource configSource : configSourceProvider.getConfigSources(classLoader)) { + Map properties = configSource.getProperties(); + logger.debug("Loaded {} Quarkus application config entries via {}", properties.size(), + configSource); + config.putAll(properties); + } + } + + if (logger.isDebugEnabled()) { + logger.debug("Loaded Quarkus application config from 'application.[properties|yaml|yml]: {}", + new TreeMap<>(config).entrySet().stream().map(Objects::toString) + .collect(Collectors.joining("\n ", "\n ", ""))); + } + + return unmodifiableMap(config); + } + + static final class CombinedConfigSourceProvider extends AbstractLocationConfigSourceLoader implements ConfigSourceProvider { + final Consumer sourceUrls; + + CombinedConfigSourceProvider(Consumer sourceUrls) { + this.sourceUrls = sourceUrls; + } + + @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.yml", "application.yaml", "application.properties" }, 260, + classLoader); + } + } + + static Map collectQuarkusSystemProperties() { + return collectQuarkusSystemProperties(System.getProperties()); + } + + static Map collectQuarkusSystemProperties(Map systemProperties) { + return unmodifiableMap(collectProperties(systemProperties)); + } + + static Map collectQuarkusEnvProperties() { + return collectQuarkusEnvProperties(System.getenv()); + } + + static Map collectQuarkusEnvProperties(Map env) { + Map quarkusEnvProperties = new HashMap<>(); + env.forEach((k, v) -> { + if (k.startsWith("QUARKUS_")) { + // convert environment name to property key + String key = k.toLowerCase(Locale.ROOT).replace('_', '.'); + quarkusEnvProperties.put(key, v); + } + }); + return unmodifiableMap(quarkusEnvProperties); + } + + static Map collectProperties(Map source) { + Map target = new HashMap<>(); + source.forEach((k, v) -> { + String key = k.toString(); + if (key.startsWith("quarkus.") && v instanceof String) { + target.put(key, (String) v); + } + }); + return target; + } + + static void addNonProfileToEffectiveConfig(Map source, Map map) { + source.forEach((k, v) -> { + if (v instanceof String && !k.startsWith("%")) { + map.put(k, (String) v); + } + }); + } + + static void addProfileToEffectiveConfig(Map source, String profilePrefix, Map map) { + source.forEach((k, v) -> { + if (v instanceof String && k.startsWith(profilePrefix)) { + map.put(k.substring(profilePrefix.length()), (String) v); + } + }); + } +} 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 1b3698e4f668a2..9a2e0e0f419c9d 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,64 +1,35 @@ 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.nio.file.Files; +import java.nio.file.Path; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Properties; +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.MapProperty; import org.gradle.api.tasks.CacheableTask; -import org.gradle.api.tasks.Classpath; -import org.gradle.api.tasks.Input; -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.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"; - - private List ignoredEntries = new ArrayList<>(); - private Manifest manifest = new Manifest(); - - private Properties applicationProperties = new Properties(); +public abstract class QuarkusBuild extends QuarkusBuildTask { @Inject - public QuarkusBuild() { - super("Quarkus builds a runner jar based on the build jar"); - } - - public QuarkusBuild(String description) { - super(description); + @SuppressWarnings("CdiInjectionPointsInspection") + public QuarkusBuild(QuarkusBuildConfiguration buildConfiguration) { + super(buildConfiguration, "Quarkus builds a runner jar based on the build jar"); } public QuarkusBuild nativeArgs(Action> action) { @@ -70,159 +41,164 @@ public QuarkusBuild nativeArgs(Action> action) { return this; } - @Optional - @Input - public abstract MapProperty getForcedProperties(); - - @Optional - @Input - public List getIgnoredEntries() { - return ignoredEntries; + public QuarkusBuild manifest(Action action) { + action.execute(this.getManifest()); + return this; } @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; - } - - @Internal - public Manifest getManifest() { - return this.manifest; - } - - public QuarkusBuild manifest(Action action) { - action.execute(this.getManifest()); - return this; + buildConfiguration.ignoredEntries.addAll(ignoredEntries); } @OutputFile public File getRunnerJar() { - return new File(getProject().getBuildDir(), String.format("%s.jar", extension().buildNativeRunnerName(Map.of()))); + return effectiveConfig().runnerJar(); } @OutputFile public File getNativeRunner() { - return new File(getProject().getBuildDir(), extension().buildNativeRunnerName(Map.of())); + return effectiveConfig().nativeRunner(); } @OutputDirectory public File getFastJar() { - return new File(getProject().getBuildDir(), - this.getPropValueWithPrecedence(OUTPUT_DIRECTORY, java.util.Optional.of("quarkus-app"))); + return effectiveConfig().fastJar(); + } + + @OutputFile + public File getArtifactProperties() { + return new File(getProject().getBuildDir(), QUARKUS_ARTIFACT_PROPERTIES); } @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); + String packageType = effectiveConfig().packageType(); + + cleanup(); + + if (QuarkusBuildConfiguration.isFastJarPackageType(packageType)) { + assembleFastJar(); + } else if (QuarkusBuildConfiguration.isLegacyJarPackageType(packageType)) { + assembleLegacyJar(); + } else if (QuarkusBuildConfiguration.isMutableJarPackageType(packageType)) { + assembleFullBuild(); + } else if (QuarkusBuildConfiguration.isUberJarPackageType(packageType)) { + assembleFullBuild(); + } else { + throw new GradleException("Unsupported package type " + packageType); } + } - 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); + 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."); + File fastJar = effectiveConfig().fastJar(); + 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 assembleLegacyJar() { + getLogger().info("Finalizing Quarkus build for {} packaging", effectiveConfig().packageType()); + + Path buildDir = getProject().getBuildDir().toPath(); + Path libDir = buildDir.resolve("lib"); + Path depBuildDir = effectiveConfig().depBuildDir(); + Path appBuildDir = effectiveConfig().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/"); } + + copyRunnersAndArtifactProperties(effectiveConfig().appBuildDir()); } - private void exportCustomManifestProperties(Properties buildSystemProperties) { - if (this.manifest == null) { - return; - } + private void assembleFullBuild() { + File targetDir = getProject().getBuildDir(); - for (Map.Entry attribute : manifest.getAttributes().entrySet()) { - buildSystemProperties.put(toManifestAttributeKey(attribute.getKey()), - attribute.getValue()); - } + // build/quarkus-build/gen + Path genBuildDir = effectiveConfig().genBuildDir(); - 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("Copying Quarkus build for {} packaging from {} into {}", effectiveConfig().packageType(), + genBuildDir, targetDir); + getFileSystemOperations().copy(copy -> { + copy.into(targetDir); + copy.from(genBuildDir); + }); + + copyRunnersAndArtifactProperties(effectiveConfig().genBuildDir()); } - 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 assembleFastJar() { + File appTargetDir = effectiveConfig().fastJar(); + + // build/quarkus-build/app + Path appBuildBaseDir = effectiveConfig().appBuildDir(); + // build/quarkus-build/app/quarkus-app + Path appBuildDir = appBuildBaseDir.resolve(effectiveConfig().outputDirectory()); + // build/quarkus-build/dep + Path depBuildDir = effectiveConfig().depBuildDir(); + + getLogger().info("Synchronizing Quarkus build for {} packaging from {} and {} into {}", effectiveConfig().packageType(), + appBuildDir, depBuildDir, appTargetDir); + getFileSystemOperations().sync(sync -> { + sync.into(appTargetDir); + sync.from(appBuildDir, depBuildDir); + }); + + copyRunnersAndArtifactProperties(effectiveConfig().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."); - } - return String.format("%s.\"%s\".\"%s\"", MANIFEST_SECTIONS_PROPERTY_PREFIX, section, - key); + private void copyRunnersAndArtifactProperties(Path sourceDir) { + File buildDir = getProject().getBuildDir(); + + getLogger().info("Copying remaining Quarkus application artifacts for {} packaging from {} into {}", + effectiveConfig().packageType(), sourceDir, buildDir); + getFileSystemOperations().copy( + copy -> copy.into(buildDir).from(sourceDir).include(QUARKUS_ARTIFACT_PROPERTIES, + effectiveConfig().nativeRunnerFileName(), + effectiveConfig().runnerJarFileName(), "jib-image*", + effectiveConfig().runnerBaseName() + "-native-image-source-jar/**")); } private String expandConfigurationKey(String shortKey) { @@ -232,40 +208,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 00000000000000..25df22b8fc71d4 --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildApp.java @@ -0,0 +1,231 @@ +package io.quarkus.gradle.tasks; + +import java.io.File; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.stream.Collectors; + +import javax.inject.Inject; + +import org.gradle.api.GradleException; +import org.gradle.api.java.archives.Attributes; +import org.gradle.api.logging.LogLevel; +import org.gradle.api.tasks.CacheableTask; +import org.gradle.api.tasks.OutputDirectory; +import org.gradle.api.tasks.TaskAction; + +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.bootstrap.app.QuarkusBootstrap; +import io.quarkus.bootstrap.model.ApplicationModel; +import io.quarkus.bootstrap.resolver.AppModelResolverException; +import io.quarkus.gradle.QuarkusPlugin; +import io.quarkus.maven.dependency.GACTV; + +@CacheableTask +public abstract class QuarkusBuildApp extends QuarkusBuildTask { + + @Inject + @SuppressWarnings("CdiInjectionPointsInspection") + public QuarkusBuildApp(QuarkusBuildConfiguration buildConfiguration) { + super(buildConfiguration, "Quarkus builds a runner jar based on the build jar"); + } + + /** + * Points to {@code build/quarkus-build/app} and includes the uber-jar, native runner and "quarkus-app" directory + * w/o the `lib/` folder. + */ + @OutputDirectory + public File getAppBuildDir() { + return effectiveConfig().appBuildDir().toFile(); + } + + @TaskAction + public void finalizeQuarkusBuild() { + Path appDir = effectiveConfig().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)); + + String packageType = effectiveConfig().packageType(); + + if (QuarkusBuildConfiguration.isFastJarPackageType(packageType)) { + fastJarBuild(); + } else if (QuarkusBuildConfiguration.isLegacyJarPackageType(packageType)) { + legacyJarBuild(); + } else if (QuarkusBuildConfiguration.isMutableJarPackageType(packageType)) { + generateBuild(); + } else if (QuarkusBuildConfiguration.isUberJarPackageType(packageType)) { + generateBuild(); + } else { + throw new GradleException("Unsupported package type " + packageType); + } + } + + private void legacyJarBuild() { + generateBuild(); + + Path genDir = effectiveConfig().genBuildDir(); + Path appDir = effectiveConfig().appBuildDir(); + + getLogger().info("Synchronizing Quarkus legacy-jar app for package type {} into {}", effectiveConfig().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 = effectiveConfig().outputDirectory(); + Path genDir = effectiveConfig().genBuildDir(); + Path appDir = effectiveConfig().appBuildDir(); + + getLogger().info("Synchronizing Quarkus fast-jar-like app for package type {} into {}", effectiveConfig().packageType(), + appDir); + + getFileSystemOperations().sync(sync -> { + sync.into(appDir); + sync.from(genDir); + sync.exclude(outputDirectory + "/lib/**"); + }); + } + + void generateBuild() { + Path genDir = effectiveConfig().genBuildDir(); + String packageType = effectiveConfig().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; + 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); + } + + final Properties effectiveProperties = getBuildSystemProperties(appModel.getAppArtifact()); + effectiveProperties.putAll(forcedProperties); + List ignoredEntries = getIgnoredEntries(); + if (!ignoredEntries.isEmpty()) { + String joinedEntries = String.join(",", ignoredEntries); + effectiveProperties.setProperty("quarkus.package.user-configured-ignored-entries", joinedEntries); + } + + exportCustomManifestProperties(effectiveProperties); + + getLogger().info("Starting Quarkus application build for package type {}", packageType); + + if (getLogger().isEnabled(LogLevel.DEBUG)) { + getLogger().debug("Effective properties: {}", + effectiveProperties.entrySet().stream().map(e -> "" + e) + .collect(Collectors.joining("\n ", "\n ", ""))); + } + + try (CuratedApplication appCreationContext = QuarkusBootstrap.builder() + .setBaseClassLoader(getClass().getClassLoader()) + .setExistingModel(appModel) + .setTargetDirectory(genDir) + .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 + AugmentAction augmentor = appCreationContext + .createAugmentor("io.quarkus.deployment.pkg.builditem.ProcessInheritIODisabled$Factory", + Collections.emptyMap()); + + AugmentResult result = augmentor.createProductionApplication(); + if (result == null) { + getLogger().warn("createProductionApplication() returned 'null' AugmentResult"); + } else { + Path nativeResult = result.getNativeResult(); + getLogger().info("AugmentResult.nativeResult = {}", nativeResult); + List results = result.getResults(); + if (results == null) { + getLogger().info("AugmentResult.results = null"); + } else { + getLogger().info("AugmentResult.results = {}", results.stream().map(ArtifactResult::getPath) + .map(Object::toString).collect(Collectors.joining("\n ", "\n ", ""))); + } + JarResult jar = result.getJar(); + if (jar == null) { + getLogger().info("AugmentResult.jar = null"); + } else { + getLogger().info("AugmentResult.jar.path = {}", jar.getPath()); + getLogger().info("AugmentResult.jar.libraryDir = {}", jar.getLibraryDir()); + getLogger().info("AugmentResult.jar.originalArtifact = {}", jar.getOriginalArtifact()); + getLogger().info("AugmentResult.jar.uberJar = {}", jar.isUberJar()); + } + } + + getLogger().debug("Quarkus application built successfully"); + } catch (BootstrapException e) { + throw new GradleException("Failed to build Quarkus application", e); + } + } + + private void exportCustomManifestProperties(Properties buildSystemProperties) { + for (Map.Entry attribute : effectiveConfig().manifest.getAttributes().entrySet()) { + buildSystemProperties.put(toManifestAttributeKey(attribute.getKey()), + attribute.getValue()); + } + + for (Map.Entry section : effectiveConfig().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/QuarkusBuildConfiguration.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildConfiguration.java index 844feb76619a21..54c0a1e3645b85 100644 --- 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 @@ -1,39 +1,206 @@ package io.quarkus.gradle.tasks; +import static io.quarkus.gradle.tasks.QuarkusGradleUtils.getSourceSet; + +import java.io.File; +import java.net.URL; +import java.nio.file.Path; +import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.TreeMap; +import java.util.stream.Collectors; import org.gradle.api.Project; +import org.gradle.api.file.FileCollection; import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.MapProperty; +import org.gradle.api.tasks.SourceSet; + +import io.quarkus.gradle.QuarkusPlugin; +import io.quarkus.gradle.dsl.Manifest; +import io.quarkus.gradle.extension.QuarkusPluginExtension; public class QuarkusBuildConfiguration { + static final String QUARKUS_BUILD_DIR = "quarkus-build"; + static final String QUARKUS_BUILD_GEN_DIR = QUARKUS_BUILD_DIR + "/gen"; + static final String QUARKUS_BUILD_APP_DIR = QUARKUS_BUILD_DIR + "/app"; + static final String QUARKUS_BUILD_DEP_DIR = QUARKUS_BUILD_DIR + "/dep"; - private final Project project; + final Project project; + final QuarkusPluginExtension extension; + final SourceSet mainSourceSet; + final FileCollection classpath; + final ListProperty forcedDependenciesProperty; + final MapProperty forcedPropertiesProperty; + final List ignoredEntries = new ArrayList<>(); + final Manifest manifest = new Manifest(); - private ListProperty forcedDependencies; - private MapProperty forcedProperties; + private final EffectiveConfigHelper effectiveConfig; + private boolean effective; + private Map configMap; - public QuarkusBuildConfiguration(Project project) { + public QuarkusBuildConfiguration(Project project, QuarkusPluginExtension extension) { this.project = project; - forcedDependencies = project.getObjects().listProperty(String.class); - forcedProperties = project.getObjects().mapProperty(String.class, String.class); + this.extension = extension; + + mainSourceSet = getSourceSet(project, SourceSet.MAIN_SOURCE_SET_NAME); + classpath = dependencyClasspath(mainSourceSet); + + effectiveConfig = new EffectiveConfigHelper(); + + forcedDependenciesProperty = project.getObjects().listProperty(String.class); + forcedPropertiesProperty = project.getObjects().mapProperty(String.class, String.class); } - public ListProperty getForcedDependencies() { - return forcedDependencies; + public void setForcedProperties(Map forcedProperties) { + forcedPropertiesProperty.putAll(forcedProperties); } - public void setForcedDependencies(List forcedDependencies) { - this.forcedDependencies.addAll(forcedDependencies); + Path genBuildDir() { + return project.getBuildDir().toPath().resolve(QUARKUS_BUILD_GEN_DIR); } - public MapProperty getForcedProperties() { - return forcedProperties; + Path appBuildDir() { + return project.getBuildDir().toPath().resolve(QUARKUS_BUILD_APP_DIR); } - public void setForcedProperties(Map forcedProperties) { - this.forcedProperties.putAll(forcedProperties); + Path depBuildDir() { + return project.getBuildDir().toPath().resolve(QUARKUS_BUILD_DEP_DIR); + } + + /** + * "final" location of the "fast-jar". + */ + File fastJar() { + return new File(project.getBuildDir(), outputDirectory()); + } + + /** + * "final" location of the "uber-jar". + */ + File runnerJar() { + return new File(project.getBuildDir(), runnerJarFileName()); + } + + /** + * "final" location of the "native" runner. + */ + File nativeRunner() { + return new File(project.getBuildDir(), nativeRunnerFileName()); + } + + String runnerJarFileName() { + return String.format("%s.jar", runnerName()); + } + + String nativeRunnerFileName() { + return runnerName(); + } + + String runnerName() { + return extension.buildNativeRunnerName(Map.of()); + } + + String runnerBaseName() { + return extension.buildNativeRunnerBaseName(Map.of()); + } + + String outputDirectory() { + return effective().configMap().getOrDefault(QuarkusPlugin.OUTPUT_DIRECTORY, + QuarkusPlugin.DEFAULT_OUTPUT_DIRECTORY); + } + + String packageType() { + return effective().configMap().getOrDefault(QuarkusPlugin.QUARKUS_PACKAGE_TYPE, + QuarkusPlugin.DEFAULT_PACKAGE_TYPE); } + + Map getQuarkusSystemProperties() { + return effectiveConfig.quarkusSystemProperties; + } + + Map getQuarkusEnvProperties() { + return effectiveConfig.quarkusEnvProperties; + } + + static boolean isUberJarPackageType(String packageType) { + // Layout: + // build/ + return "uber-jar".equals(packageType); + } + + static boolean isLegacyJarPackageType(String packageType) { + // Layout: + // build/ + // build/lib/ + return "legacy-jar".equals(packageType); + } + + static boolean isMutableJarPackageType(String packageType) { + // like "fast-jar", but additional folder (not implemented, fallback to "full build" ATM). + // Additional folder: + // build//lib/deployment/ + // ^ contains dependency jars AND generated files + return "mutable-jar".equals(packageType); + } + + static boolean isFastJarPackageType(String packageType) { + // Layout: + // build// + // build//lib/boot/ + // build//lib/main/ + // build//quarkus/ + // build//app/ + // build//... + switch (packageType) { + case "jar": + case "fast-jar": + case "native": + return true; + default: + return false; + } + } + + QuarkusBuildConfiguration effective() { + if (!effective) { + configMap = buildEffectiveConfiguration(); + effective = true; + } + return this; + } + + List applicationPropsSources() { + return effectiveConfig.applicationPropertiesSourceUrls; + } + + private Map buildEffectiveConfiguration() { + Map map = effectiveConfig.applyForcedProperties(forcedPropertiesProperty.get()) + .applyBuildProperties(extension.getQuarkusBuildProperties().get()) + .applyProjectProperties(project.getProperties()) + .applyApplicationProperties(mainSourceSet.getResources().getSourceDirectories().getFiles(), project.getLogger()) + .buildEffectiveConfiguration(); + + if (project.getLogger().isInfoEnabled()) { + project.getLogger().info("Effective Quarkus application config: {}", + new TreeMap<>(map).entrySet().stream().map(Objects::toString) + .collect(Collectors.joining("\n ", "\n ", ""))); + } + + return map; + } + + Map configMap() { + return configMap; + } + + private static FileCollection dependencyClasspath(SourceSet mainSourceSet) { + return mainSourceSet.getCompileClasspath().plus(mainSourceSet.getRuntimeClasspath()) + .plus(mainSourceSet.getAnnotationProcessorPath()) + .plus(mainSourceSet.getResources()); + } + } 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 00000000000000..8013c11748d94c --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildDependencies.java @@ -0,0 +1,162 @@ +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.Set; +import java.util.stream.Collectors; + +import javax.inject.Inject; + +import org.gradle.api.GradleException; +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 + @SuppressWarnings("CdiInjectionPointsInspection") + public QuarkusBuildDependencies(QuarkusBuildConfiguration buildConfiguration) { + super(buildConfiguration, "Collect dependencies for the Quarkus application, prefer the 'quarkusBuild' task"); + } + + /** + * Points to {@code build/quarkus-build/dep}. + */ + @OutputDirectory + public File getDependenciesBuildDir() { + return effectiveConfig().depBuildDir().toFile(); + } + + @TaskAction + public void collectDependencies() { + Path depDir = effectiveConfig().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)); + + String packageType = effectiveConfig().packageType(); + if (QuarkusBuildConfiguration.isFastJarPackageType(packageType)) { + fastJarDependencies(); + } else if (QuarkusBuildConfiguration.isLegacyJarPackageType(packageType)) { + legacyJarDependencies(); + } else if (QuarkusBuildConfiguration.isMutableJarPackageType(packageType)) { + getLogger().info( + "Falling back to 'full quarkus application build' for packaging type {}, this task's output is empty for this package type", + packageType); + } else if (QuarkusBuildConfiguration.isUberJarPackageType(packageType)) { + getLogger().info("Dependencies not needed for packaging type {}, this task's output is empty for this package type", + packageType); + } else { + 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 = effectiveConfig().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 = effectiveConfig().depBuildDir(); + Path lib = depDir.resolve("lib"); + jarDependencies(lib, lib); + } + + private void jarDependencies(Path libBoot, Path libMain) { + Path depDir = effectiveConfig().depBuildDir(); + + getLogger().info("Placing Quarkus application dependencies for package type {} in {}", effectiveConfig().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 = extension().getApplicationModel(); + + // 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/QuarkusBuildTask.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildTask.java new file mode 100644 index 00000000000000..6b39b8b49ea3e5 --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildTask.java @@ -0,0 +1,102 @@ +package io.quarkus.gradle.tasks; + +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import javax.inject.Inject; + +import org.gradle.api.file.FileCollection; +import org.gradle.api.file.FileSystemOperations; +import org.gradle.api.provider.MapProperty; +import org.gradle.api.tasks.Classpath; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.Internal; +import org.gradle.api.tasks.Optional; + +import io.quarkus.gradle.dsl.Manifest; +import io.quarkus.maven.dependency.ResolvedDependency; + +/** + * Base class for the {@link QuarkusBuildDependencies}, {@link QuarkusBuildApp}, {@link QuarkusBuild} tasks + */ +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_ARTIFACT_PROPERTIES = "quarkus-artifact.properties"; + final QuarkusBuildConfiguration buildConfiguration; + + QuarkusBuildTask(QuarkusBuildConfiguration buildConfiguration, String description) { + super(description); + this.buildConfiguration = buildConfiguration; + } + + QuarkusBuildConfiguration effectiveConfig() { + return buildConfiguration.effective(); + } + + @Internal + public boolean isCachedByDefault() { + switch (effectiveConfig().packageType()) { + case "jar": + case "fast-jar": + case "legacy-jar": + return true; + default: + return false; + } + } + + @Optional + @Input + public List getIgnoredEntries() { + return buildConfiguration.ignoredEntries; + } + + @Inject + protected abstract FileSystemOperations getFileSystemOperations(); + + @Optional + @Input + public abstract MapProperty getForcedProperties(); + + @Input + public Map getQuarkusBuildSystemProperties() { + return effectiveConfig().getQuarkusSystemProperties(); + } + + @Input + public Map getQuarkusBuildEnvProperties() { + return effectiveConfig().getQuarkusEnvProperties(); + } + + /** + * Retrieve all {@code quarkus.*} properties, which may be relevant for the Quarkus application build, from + * (likely) all possible sources. + */ + protected Properties getBuildSystemProperties(ResolvedDependency appArtifact) { + final Properties realProperties = new Properties(); + realProperties.putAll(effectiveConfig().configMap()); + realProperties.putIfAbsent("quarkus.application.name", appArtifact.getArtifactId()); + realProperties.putIfAbsent("quarkus.application.version", appArtifact.getVersion()); + return realProperties; + } + + @Classpath + public FileCollection getClasspath() { + return effectiveConfig().classpath; + } + + @Internal + public Manifest getManifest() { + return effectiveConfig().manifest; + } + + @Input + public Map getCachingRelevantInput() { + return effectiveConfig().configMap(); + } +} 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 c10b0b814a4e60..9d647ed21853a3 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/QuarkusShowEffectiveConfig.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusShowEffectiveConfig.java new file mode 100644 index 00000000000000..f03463343aea6b --- /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 + @SuppressWarnings("CdiInjectionPointsInspection") + public QuarkusShowEffectiveConfig(QuarkusBuildConfiguration buildConfiguration) { + super(buildConfiguration, "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 { + QuarkusBuildConfiguration effective = effectiveConfig().effective(); + + String config = effective.configMap().entrySet().stream() + .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 = effective.extension.finalName(); + String packageType = effective.packageType(); + File fastJar = effective.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, + effective.outputDirectory(), + fastJar, + effective.runnerJar(), + effective.nativeRunner(), + effective.applicationPropsSources().stream().map(Object::toString) + .collect(Collectors.joining("\n ", "\n ", "\n"))); + + if (saveConfigProperties.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/test/java/io/quarkus/gradle/QuarkusPluginTest.java b/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/QuarkusPluginTest.java index 107f407dd6f6ea..dbe786c60a3117 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 @@ -41,6 +41,8 @@ public void shouldCreateTasks() { assertTrue(project.getPluginManager().hasPlugin(QuarkusPlugin.ID)); TaskContainer tasks = project.getTasks(); + assertNotNull(tasks.getByName(QuarkusPlugin.QUARKUS_BUILD_APP_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 +72,19 @@ public void shouldMakeQuarkusDevAndQuarkusBuildDependOnClassesTask() { TaskContainer tasks = project.getTasks(); + Task quarkusAppBuild = tasks.getByName(QuarkusPlugin.QUARKUS_BUILD_APP_TASK_NAME); + assertThat(getDependantProvidedTaskName(quarkusAppBuild)) + .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_TASK_NAME) + .contains(QuarkusPlugin.QUARKUS_BUILD_APP_TASK_NAME); Task quarkusDev = tasks.getByName(QuarkusPlugin.QUARKUS_DEV_TASK_NAME); assertThat(getDependantProvidedTaskName(quarkusDev)) diff --git a/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/tasks/EffectiveConfigHelperTest.java b/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/tasks/EffectiveConfigHelperTest.java new file mode 100644 index 00000000000000..1c1bac8dae7fb2 --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/tasks/EffectiveConfigHelperTest.java @@ -0,0 +1,212 @@ +package io.quarkus.gradle.tasks; + +import static io.quarkus.gradle.tasks.EffectiveConfigHelper.*; +import static java.util.Collections.*; +import static java.util.Map.entry; +import static org.mockito.Mockito.mock; + +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.gradle.api.logging.Logger; +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; + +@ExtendWith(SoftAssertionsExtension.class) +public class EffectiveConfigHelperTest { + @InjectSoftAssertions + protected SoftAssertions soft; + + @Test + void empty() { + Logger dummyLogger = mock(Logger.class); + + EffectiveConfigHelper effectiveConfigHelper = new EffectiveConfigHelper(emptyMap(), emptyMap()); + Map map = effectiveConfigHelper.applyBuildProperties(emptyMap()) + .applyProjectProperties(emptyMap()) + .applyForcedProperties(emptyMap()) + .applyApplicationProperties(emptySet(), dummyLogger) + .buildEffectiveConfiguration(); + + soft.assertThat(map).isEmpty(); + } + + @Test + void fromSystemProperties() { + Logger dummyLogger = mock(Logger.class); + + EffectiveConfigHelper effectiveConfigHelper = new EffectiveConfigHelper( + collectQuarkusSystemProperties(Map.of("quarkus.foo", "bar", "not.a.quarkus.thing", "nope")), emptyMap()); + Map map = effectiveConfigHelper.applyBuildProperties(emptyMap()) + .applyProjectProperties(emptyMap()) + .applyForcedProperties(emptyMap()) + .applyApplicationProperties(emptySet(), dummyLogger) + .buildEffectiveConfiguration(); + + soft.assertThat(map).containsExactly(entry("quarkus.foo", "bar")); + } + + @Test + void fromSystemEnvironment() { + Logger dummyLogger = mock(Logger.class); + + EffectiveConfigHelper effectiveConfigHelper = new EffectiveConfigHelper(emptyMap(), + collectQuarkusEnvProperties(Map.of("QUARKUS_FOO", "bar", "NOT_A_QUARKUS_THING", "nope"))); + Map map = effectiveConfigHelper.applyBuildProperties(emptyMap()) + .applyProjectProperties(emptyMap()) + .applyForcedProperties(emptyMap()) + .applyApplicationProperties(emptySet(), dummyLogger) + .buildEffectiveConfiguration(); + + soft.assertThat(map).containsExactly(entry("quarkus.foo", "bar")); + } + + @Test + void fromProjectProperties() { + Logger dummyLogger = mock(Logger.class); + + EffectiveConfigHelper effectiveConfigHelper = new EffectiveConfigHelper(emptyMap(), emptyMap()); + Map map = effectiveConfigHelper.applyBuildProperties(emptyMap()) + .applyProjectProperties(Map.of("quarkus.foo", "bar")) + .applyForcedProperties(emptyMap()) + .applyApplicationProperties(emptySet(), dummyLogger) + .buildEffectiveConfiguration(); + + soft.assertThat(map).containsExactly(entry("quarkus.foo", "bar")); + } + + @Test + void fromForcedProperties() { + Logger dummyLogger = mock(Logger.class); + + EffectiveConfigHelper effectiveConfigHelper = new EffectiveConfigHelper(emptyMap(), emptyMap()); + Map map = effectiveConfigHelper.applyBuildProperties(emptyMap()) + .applyProjectProperties(emptyMap()) + .applyForcedProperties(Map.of("quarkus.foo", "bar")) + .applyApplicationProperties(emptySet(), dummyLogger) + .buildEffectiveConfiguration(); + + soft.assertThat(map).containsExactly(entry("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 { + Logger dummyLogger = mock(Logger.class); + + URL url = getClass().getClassLoader().getResource("io/quarkus/gradle/cfg/" + variant); + List urls = new ArrayList<>(); + Map props = loadApplicationProperties(singleton(new File(url.toURI())), dummyLogger, urls::add); + + 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()); + + soft.assertThat(props).isEqualTo(expected); + } + + @Test + void appPropsOverload() throws Exception { + Logger dummyLogger = mock(Logger.class); + + URL url1 = getClass().getClassLoader().getResource("io/quarkus/gradle/cfg/overload/1/"); + URL url2 = getClass().getClassLoader().getResource("io/quarkus/gradle/cfg/overload/2/"); + Set source = new LinkedHashSet<>(); + source.add(new File(url1.toURI())); + source.add(new File(url2.toURI())); + + EffectiveConfigHelper effectiveConfigHelper = new EffectiveConfigHelper(emptyMap(), emptyMap()); + Map props = effectiveConfigHelper.applyBuildProperties(emptyMap()) + .applyProjectProperties(emptyMap()) + .applyForcedProperties(emptyMap()) + .applyApplicationProperties(source, dummyLogger) + .buildEffectiveConfiguration(); + + soft.assertThat(effectiveConfigHelper.applicationPropertiesSourceUrls).containsExactly( + url1.toURI().resolve("application.properties").toURL(), + url2.toURI().resolve("application.yaml").toURL()); + soft.assertThat(props).containsExactly(entry("quarkus.prop.overload", "overloaded")); + } + + @Test + void appPropsOverloadWrongProfile() throws Exception { + Logger dummyLogger = mock(Logger.class); + + URL url1 = getClass().getClassLoader().getResource("io/quarkus/gradle/cfg/overload/1/"); + URL url2 = getClass().getClassLoader().getResource("io/quarkus/gradle/cfg/overload/2/"); + URL url3 = getClass().getClassLoader().getResource("io/quarkus/gradle/cfg/overload/3/"); + Set source = new LinkedHashSet<>(); + source.add(new File(url1.toURI())); + source.add(new File(url2.toURI())); + source.add(new File(url3.toURI())); + + EffectiveConfigHelper effectiveConfigHelper = new EffectiveConfigHelper(emptyMap(), emptyMap()); + Map props = effectiveConfigHelper.applyBuildProperties(emptyMap()) + .applyProjectProperties(emptyMap()) + .applyForcedProperties(emptyMap()) + .applyApplicationProperties(source, dummyLogger) + .buildEffectiveConfiguration(); + + soft.assertThat(effectiveConfigHelper.applicationPropertiesSourceUrls).containsExactly( + url1.toURI().resolve("application.properties").toURL(), + url2.toURI().resolve("application.yaml").toURL(), + url3.toURI().resolve("application.properties").toURL()); + soft.assertThat(props).containsExactly(entry("quarkus.prop.overload", "overloaded")); + } + + @Test + void appPropsOverloadProdProfile() throws Exception { + Logger dummyLogger = mock(Logger.class); + + URL url1 = getClass().getClassLoader().getResource("io/quarkus/gradle/cfg/overload/1/"); + URL url2 = getClass().getClassLoader().getResource("io/quarkus/gradle/cfg/overload/2/"); + URL url3 = getClass().getClassLoader().getResource("io/quarkus/gradle/cfg/overload/3/"); + URL url4 = getClass().getClassLoader().getResource("io/quarkus/gradle/cfg/overload/4/"); + Set source = new LinkedHashSet<>(); + source.add(new File(url1.toURI())); + source.add(new File(url2.toURI())); + source.add(new File(url3.toURI())); + source.add(new File(url4.toURI())); + + EffectiveConfigHelper effectiveConfigHelper = new EffectiveConfigHelper(emptyMap(), emptyMap()); + Map props = effectiveConfigHelper.applyBuildProperties(emptyMap()) + .applyProjectProperties(emptyMap()) + .applyForcedProperties(emptyMap()) + .applyApplicationProperties(source, dummyLogger) + .buildEffectiveConfiguration(); + + soft.assertThat(effectiveConfigHelper.applicationPropertiesSourceUrls).containsExactly( + url1.toURI().resolve("application.properties").toURL(), + url2.toURI().resolve("application.yaml").toURL(), + url3.toURI().resolve("application.properties").toURL(), + url4.toURI().resolve("application.properties").toURL()); + soft.assertThat(props).containsExactly(entry("quarkus.prop.overload", "but-this-one")); + } +} diff --git a/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/cfg/app-props-and-yaml-and-yml/application.properties b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/cfg/app-props-and-yaml-and-yml/application.properties new file mode 100644 index 00000000000000..cfe085a3e1b514 --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/cfg/app-props-and-yaml-and-yml/application.properties @@ -0,0 +1 @@ +quarkus.prop.properties=hello diff --git a/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/cfg/app-props-and-yaml-and-yml/application.yaml b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/cfg/app-props-and-yaml-and-yml/application.yaml new file mode 100644 index 00000000000000..5cb44a30844f9b --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/cfg/app-props-and-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/cfg/app-props-and-yaml-and-yml/application.yml b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/cfg/app-props-and-yaml-and-yml/application.yml new file mode 100644 index 00000000000000..df2aa80f08d2ca --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/cfg/app-props-and-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/cfg/app-props-and-yaml/application.properties b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/cfg/app-props-and-yaml/application.properties new file mode 100644 index 00000000000000..cfe085a3e1b514 --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/cfg/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/cfg/app-props-and-yaml/application.yaml b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/cfg/app-props-and-yaml/application.yaml new file mode 100644 index 00000000000000..5cb44a30844f9b --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/cfg/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/cfg/app-yaml-and-yml/application.yaml b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/cfg/app-yaml-and-yml/application.yaml new file mode 100644 index 00000000000000..5cb44a30844f9b --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/cfg/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/cfg/app-yaml-and-yml/application.yml b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/cfg/app-yaml-and-yml/application.yml new file mode 100644 index 00000000000000..df2aa80f08d2ca --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/cfg/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/cfg/overload/1/application.properties b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/cfg/overload/1/application.properties new file mode 100644 index 00000000000000..21baa426a52478 --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/cfg/overload/1/application.properties @@ -0,0 +1 @@ +quarkus.prop.overload=not-this diff --git a/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/cfg/overload/2/application.yaml b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/cfg/overload/2/application.yaml new file mode 100644 index 00000000000000..54b46299f0aa87 --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/cfg/overload/2/application.yaml @@ -0,0 +1,3 @@ +quarkus: + prop: + overload: 'overloaded' diff --git a/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/cfg/overload/3/application.properties b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/cfg/overload/3/application.properties new file mode 100644 index 00000000000000..5c84cb28a20474 --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/cfg/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/cfg/overload/4/application.properties b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/cfg/overload/4/application.properties new file mode 100644 index 00000000000000..8729402ad91a13 --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/cfg/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/cfg/single-app-props/application.properties b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/cfg/single-app-props/application.properties new file mode 100644 index 00000000000000..cfe085a3e1b514 --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/cfg/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/cfg/single-app-yaml/application.yaml b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/cfg/single-app-yaml/application.yaml new file mode 100644 index 00000000000000..5cb44a30844f9b --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/cfg/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/cfg/single-app-yml/application.yml b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/cfg/single-app-yml/application.yml new file mode 100644 index 00000000000000..df2aa80f08d2ca --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/test/resources/io/quarkus/gradle/cfg/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 f0de20a9348205..fbf9bb9588a10e 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/integration-tests/gradle/src/test/java/io/quarkus/gradle/BuildConfigurationTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/BuildConfigurationTest.java index 78cb93b8ccba2e..0f6f401f010806 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 @@ -15,13 +15,13 @@ public class BuildConfigurationTest extends QuarkusGradleWrapperTestBase { private static final String ROOT_PROJECT_NAME = "build-configuration"; - private static final String BUILD_GRADLE_PROJECT_NAME = "with-build-configuration"; + private static final String WITH_BUILD_CONFIGURATION_PROJECT_NAME = "with-build-configuration"; private static final String BUILD_GRADLE_OUTPUT_DIR = "build-gradle-output-dir"; private static final String BUILD_GRADLE_UBER_JAR_FILE = "with-build-configuration-1.0.0-SNAPSHOT-runner.jar"; - private static final String APPLICATION_PROPERTIES_PROJECT_NAME = "with-application-properties"; + private static final String WITH_APPLICATION_PROPERTIES_PROJECT_NAME = "with-application-properties"; private static final String APPLICATION_PROPERTIES_OUTPUT_DIR = "application-properties-output-dir"; private static final String APPLICATION_PROPERTIES_UBER_JAR_FILE = "with-application-properties-1.0.0-SNAPSHOT-runner.jar"; @@ -31,9 +31,9 @@ public void buildUseApplicationPropertiesUberJar() throws IOException, Interrupt final File projectDir = getProjectDir(ROOT_PROJECT_NAME); runGradleWrapper(projectDir, "clean", "quarkusBuild"); final Path projectPath = projectDir.toPath(); - final Path buildDirPath = projectPath.resolve(APPLICATION_PROPERTIES_PROJECT_NAME).resolve("build"); - assertThat(buildDirPath.resolve(APPLICATION_PROPERTIES_OUTPUT_DIR)).exists(); + final Path buildDirPath = projectPath.resolve(WITH_APPLICATION_PROPERTIES_PROJECT_NAME).resolve("build"); final Path quarkusAppPath = buildDirPath.resolve(APPLICATION_PROPERTIES_OUTPUT_DIR); + assertThat(quarkusAppPath).exists(); final Path jar = quarkusAppPath.resolve(APPLICATION_PROPERTIES_UBER_JAR_FILE); assertThat(jar).exists(); } @@ -44,11 +44,12 @@ public void applicationPropertiesWithDPropsFastJar() final File projectDir = getProjectDir(ROOT_PROJECT_NAME); runGradleWrapper(projectDir, "clean", "quarkusBuild", "-Dquarkus.package.type=fast-jar"); final Path projectPath = projectDir.toPath(); - final Path buildDirPath = projectPath.resolve(APPLICATION_PROPERTIES_PROJECT_NAME).resolve("build"); - assertThat(buildDirPath.resolve(APPLICATION_PROPERTIES_OUTPUT_DIR)).exists(); + final Path buildDirPath = projectPath.resolve(WITH_APPLICATION_PROPERTIES_PROJECT_NAME).resolve("build"); final Path quarkusAppPath = buildDirPath.resolve(APPLICATION_PROPERTIES_OUTPUT_DIR); + assertThat(quarkusAppPath).exists(); assertThat(quarkusAppPath.resolve("quarkus-run.jar")).exists(); - assertThat(quarkusAppPath.resolve(APPLICATION_PROPERTIES_UBER_JAR_FILE)).doesNotExist(); + assertThat(quarkusAppPath.resolve(APPLICATION_PROPERTIES_UBER_JAR_FILE)) + .doesNotExist(); final Path deploymentDirPath = quarkusAppPath.resolve("lib").resolve("deployment"); assertThat(deploymentDirPath).doesNotExist(); } @@ -59,7 +60,7 @@ public void applicationPropertiesWithDPropsUnmutableJar() final File projectDir = getProjectDir(ROOT_PROJECT_NAME); runGradleWrapper(projectDir, "clean", "quarkusBuild", "-Dquarkus.package.type=mutable-jar"); final Path projectPath = projectDir.toPath(); - final Path buildDirPath = projectPath.resolve(APPLICATION_PROPERTIES_PROJECT_NAME).resolve("build"); + final Path buildDirPath = projectPath.resolve(WITH_APPLICATION_PROPERTIES_PROJECT_NAME).resolve("build"); assertThat(buildDirPath.resolve(APPLICATION_PROPERTIES_OUTPUT_DIR)).exists(); final Path quarkusAppPath = buildDirPath.resolve(APPLICATION_PROPERTIES_OUTPUT_DIR); assertThat(quarkusAppPath.resolve("quarkus-run.jar")).exists(); @@ -73,9 +74,9 @@ public void buildConfigUberJar() throws IOException, URISyntaxException, Interru final File projectDir = getProjectDir(ROOT_PROJECT_NAME); runGradleWrapper(projectDir, "clean", "quarkusBuild"); final Path projectPath = projectDir.toPath(); - final Path buildDirPath = projectPath.resolve(BUILD_GRADLE_PROJECT_NAME).resolve("build"); - assertThat(buildDirPath.resolve(BUILD_GRADLE_OUTPUT_DIR)).exists(); + final Path buildDirPath = projectPath.resolve(WITH_BUILD_CONFIGURATION_PROJECT_NAME).resolve("build"); final Path quarkusAppPath = buildDirPath.resolve(BUILD_GRADLE_OUTPUT_DIR); + assertThat(quarkusAppPath).exists(); final Path jar = quarkusAppPath.resolve(BUILD_GRADLE_UBER_JAR_FILE); assertThat(jar).exists(); } @@ -85,9 +86,9 @@ public void buildConfigFastJarOverride() throws IOException, URISyntaxException, final File projectDir = getProjectDir(ROOT_PROJECT_NAME); runGradleWrapper(projectDir, "clean", "quarkusBuild", "-Dquarkus.package.type=fast-jar"); final Path projectPath = projectDir.toPath(); - final Path buildDirPath = projectPath.resolve(BUILD_GRADLE_PROJECT_NAME).resolve("build"); - assertThat(buildDirPath.resolve(BUILD_GRADLE_OUTPUT_DIR)).exists(); + final Path buildDirPath = projectPath.resolve(WITH_BUILD_CONFIGURATION_PROJECT_NAME).resolve("build"); final Path quarkusAppPath = buildDirPath.resolve(BUILD_GRADLE_OUTPUT_DIR); + assertThat(quarkusAppPath).exists(); assertThat(quarkusAppPath.resolve("quarkus-run.jar")).exists(); assertThat(quarkusAppPath.resolve(BUILD_GRADLE_UBER_JAR_FILE)).doesNotExist(); final Path deploymentDirPath = quarkusAppPath.resolve("lib").resolve("deployment"); @@ -100,8 +101,8 @@ public void withoutConfigurationBuildsDefaults() throws IOException, Interrupted runGradleWrapper(projectDir, "clean", "quarkusBuild"); final Path projectPath = projectDir.toPath(); final Path buildDirPath = projectPath.resolve(WITHOUT_CONFIGURATION_PROJECT_NAME).resolve("build"); - assertThat(buildDirPath.resolve("quarkus-app")).exists(); final Path quarkusAppPath = buildDirPath.resolve("quarkus-app"); + assertThat(quarkusAppPath).exists(); final Path jar = quarkusAppPath.resolve("quarkus-run.jar"); assertThat(jar).exists(); } diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/BuildResult.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/BuildResult.java index c1288e1e04cf3a..5f98405cd6cc39 100644 --- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/BuildResult.java +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/BuildResult.java @@ -1,9 +1,8 @@ package io.quarkus.gradle; -import java.io.BufferedReader; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -21,13 +20,11 @@ public class BuildResult { private BuildResult() { } - public static BuildResult of(InputStream input) { + public static BuildResult of(File logFile) throws IOException { BuildResult result = new BuildResult(); - final List outputLines = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8)) - .lines() - .collect(Collectors.toList()); + List outputLines = Files.readAllLines(logFile.toPath()); result.setTasks(outputLines.stream() - .filter(l -> l.length() != 0 && l.startsWith(TASK_RESULT_PREFIX)) + .filter(l -> l.startsWith(TASK_RESULT_PREFIX)) .map(l -> l.replaceFirst(TASK_RESULT_PREFIX, "").trim()) .map(l -> l.split(" ")) .collect(Collectors.toMap(p -> p[0], p -> { diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/FastJarFormatWorksTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/FastJarFormatWorksTest.java index 1241c56d18c9f8..4b3489c7fb6011 100644 --- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/FastJarFormatWorksTest.java +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/FastJarFormatWorksTest.java @@ -24,7 +24,6 @@ public void testFastJarFormatWorks() throws Exception { BuildResult result = runGradleWrapper(projectDir, "clean", "build"); result.getTasks().forEach((k, v) -> System.err.println(" " + k + " --> " + v)); - System.err.println(result.getOutput()); final Path quarkusApp = projectDir.toPath().resolve("build").resolve("quarkus-app"); assertThat(quarkusApp).exists(); diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusGradleWrapperTestBase.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusGradleWrapperTestBase.java index 8980070833bee7..0f4c033fee31a0 100644 --- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusGradleWrapperTestBase.java +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusGradleWrapperTestBase.java @@ -1,9 +1,7 @@ package io.quarkus.gradle; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; -import java.io.InputStream; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; @@ -46,7 +44,6 @@ public BuildResult runGradleWrapper(File projectDir, String... args) throws IOEx .redirectInput(ProcessBuilder.Redirect.INHERIT) .redirectError(logOutput) .redirectOutput(logOutput) - .redirectError(logOutput) .start(); Runtime.getRuntime().addShutdownHook(new Thread(p::destroyForcibly)); @@ -57,14 +54,12 @@ public BuildResult runGradleWrapper(File projectDir, String... args) throws IOEx if (!done) { destroyProcess(p); } - try (InputStream is = new FileInputStream(logOutput)) { - final BuildResult commandResult = BuildResult.of(is); - int exitCode = p.exitValue(); - if (exitCode != 0) { - printCommandOutput(projectDir, command, commandResult, exitCode); - } - return commandResult; + final BuildResult commandResult = BuildResult.of(logOutput); + int exitCode = p.exitValue(); + if (exitCode != 0) { + printCommandOutput(projectDir, command, commandResult, exitCode); } + return commandResult; } protected void setSystemProperty(String name, String value) { 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 b22b4eab2df071..58c4b50c26057f 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); } }