Skip to content

Commit

Permalink
Initialize dependency validation for gradle extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
glefloch committed Dec 8, 2021
1 parent 7ac7d02 commit 6a907d0
Show file tree
Hide file tree
Showing 8 changed files with 297 additions and 21 deletions.
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

0 comments on commit 6a907d0

Please sign in to comment.