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

Initialize dependency validation for gradle extensions #21856

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 @@ -6,6 +6,7 @@

public class QuarkusExtensionConfiguration {

private boolean disableValidation;
private String deploymentArtifact;
private String deploymentModule;
private List<String> excludedArtifacts;
Expand All @@ -21,6 +22,14 @@ public QuarkusExtensionConfiguration(Project project) {
this.project = project;
}

public void setDisableValidation(boolean disableValidation) {
this.disableValidation = disableValidation;
}

public boolean isValidationDisabled() {
return disableValidation;
}

public String getDeploymentArtifact() {
return deploymentArtifact;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,17 @@
import io.quarkus.bootstrap.model.ApplicationModel;
import io.quarkus.extension.gradle.dependency.DeploymentClasspathBuilder;
import io.quarkus.extension.gradle.tasks.ExtensionDescriptorTask;
import io.quarkus.extension.gradle.tasks.ValidateExtensionTask;
import io.quarkus.gradle.tooling.ToolingUtils;
import io.quarkus.runtime.LaunchMode;

public class QuarkusExtensionPlugin implements Plugin<Project> {

public static final String DEFAULT_DEPLOYMENT_PROJECT_NAME = "deployment";
public static final String EXTENSION_CONFIGURATION_NAME = "quarkusExtension";

public static final String EXTENSION_DESCRIPTOR_TASK_NAME = "extensionDescriptor";
public static final String VALIDATE_EXTENSION_TASK_NAME = "validateExtension";

public static final String QUARKUS_ANNOTATION_PROCESSOR = "io.quarkus:quarkus-extension-processor";

Expand All @@ -39,16 +42,24 @@ public void apply(Project project) {

private void registerTasks(Project project, QuarkusExtensionConfiguration quarkusExt) {
TaskContainer tasks = project.getTasks();
Configuration runtimeModuleClasspath = project.getConfigurations()
.getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME);

TaskProvider<ExtensionDescriptorTask> extensionDescriptorTask = tasks.register(EXTENSION_DESCRIPTOR_TASK_NAME,
ExtensionDescriptorTask.class, task -> {
JavaPluginConvention convention = project.getConvention().getPlugin(JavaPluginConvention.class);
SourceSet mainSourceSet = convention.getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME);
task.setOutputResourcesDir(mainSourceSet.getOutput().getResourcesDir());
task.setInputResourcesDir(mainSourceSet.getResources().getSourceDirectories().getAsPath());
task.setQuarkusExtensionConfiguration(quarkusExt);
Configuration classpath = project.getConfigurations()
.getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME);
task.setClasspath(classpath);
task.setClasspath(runtimeModuleClasspath);
});

TaskProvider<ValidateExtensionTask> validateExtensionTask = tasks.register(VALIDATE_EXTENSION_TASK_NAME,
ValidateExtensionTask.class, task -> {
task.setRuntimeModuleClasspath(runtimeModuleClasspath);
task.setQuarkusExtensionConfiguration(quarkusExt);
task.onlyIf(t -> !quarkusExt.isValidationDisabled());
});

project.getPlugins().withType(
Expand All @@ -67,6 +78,13 @@ private void registerTasks(Project project, QuarkusExtensionConfiguration quarku
deploymentProject.getPlugins().withType(
JavaPlugin.class,
javaPlugin -> addAnnotationProcessorDependency(deploymentProject));

validateExtensionTask.configure(task -> {
Configuration deploymentModuleClasspath = deploymentProject.getConfigurations()
.getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME);
task.setDeploymentModuleClasspath(deploymentModuleClasspath);
});

deploymentProject.getTasks().withType(Test.class, test -> {
test.useJUnitPlatform();
test.doFirst(task -> {
Expand Down Expand Up @@ -117,10 +135,14 @@ private Project findDeploymentProject(Project project, QuarkusExtensionConfigura

Project deploymentProject = project.getRootProject().findProject(deploymentProjectName);
if (deploymentProject == null) {
project.getLogger().warn("Unable to find deployment project with name: " + deploymentProjectName
+ ". You can configure the deployment project name by setting the 'deploymentArtifact' property in the plugin extension.");
if (project.getParent() != null) {
deploymentProject = project.getParent().findProject(deploymentProjectName);
}
if (deploymentProject == null) {
project.getLogger().warn("Unable to find deployment project with name: " + deploymentProjectName
+ ". You can configure the deployment project name by setting the 'deploymentArtifact' property in the plugin extension.");
}
}

return deploymentProject;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package io.quarkus.extension.gradle.tasks;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import org.gradle.api.DefaultTask;
import org.gradle.api.GradleException;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ResolvedArtifact;
import org.gradle.api.artifacts.ResolvedModuleVersion;
import org.gradle.api.logging.Logger;
import org.gradle.api.tasks.Internal;
import org.gradle.api.tasks.TaskAction;

import io.quarkus.bootstrap.model.AppArtifactKey;
import io.quarkus.extension.gradle.QuarkusExtensionConfiguration;
import io.quarkus.gradle.tooling.dependency.DependencyUtils;
import io.quarkus.gradle.tooling.dependency.ExtensionDependency;

public class ValidateExtensionTask extends DefaultTask {

private QuarkusExtensionConfiguration extensionConfiguration;
private Configuration runtimeModuleClasspath;
private Configuration deploymentModuleClasspath;

public ValidateExtensionTask() {
setDescription("Validate extension dependencies");
setGroup("quarkus");
}

public void setQuarkusExtensionConfiguration(QuarkusExtensionConfiguration extensionConfiguration) {
this.extensionConfiguration = extensionConfiguration;
}

@Internal
public Configuration getRuntimeModuleClasspath() {
return this.runtimeModuleClasspath;
}

public void setRuntimeModuleClasspath(Configuration runtimeModuleClasspath) {
this.runtimeModuleClasspath = runtimeModuleClasspath;
}

@Internal
public Configuration getDeploymentModuleClasspath() {
return this.deploymentModuleClasspath;
}

public void setDeploymentModuleClasspath(Configuration deploymentModuleClasspath) {
this.deploymentModuleClasspath = deploymentModuleClasspath;
}

@TaskAction
public void validateExtension() {
Set<ResolvedArtifact> runtimeArtifacts = getRuntimeModuleClasspath().getResolvedConfiguration().getResolvedArtifacts();

List<AppArtifactKey> deploymentModuleKeys = collectRuntimeExtensionsDeploymentKeys(runtimeArtifacts);
List<AppArtifactKey> invalidRuntimeArtifacts = findExtensionInConfiguration(runtimeArtifacts, deploymentModuleKeys);

Set<ResolvedArtifact> deploymentArtifacts = getDeploymentModuleClasspath().getResolvedConfiguration()
.getResolvedArtifacts();
List<AppArtifactKey> existingDeploymentModuleKeys = findExtensionInConfiguration(deploymentArtifacts,
deploymentModuleKeys);
deploymentModuleKeys.removeAll(existingDeploymentModuleKeys);

boolean hasErrors = false;
if (!invalidRuntimeArtifacts.isEmpty()) {
hasErrors = true;
}
if (!deploymentModuleKeys.isEmpty()) {
hasErrors = true;
}

if (hasErrors) {
printValidationErrors(invalidRuntimeArtifacts, deploymentModuleKeys);
}
}

private List<AppArtifactKey> collectRuntimeExtensionsDeploymentKeys(Set<ResolvedArtifact> runtimeArtifacts) {
List<AppArtifactKey> runtimeExtensions = new ArrayList<>();
for (ResolvedArtifact resolvedArtifact : runtimeArtifacts) {
ExtensionDependency extension = DependencyUtils.getExtensionInfoOrNull(getProject(), resolvedArtifact);
if (extension != null) {
runtimeExtensions.add(new AppArtifactKey(extension.getDeploymentModule().getGroupId(),
extension.getDeploymentModule().getArtifactId()));
}
}
return runtimeExtensions;
}

private List<AppArtifactKey> findExtensionInConfiguration(Set<ResolvedArtifact> deploymentArtifacts,
List<AppArtifactKey> extensions) {
List<AppArtifactKey> foundExtensions = new ArrayList<>();

for (ResolvedArtifact deploymentArtifact : deploymentArtifacts) {
AppArtifactKey key = toAppArtifactKey(deploymentArtifact.getModuleVersion());
if (extensions.contains(key)) {
foundExtensions.add(key);
}
}
return foundExtensions;
}

private void printValidationErrors(List<AppArtifactKey> invalidRuntimeArtifacts,
List<AppArtifactKey> missingDeploymentArtifacts) {
Logger log = getLogger();
log.error("Quarkus Extension Dependency Verification Error");

if (!invalidRuntimeArtifacts.isEmpty()) {
log.error("The following deployment artifact(s) appear on the runtime classpath: ");
for (AppArtifactKey invalidRuntimeArtifact : invalidRuntimeArtifacts) {
log.error("- " + invalidRuntimeArtifact);
}
}

if (!missingDeploymentArtifacts.isEmpty()) {
log.error("The following deployment artifact(s) were found to be missing in the deployment module: ");
for (AppArtifactKey missingDeploymentArtifact : missingDeploymentArtifacts) {
log.error("- " + missingDeploymentArtifact);
}
}

throw new GradleException("Quarkus Extension Dependency Verification Error. See logs below");
}

private static AppArtifactKey toAppArtifactKey(ResolvedModuleVersion artifactId) {
return new AppArtifactKey(artifactId.getId().getGroup(), artifactId.getId().getName());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.Path;
import java.util.Collections;
import java.util.Properties;

import org.assertj.core.api.Assertions;
Expand Down Expand Up @@ -36,7 +37,7 @@ public void setupProject() throws IOException {

@Test
public void jarShouldContainsExtensionPropertiesFile() throws IOException {
TestUtils.writeFile(buildFile, TestUtils.getDefaultGradleBuildFileContent());
TestUtils.writeFile(buildFile, TestUtils.getDefaultGradleBuildFileContent(true, Collections.emptyList()));

BuildResult jarResult = GradleRunner.create()
.withPluginClasspath()
Expand Down Expand Up @@ -65,7 +66,7 @@ public void jarShouldContainsExtensionPropertiesFile() throws IOException {

@Test
public void pluginShouldAddAnnotationProcessor() throws IOException {
TestUtils.createExtensionProject(testProjectDir);
TestUtils.createExtensionProject(testProjectDir, false, Collections.emptyList(), Collections.emptyList());
BuildResult dependencies = GradleRunner.create()
.withPluginClasspath()
.withProjectDir(testProjectDir)
Expand All @@ -77,7 +78,7 @@ public void pluginShouldAddAnnotationProcessor() throws IOException {

@Test
public void pluginShouldAddAnnotationProcessorToDeploymentModule() throws IOException {
TestUtils.createExtensionProject(testProjectDir);
TestUtils.createExtensionProject(testProjectDir, false, Collections.emptyList(), Collections.emptyList());
BuildResult dependencies = GradleRunner.create()
.withPluginClasspath()
.withProjectDir(testProjectDir)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Properties;

import org.gradle.testkit.runner.BuildResult;
Expand All @@ -24,7 +25,12 @@

public class TestUtils {

public static String getDefaultGradleBuildFileContent() throws IOException {
public static String getDefaultGradleBuildFileContent(boolean disableValidation, List<String> implementationDependencies)
throws IOException {
StringBuilder implementationBuilder = new StringBuilder();
for (String implementationDependency : implementationDependencies) {
implementationBuilder.append("implementation(\"").append(implementationDependency).append("\")\n");
}
return "plugins {\n" +
"id 'java'\n" +
"id 'io.quarkus.extension'\n" +
Expand All @@ -34,14 +40,25 @@ public static String getDefaultGradleBuildFileContent() throws IOException {
"repositories { \n" +
"mavenCentral()\n" +
"mavenLocal()\n" +
"}\n" +
QuarkusExtensionPlugin.EXTENSION_CONFIGURATION_NAME + " { \n" +
"disableValidation = " + disableValidation + "\n" +

"}\n" +
"dependencies { \n" +
"implementation enforcedPlatform(\"io.quarkus:quarkus-bom:" + getCurrentQuarkusVersion() + "\")\n" +
"implementation \"io.quarkus:quarkus-arc\" \n" +
implementationBuilder +
"}\n";
}

public static String getDefaultDeploymentBuildFileContent() throws IOException {
public static String getDefaultDeploymentBuildFileContent(List<String> implementationDependencies) throws IOException {

StringBuilder implementationBuilder = new StringBuilder();
for (String implementationDependency : implementationDependencies) {
implementationBuilder.append("implementation(\"").append(implementationDependency).append("\")\n");
}

return "plugins {\n" +
"id 'java'\n" +
"}\n" +
Expand All @@ -53,8 +70,9 @@ public static String getDefaultDeploymentBuildFileContent() throws IOException {
"}\n" +
"dependencies {\n" +
"implementation enforcedPlatform(\"io.quarkus:quarkus-bom:" + getCurrentQuarkusVersion() + "\")\n" +
"implementation \"io.quarkus:quarkus-arc\" \n" +
"implementation project(\":runtime\")" +
"implementation \"io.quarkus:quarkus-arc-deployment\" \n" +
"implementation project(\":runtime\") \n" +
implementationBuilder +
"}\n";
}

Expand All @@ -70,17 +88,19 @@ public static BuildResult runExtensionDescriptorTask(File testProjectDir) {
return extensionDescriptorResult;
}

public static void createExtensionProject(File testProjectDir) throws IOException {
public static void createExtensionProject(File testProjectDir, boolean disableValidation, List<String> runtimeDependencies,
List<String> deploymentDependencies) throws IOException {
File runtimeModule = new File(testProjectDir, "runtime");
runtimeModule.mkdir();
writeFile(new File(runtimeModule, "build.gradle"), getDefaultGradleBuildFileContent());
writeFile(new File(runtimeModule, "build.gradle"),
getDefaultGradleBuildFileContent(disableValidation, runtimeDependencies));
File runtimeTestFile = new File(runtimeModule, "src/main/java/runtime/Test.java");
runtimeTestFile.getParentFile().mkdirs();
writeFile(runtimeTestFile, "package runtime; public class Test {}");

File deploymentModule = new File(testProjectDir, "deployment");
deploymentModule.mkdir();
writeFile(new File(deploymentModule, "build.gradle"), getDefaultDeploymentBuildFileContent());
writeFile(new File(deploymentModule, "build.gradle"), getDefaultDeploymentBuildFileContent(deploymentDependencies));
File deploymentTestFile = new File(deploymentModule, "src/main/java/deployment/Test.java");
deploymentTestFile.getParentFile().mkdirs();
writeFile(deploymentTestFile, "package deployment; public class Test {}");
Expand Down
Loading