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 3e8a005
Show file tree
Hide file tree
Showing 14 changed files with 303 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 3e8a005

Please sign in to comment.