Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compatibility with configuration cache for image tasks #42875

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -120,6 +122,7 @@ public class QuarkusPlugin implements Plugin<Project> {
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;

Expand All @@ -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
Expand Down Expand Up @@ -279,16 +286,28 @@ public boolean isSatisfiedBy(Task t) {
});
});

TaskProvider<ImageCheckRequirementsTask> 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));
task.finalizedBy(quarkusBuild);
});

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));
Expand Down
Original file line number Diff line number Diff line change
@@ -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> 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<String, String> forcedProperties = extension().forcedPropertiesProperty();
super("Perform an image build", true);
}

@TaskAction
public void imageBuild() throws IOException {
Map<String, String> forcedProperties = new HashMap<String, String>();
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);
}
}
Original file line number Diff line number Diff line change
@@ -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<String, ImageCheckRequirementsTask.Builder> BUILDERS = new HashMap<>();
static {
for (ImageCheckRequirementsTask.Builder builder : ImageCheckRequirementsTask.Builder.values()) {
BUILDERS.put(builder.name(), builder);
}
}

enum Builder {
docker,
jib,
buildpack,
openshift
}

Optional<ImageCheckRequirementsTask.Builder> builderFromSystemProperties() {
return Optional.ofNullable(System.getProperty(QUARKUS_CONTAINER_IMAGE_BUILDER))
.filter(BUILDERS::containsKey)
.map(BUILDERS::get);
}

List<ImageCheckRequirementsTask.Builder> 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<Builder> availableBuidlers = availableBuilders();
Optional<Builder> 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=<extension name>",
getName(), availableExtensions));
}

File outputFile = getOutputFile().get().getAsFile();
Files.write(outputFile.toPath(), builder().name().getBytes());
}
}
Original file line number Diff line number Diff line change
@@ -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<String, String> 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<String> 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<String> 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<String, String> forcedProperties = new HashMap<String, String>();
forcedProperties.put(QUARKUS_CONTAINER_IMAGE_BUILD, "true");
forcedProperties.put(QUARKUS_CONTAINER_IMAGE_PUSH, "true");
getAdditionalForcedProperties().get().getProperties().putAll(forcedProperties);
}
}
Original file line number Diff line number Diff line change
@@ -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<String, Builder> 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<Builder> builderFromSystemProperties() {
return Optional.ofNullable(System.getProperty(QUARKUS_CONTAINER_IMAGE_BUILDER))
.filter(BUILDERS::containsKey)
.map(BUILDERS::get);
}

List<Builder> 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<Builder> availableBuidlers = availableBuilders();
Optional<Builder> 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=<extension name>");
abort("Aborting.");
}
public void imageTask() {
}
}
Loading
Loading