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 fa3a5ba77b67b..41f376657d05d 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 @@ -43,6 +43,7 @@ import io.quarkus.gradle.extension.SourceSetExtension; import io.quarkus.gradle.tasks.Deploy; import io.quarkus.gradle.tasks.ImageBuild; +import io.quarkus.gradle.tasks.ImageCheckRequirementsTask; import io.quarkus.gradle.tasks.ImagePush; import io.quarkus.gradle.tasks.QuarkusAddExtension; import io.quarkus.gradle.tasks.QuarkusApplicationModelTask; @@ -64,6 +65,7 @@ import io.quarkus.gradle.tasks.QuarkusTest; import io.quarkus.gradle.tasks.QuarkusTestConfig; import io.quarkus.gradle.tasks.QuarkusUpdate; +import io.quarkus.gradle.tasks.services.ForcedPropertieBuildService; import io.quarkus.gradle.tooling.GradleApplicationModelBuilder; import io.quarkus.gradle.tooling.ToolingUtils; import io.quarkus.gradle.tooling.dependency.DependencyUtils; @@ -120,6 +122,7 @@ public class QuarkusPlugin implements Plugin { public static final String INTEGRATION_TEST_SOURCE_SET_NAME = "integrationTest"; public static final String INTEGRATION_TEST_IMPLEMENTATION_CONFIGURATION_NAME = "integrationTestImplementation"; public static final String INTEGRATION_TEST_RUNTIME_ONLY_CONFIGURATION_NAME = "integrationTestRuntimeOnly"; + public static final String IMAGE_CHECK_REQUIREMENTS_NAME = "quarkusImageExtensionChecks"; private final ToolingModelBuilderRegistry registry; @@ -136,6 +139,10 @@ public void apply(Project project) { // Apply the `java` plugin project.getPluginManager().apply(JavaPlugin.class); + project.getGradle().getSharedServices().registerIfAbsent("forcedPropertiesService", ForcedPropertieBuildService.class, + spec -> { + }); + registerModel(); // register extension @@ -279,8 +286,18 @@ public boolean isSatisfiedBy(Task t) { }); }); + TaskProvider quarkusRequiredExtension = tasks.register(IMAGE_CHECK_REQUIREMENTS_NAME, + ImageCheckRequirementsTask.class, task -> { + task.getOutputFile().set(project.getLayout().getBuildDirectory().file("quarkus/image-name")); + task.getApplicationModel() + .set(quarkusGenerateAppModelTask.flatMap(QuarkusApplicationModelTask::getApplicationModel)); + + }); + tasks.register(IMAGE_BUILD_TASK_NAME, ImageBuild.class, task -> { + task.dependsOn(quarkusRequiredExtension); configureQuarkusBuildTask(project, quarkusExt, task, quarkusBuildAppModelTask); + task.getBuilderName().set(quarkusRequiredExtension.flatMap(ImageCheckRequirementsTask::getOutputFile)); task.getOutputs().doNotCacheIf("Dependencies are never cached", t -> true); task.getApplicationModel() .set(quarkusGenerateAppModelTask.flatMap(QuarkusApplicationModelTask::getApplicationModel)); @@ -288,7 +305,9 @@ public boolean isSatisfiedBy(Task t) { }); tasks.register(IMAGE_PUSH_TASK_NAME, ImagePush.class, task -> { + task.dependsOn(quarkusRequiredExtension); configureQuarkusBuildTask(project, quarkusExt, task, quarkusBuildAppModelTask); + task.getBuilderName().set(quarkusRequiredExtension.flatMap(ImageCheckRequirementsTask::getOutputFile)); task.getOutputs().doNotCacheIf("Dependencies are never cached", t -> true); task.getApplicationModel() .set(quarkusGenerateAppModelTask.flatMap(QuarkusApplicationModelTask::getApplicationModel)); diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/ImageBuild.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/ImageBuild.java index a4a3c58d4f1ca..e2b27b61845f6 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/ImageBuild.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/ImageBuild.java @@ -1,27 +1,33 @@ package io.quarkus.gradle.tasks; -import java.util.Optional; +import static io.quarkus.gradle.tasks.ImageCheckRequirementsTask.QUARKUS_CONTAINER_IMAGE_BUILD; +import static io.quarkus.gradle.tasks.ImageCheckRequirementsTask.QUARKUS_CONTAINER_IMAGE_BUILDER; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.HashMap; +import java.util.Map; import javax.inject.Inject; -import org.gradle.api.provider.MapProperty; -import org.gradle.api.tasks.options.Option; +import org.gradle.api.tasks.TaskAction; public abstract class ImageBuild extends ImageTask { - Optional builder = Optional.empty(); - - @Option(option = "builder", description = "The container image extension to use for building the image (e.g. docker, jib, buildpack, openshift).") - public void setBuilder(Builder builder) { - this.builder = Optional.of(builder); - } - @Inject public ImageBuild() { - super("Perform an image build"); - MapProperty forcedProperties = extension().forcedPropertiesProperty(); + super("Perform an image build", true); + } + + @TaskAction + public void imageBuild() throws IOException { + Map forcedProperties = new HashMap(); + File imageBuilder = getBuilderName().get().getAsFile(); + String inputString = new String(Files.readAllBytes(imageBuilder.toPath())); forcedProperties.put(QUARKUS_CONTAINER_IMAGE_BUILD, "true"); - forcedProperties.put(QUARKUS_CONTAINER_IMAGE_BUILDER, getProject().provider(() -> builder().name())); + forcedProperties.put(QUARKUS_CONTAINER_IMAGE_BUILDER, inputString); + getAdditionalForcedProperties().get().getProperties().putAll(forcedProperties); } } diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/ImageCheckRequirementsTask.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/ImageCheckRequirementsTask.java new file mode 100644 index 0000000000000..53177fc0028b2 --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/ImageCheckRequirementsTask.java @@ -0,0 +1,121 @@ +package io.quarkus.gradle.tasks; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import org.gradle.api.DefaultTask; +import org.gradle.api.GradleException; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.tasks.InputFile; +import org.gradle.api.tasks.OutputFile; +import org.gradle.api.tasks.PathSensitive; +import org.gradle.api.tasks.PathSensitivity; +import org.gradle.api.tasks.TaskAction; + +import io.quarkus.bootstrap.model.ApplicationModel; +import io.quarkus.gradle.tooling.ToolingUtils; +import io.quarkus.maven.dependency.ArtifactCoords; + +public abstract class ImageCheckRequirementsTask extends DefaultTask { + + @OutputFile + public abstract RegularFileProperty getOutputFile(); + + private static final String DEPLOYMENT_SUFFIX = "-deployment"; + static final String QUARKUS_PREFIX = "quarkus-"; + static final String QUARKUS_CONTAINER_IMAGE_PREFIX = "quarkus-container-image-"; + static final String QUARKUS_CONTAINER_IMAGE_BUILD = "quarkus.container-image.build"; + static final String QUARKUS_CONTAINER_IMAGE_PUSH = "quarkus.container-image.push"; + static final String QUARKUS_CONTAINER_IMAGE_BUILDER = "quarkus.container-image.builder"; + + @InputFile + @PathSensitive(PathSensitivity.RELATIVE) + public abstract RegularFileProperty getApplicationModel(); + + static final Map BUILDERS = new HashMap<>(); + static { + for (ImageCheckRequirementsTask.Builder builder : ImageCheckRequirementsTask.Builder.values()) { + BUILDERS.put(builder.name(), builder); + } + } + + enum Builder { + docker, + jib, + buildpack, + openshift + } + + Optional builderFromSystemProperties() { + return Optional.ofNullable(System.getProperty(QUARKUS_CONTAINER_IMAGE_BUILDER)) + .filter(BUILDERS::containsKey) + .map(BUILDERS::get); + } + + List availableBuilders() throws IOException { + // This will pick up all dependencies set in the serialized ApplicationModel. + // This means that extensions like quarkus-container-image-openshift via quarkus-openshift are not picked up + // So, let's relax our filters a bit so that we can pickup quarkus-openshift directly (relax the prefix requirement). + ApplicationModel appModel = ToolingUtils.deserializeAppModel(getApplicationModel().get().getAsFile().toPath()); + return appModel.getDependencies() + .stream() + .map(ArtifactCoords::getArtifactId) + .filter(n -> n.startsWith(QUARKUS_CONTAINER_IMAGE_PREFIX) || n.startsWith(QUARKUS_PREFIX)) + .map(n -> n.replace(QUARKUS_CONTAINER_IMAGE_PREFIX, "").replace(QUARKUS_PREFIX, "").replace(DEPLOYMENT_SUFFIX, + "")) + .filter(BUILDERS::containsKey) + .map(BUILDERS::get) + .collect(Collectors.toList()); + } + + private Builder builder() { + return builderFromSystemProperties() + .or(() -> { + try { + return availableBuilders().stream().findFirst(); + } catch (IOException e) { + throw new RuntimeException(e); + } + }) + .orElse(ImageCheckRequirementsTask.Builder.docker); + } + + @TaskAction + public void checkRequiredExtensions() throws IOException { + + // Currently forcedDependencies() is not implemented for gradle. + // So, let's give users a meaningful warning message. + List availableBuidlers = availableBuilders(); + Optional missingBuilder = Optional.of(builder()).filter(Predicate.not(availableBuidlers::contains)); + missingBuilder.map(builder -> QUARKUS_CONTAINER_IMAGE_PREFIX + builder.name()).ifPresent(missingDependency -> { + throw new GradleException(String.format( + "Task: %s requires extensions: %s. " + + "To add the extensions to the project, you can run the following command:\n" + + "\tgradle addExtension --extensions=%s", + getName(), missingDependency, missingDependency)); + }); + + if (!missingBuilder.isPresent() && availableBuidlers.isEmpty()) { + String availableExtensions = Arrays.stream(Builder.values()) + .map(Builder::name) + .collect(Collectors.joining(", ", "[", "]")); + + throw new GradleException(String.format( + "Task: %s requires one of the extensions: %s. " + + "To add the extensions to the project, you can run the following command:\n" + + "\tgradle addExtension --extensions=", + getName(), availableExtensions)); + } + + File outputFile = getOutputFile().get().getAsFile(); + Files.write(outputFile.toPath(), builder().name().getBytes()); + } +} diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/ImagePush.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/ImagePush.java index 4fa72ecee5a1c..c3f16c89eb909 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/ImagePush.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/ImagePush.java @@ -1,45 +1,26 @@ - package io.quarkus.gradle.tasks; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; +import static io.quarkus.gradle.tasks.ImageCheckRequirementsTask.*; + +import java.util.HashMap; +import java.util.Map; import javax.inject.Inject; -import org.gradle.api.provider.MapProperty; import org.gradle.api.tasks.TaskAction; public abstract class ImagePush extends ImageTask { @Inject public ImagePush() { - super("Perform an image push"); - MapProperty forcedProperties = extension().forcedPropertiesProperty(); - forcedProperties.put(QUARKUS_CONTAINER_IMAGE_BUILD, "true"); - forcedProperties.put(QUARKUS_CONTAINER_IMAGE_PUSH, "true"); + super("Perform an image push", true); } @TaskAction - public void checkRequiredExtensions() { - List containerImageExtensions = getProject().getConfigurations().stream() - .flatMap(c -> c.getDependencies().stream()) - .map(d -> d.getName()) - .filter(n -> n.startsWith(QUARKUS_CONTAINER_IMAGE_PREFIX)) - .map(n -> n.replaceAll("-deployment$", "")) - .collect(Collectors.toList()); - - List extensions = Arrays.stream(ImageBuild.Builder.values()).map(b -> QUARKUS_CONTAINER_IMAGE_PREFIX + b.name()) - .collect(Collectors.toList()); - - if (containerImageExtensions.isEmpty()) { - getLogger().warn("Task: {} requires a container image extension.", getName()); - getLogger().warn("Available container image exntesions: [{}]", - extensions.stream().collect(Collectors.joining(", "))); - getLogger().warn("To add an extension to the project, you can run one of the commands below:"); - extensions.forEach(e -> { - getLogger().warn("\tgradle addExtension --extensions={}", e); - }); - } + public void imagePush() { + Map forcedProperties = new HashMap(); + forcedProperties.put(QUARKUS_CONTAINER_IMAGE_BUILD, "true"); + forcedProperties.put(QUARKUS_CONTAINER_IMAGE_PUSH, "true"); + getAdditionalForcedProperties().get().getProperties().putAll(forcedProperties); } } diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/ImageTask.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/ImageTask.java index f4b44c8f9e8dc..4e3d824c204c3 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/ImageTask.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/ImageTask.java @@ -1,95 +1,23 @@ package io.quarkus.gradle.tasks; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.function.Predicate; -import java.util.stream.Collectors; - +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.tasks.InputFile; +import org.gradle.api.tasks.PathSensitive; +import org.gradle.api.tasks.PathSensitivity; import org.gradle.api.tasks.TaskAction; -import io.quarkus.gradle.dependency.ApplicationDeploymentClasspathBuilder; -import io.quarkus.gradle.tooling.ToolingUtils; -import io.quarkus.runtime.LaunchMode; - public abstract class ImageTask extends QuarkusBuildTask { - private static final String DEPLOYMENT_SUFFIX = "-deployment"; - static final String QUARKUS_PREFIX = "quarkus-"; - static final String QUARKUS_CONTAINER_IMAGE_PREFIX = "quarkus-container-image-"; - static final String QUARKUS_CONTAINER_IMAGE_BUILD = "quarkus.container-image.build"; - static final String QUARKUS_CONTAINER_IMAGE_PUSH = "quarkus.container-image.push"; - static final String QUARKUS_CONTAINER_IMAGE_BUILDER = "quarkus.container-image.builder"; - - static final Map BUILDERS = new HashMap<>(); - static { - for (Builder builder : Builder.values()) { - BUILDERS.put(builder.name(), builder); - } + ImageTask(String description, boolean compatible) { + super(description, compatible); } - enum Builder { - docker, - jib, - buildpack, - openshift - } - - public ImageTask(String description) { - super(description, false); - } - - public Builder builder() { - return builderFromSystemProperties() - .or(() -> availableBuilders().stream().findFirst()) - .orElse(Builder.docker); - } - - Optional builderFromSystemProperties() { - return Optional.ofNullable(System.getProperty(QUARKUS_CONTAINER_IMAGE_BUILDER)) - .filter(BUILDERS::containsKey) - .map(BUILDERS::get); - } - - List availableBuilders() { - // This will only pickup direct dependencies and not transitives - // This means that extensions like quarkus-container-image-openshift via quarkus-openshift are not picked up - // So, let's relax our filters a bit so that we can pickup quarkus-openshift directly (relax the prefix requirement). - return getProject().getConfigurations() - .getByName(ToolingUtils.toDeploymentConfigurationName( - ApplicationDeploymentClasspathBuilder.getFinalRuntimeConfigName(LaunchMode.NORMAL))) - .getDependencies().stream() - .map(d -> d.getName()) - .filter(n -> n.startsWith(QUARKUS_CONTAINER_IMAGE_PREFIX) || n.startsWith(QUARKUS_PREFIX)) - .map(n -> n.replace(QUARKUS_CONTAINER_IMAGE_PREFIX, "").replace(QUARKUS_PREFIX, "").replace(DEPLOYMENT_SUFFIX, - "")) - .filter(BUILDERS::containsKey) - .map(BUILDERS::get) - .collect(Collectors.toList()); - } + @InputFile + @PathSensitive(PathSensitivity.RELATIVE) + public abstract RegularFileProperty getBuilderName(); @TaskAction - public void checkRequiredExtensions() { - // Currently forcedDependencies() is not implemented for gradle. - // So, let's give users a meaningful warning message. - List availableBuidlers = availableBuilders(); - Optional missingBuilder = Optional.of(builder()).filter(Predicate.not(availableBuidlers::contains)); - missingBuilder.map(builder -> QUARKUS_CONTAINER_IMAGE_PREFIX + builder.name()).ifPresent(missingDependency -> { - getLogger().warn("Task: {} requires extensions: {}", getName(), missingDependency); - getLogger().warn("To add the extensions to the project you can run the following command:"); - getLogger().warn("\tgradle addExtension --extensions={}", missingDependency); - abort("Aborting."); - }); - - if (!missingBuilder.isPresent() && availableBuidlers.isEmpty()) { - getLogger().warn("Task: {} requires on of extensions: {}", getName(), - Arrays.stream(Builder.values()).map(Builder::name).collect(Collectors.joining(", ", "[", "]"))); - getLogger().warn("To add the extensions to the project you can run the following command:"); - getLogger().warn("\tgradle addExtension --extensions="); - abort("Aborting."); - } + public void imageTask() { } } 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 d4e7251c8be0c..039929188187c 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 @@ -48,7 +48,8 @@ public QuarkusBuild nativeArgs(Action> action) { Map nativeArgsMap = new HashMap<>(); action.execute(nativeArgsMap); for (Map.Entry nativeArg : nativeArgsMap.entrySet()) { - additionalForcedProperties.put(expandConfigurationKey(nativeArg.getKey()), nativeArg.getValue().toString()); + getAdditionalForcedProperties().get().getProperties().put(expandConfigurationKey(nativeArg.getKey()), + nativeArg.getValue().toString()); } return this; } diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildTask.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildTask.java index d65757c520bd9..f54010f9aba90 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildTask.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuildTask.java @@ -16,6 +16,8 @@ import org.gradle.api.file.FileSystemOperations; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.logging.LogLevel; +import org.gradle.api.provider.Property; +import org.gradle.api.services.ServiceReference; import org.gradle.api.tasks.Classpath; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputFile; @@ -28,6 +30,7 @@ import io.quarkus.bootstrap.model.ApplicationModel; import io.quarkus.deployment.pkg.PackageConfig; +import io.quarkus.gradle.tasks.services.ForcedPropertieBuildService; import io.quarkus.gradle.tasks.worker.BuildWorker; import io.quarkus.gradle.tooling.ToolingUtils; import io.smallrye.config.Expressions; @@ -44,7 +47,9 @@ public abstract class QuarkusBuildTask extends QuarkusTask { static final String QUARKUS_ARTIFACT_PROPERTIES = "quarkus-artifact.properties"; static final String NATIVE_SOURCES = "native-sources"; private final QuarkusPluginExtensionView extensionView; - protected final Map additionalForcedProperties = new HashMap<>(); + + @ServiceReference("forcedPropertiesService") + abstract Property getAdditionalForcedProperties(); QuarkusBuildTask(String description, boolean compatible) { super(description, compatible); @@ -239,7 +244,8 @@ void generateBuild() { ApplicationModel appModel = resolveAppModelForBuild(); SmallRyeConfig config = getExtensionView() - .buildEffectiveConfiguration(appModel.getAppArtifact(), additionalForcedProperties).getConfig(); + .buildEffectiveConfiguration(appModel.getAppArtifact(), getAdditionalForcedProperties().get().getProperties()) + .getConfig(); Map quarkusProperties = Expressions.withoutExpansion(() -> { Map values = new HashMap<>(); for (String key : config.getMapKeys("quarkus").values()) { 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 index adc4479990c7d..39882f3e5434c 100644 --- 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 @@ -49,7 +49,8 @@ public void dumpEffectiveConfiguration() { try { ApplicationModel appModel = resolveAppModelForBuild(); EffectiveConfig effectiveConfig = getExtensionView() - .buildEffectiveConfiguration(appModel.getAppArtifact(), additionalForcedProperties); + .buildEffectiveConfiguration(appModel.getAppArtifact(), + getAdditionalForcedProperties().get().getProperties()); SmallRyeConfig config = effectiveConfig.getConfig(); List sourceNames = new ArrayList<>(); config.getConfigSources().forEach(configSource -> sourceNames.add(configSource.getName())); diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/services/ForcedPropertieBuildService.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/services/ForcedPropertieBuildService.java new file mode 100644 index 0000000000000..9fdcc21fecd90 --- /dev/null +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/services/ForcedPropertieBuildService.java @@ -0,0 +1,21 @@ +package io.quarkus.gradle.tasks.services; + +import java.util.HashMap; +import java.util.Map; + +import org.gradle.api.services.BuildService; +import org.gradle.api.services.BuildServiceParameters; + +public abstract class ForcedPropertieBuildService implements BuildService { + + Map properties = new HashMap<>(); + + public ForcedPropertieBuildService() { + + } + + public Map getProperties() { + return properties; + } + +} diff --git a/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/tasks/TasksConfigurationCacheCompatibilityTest.java b/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/tasks/TasksConfigurationCacheCompatibilityTest.java index e1d0d1f957c2f..1b1472551cbae 100644 --- a/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/tasks/TasksConfigurationCacheCompatibilityTest.java +++ b/devtools/gradle/gradle-application-plugin/src/test/java/io/quarkus/gradle/tasks/TasksConfigurationCacheCompatibilityTest.java @@ -1,8 +1,6 @@ package io.quarkus.gradle.tasks; import static io.quarkus.gradle.QuarkusPlugin.DEPLOY_TASK_NAME; -import static io.quarkus.gradle.QuarkusPlugin.IMAGE_BUILD_TASK_NAME; -import static io.quarkus.gradle.QuarkusPlugin.IMAGE_PUSH_TASK_NAME; import static io.quarkus.gradle.QuarkusPlugin.QUARKUS_BUILD_APP_PARTS_TASK_NAME; import static io.quarkus.gradle.QuarkusPlugin.QUARKUS_BUILD_DEP_TASK_NAME; import static io.quarkus.gradle.QuarkusPlugin.QUARKUS_BUILD_TASK_NAME; @@ -48,10 +46,7 @@ private static Stream compatibleTasks() { } private static Stream nonCompatibleQuarkusBuildTasks() { - return Stream.of( - IMAGE_BUILD_TASK_NAME, - IMAGE_PUSH_TASK_NAME, - DEPLOY_TASK_NAME); + return Stream.of(DEPLOY_TASK_NAME); } @Test diff --git a/docs/src/main/asciidoc/gradle-tooling.adoc b/docs/src/main/asciidoc/gradle-tooling.adoc index d0b607b1df823..0ba1a19ae1e34 100644 --- a/docs/src/main/asciidoc/gradle-tooling.adoc +++ b/docs/src/main/asciidoc/gradle-tooling.adoc @@ -828,6 +828,8 @@ The current state of compatibility is shown in the following table: |`quarkusAppPartsBuild`|✅ |`quarkusShowEffectiveConfig`|✅ |`quarkusBuild`|✅ +|`imageBuild`|✅ +|`imagePush`|✅ |`quarkusDev`|❌ |`quarkusRun`|❌ |`quarkusRemoteDev`|❌ @@ -835,8 +837,6 @@ The current state of compatibility is shown in the following table: |`quarkusGoOffline`|❌ |`quarkusInfo`|❌ |`quarkusUpdate`|❌ -|`imageBuild`|❌ -|`imagePush`|❌ |`deploy`|❌ |`listExtensions`|❌ |`listCategories`|❌ diff --git a/integration-tests/gradle/src/main/resources/it-test-basic-project/src/main/docker/Dockerfile.jvm b/integration-tests/gradle/src/main/resources/it-test-basic-project/src/main/docker/Dockerfile.jvm new file mode 100644 index 0000000000000..da6ef65b1b925 --- /dev/null +++ b/integration-tests/gradle/src/main/resources/it-test-basic-project/src/main/docker/Dockerfile.jvm @@ -0,0 +1,97 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode +# +# Before building the container image run: +# +# ./gradlew build +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.jvm -t quarkus/code-with-quarkus-jvm . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/code-with-quarkus-jvm +# +# If you want to include the debug port into your docker image +# you will have to expose the debug port (default 5005 being the default) like this : EXPOSE 8080 5005. +# Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005 +# when running the container +# +# Then run the container using : +# +# docker run -i --rm -p 8080:8080 quarkus/code-with-quarkus-jvm +# +# This image uses the `run-java.sh` script to run the application. +# This scripts computes the command line to execute your Java application, and +# includes memory/GC tuning. +# You can configure the behavior using the following environment properties: +# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class") +# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options +# in JAVA_OPTS (example: "-Dsome.property=foo") +# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is +# used to calculate a default maximal heap memory based on a containers restriction. +# If used in a container without any memory constraints for the container then this +# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio +# of the container available memory as set here. The default is `50` which means 50% +# of the available memory is used as an upper boundary. You can skip this mechanism by +# setting this value to `0` in which case no `-Xmx` option is added. +# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This +# is used to calculate a default initial heap memory based on the maximum heap memory. +# If used in a container without any memory constraints for the container then this +# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio +# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx` +# is used as the initial heap size. You can skip this mechanism by setting this value +# to `0` in which case no `-Xms` option is added (example: "25") +# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS. +# This is used to calculate the maximum value of the initial heap memory. If used in +# a container without any memory constraints for the container then this option has +# no effect. If there is a memory constraint then `-Xms` is limited to the value set +# here. The default is 4096MB which means the calculated value of `-Xms` never will +# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096") +# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output +# when things are happening. This option, if set to true, will set +# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true"). +# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example: +# true"). +# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787"). +# - CONTAINER_CORE_LIMIT: A calculated core limit as described in +# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2") +# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024"). +# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion. +# (example: "20") +# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking. +# (example: "40") +# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection. +# (example: "4") +# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus +# previous GC times. (example: "90") +# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20") +# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100") +# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should +# contain the necessary JRE command-line options to specify the required GC, which +# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC). +# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080") +# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080") +# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be +# accessed directly. (example: "foo.example.com,bar.example.com") +# +### +FROM registry.access.redhat.com/ubi8/openjdk-21:1.18 + +ENV LANGUAGE='en_US:en' + + +# We make four distinct layers so if there are application changes the library layers can be re-used +COPY --chown=185 build/quarkus-app/lib/ /deployments/lib/ +COPY --chown=185 build/quarkus-app/*.jar /deployments/ +COPY --chown=185 build/quarkus-app/app/ /deployments/app/ +COPY --chown=185 build/quarkus-app/quarkus/ /deployments/quarkus/ + +EXPOSE 8080 +USER 185 +ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" +ENV JAVA_APP_JAR="/deployments/quarkus-run.jar" + +ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ] + diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/ImageTasksWithConfigurationCacheTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/ImageTasksWithConfigurationCacheTest.java new file mode 100644 index 0000000000000..e3010ce8e507a --- /dev/null +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/ImageTasksWithConfigurationCacheTest.java @@ -0,0 +1,58 @@ +package io.quarkus.gradle; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; + +public class ImageTasksWithConfigurationCacheTest extends QuarkusGradleWrapperTestBase { + + @Test + @DisabledOnOs(value = OS.WINDOWS, disabledReason = "cannot access the file because another process has locked a portion of the file") + public void shouldReuseConfigurationCacheImageBuildIfTheExtensionIsAdded() throws Exception { + File projectDir = getProjectDir("it-test-basic-project"); + + runGradleWrapper(projectDir, "addExtension", "--extensions=quarkus-container-image-docker"); + + BuildResult buildResult = runGradleWrapper(projectDir, "imageBuild"); + assertThat(BuildResult.isSuccessful(buildResult.getTasks().get(":imageBuild"))).isTrue(); + + assertTrue(buildResult.getOutput().contains("Configuration cache entry stored")); + BuildResult buildResult3 = runGradleWrapper(projectDir, "imageBuild"); + assertTrue(buildResult3.getOutput().contains("Reusing configuration cache.")); + + } + + @Test + @DisabledOnOs(value = OS.WINDOWS, disabledReason = "cannot access the file because another process has locked a portion of the file") + public void shouldReuseConfigurationCacheWithProjectIsolationImageBuildIfTheExtensionIsAdded() throws Exception { + File projectDir = getProjectDir("it-test-basic-project"); + + runGradleWrapper(projectDir, "addExtension", "--extensions=quarkus-container-image-docker"); + + BuildResult buildResult = runGradleWrapper(projectDir, "imageBuild", "-Dorg.gradle.unsafe.isolated-projects=true"); + assertThat(BuildResult.isSuccessful(buildResult.getTasks().get(":imageBuild"))).isTrue(); + + assertTrue(buildResult.getOutput().contains("Configuration cache entry stored")); + BuildResult buildResult3 = runGradleWrapper(projectDir, "imageBuild", "-Dorg.gradle.unsafe.isolated-projects=true"); + assertTrue(buildResult3.getOutput().contains("Reusing configuration cache.")); + + } + + @Test + public void shouldFailIfExtensionIsNotDefinedInTheBuild() throws Exception { + File projectDir = getProjectDir("it-test-basic-project"); + BuildResult buildResultImageBuild = runGradleWrapper(true, projectDir, "clean", "imageBuild", "--no-build-cache"); + assertTrue(buildResultImageBuild.getOutput() + .contains("Task: quarkusImageExtensionChecks requires extensions: quarkus-container-image-docker")); + + BuildResult buildResultImagePush = runGradleWrapper(true, projectDir, "clean", "imagePush", "--no-build-cache"); + assertTrue(buildResultImagePush.getOutput() + .contains("Task: quarkusImageExtensionChecks requires extensions: quarkus-container-image-docker")); + + } +}