From 305d903c173a1c324d460223e34a76f2c0927a8b Mon Sep 17 00:00:00 2001 From: Walter Oggioni Date: Mon, 2 Aug 2021 14:53:21 +0200 Subject: [PATCH] fixed dependency-export plugin for Gradle 7.0 --- build.gradle | 5 + .../dependency/export/ExportDependencies.java | 76 ++++--- .../dependency/export/RenderDependencies.java | 57 +++-- jpms-check/build.gradle | 2 +- jpms-check/build.gradle.kts | 14 -- .../gradle/jpms/check/JPMSCheckPlugin.groovy | 9 +- .../plugins/jpms/check/JPMSCheckPlugin.groovy | 215 ------------------ 7 files changed, 88 insertions(+), 290 deletions(-) delete mode 100644 jpms-check/build.gradle.kts delete mode 100644 jpms-check/src/main/groovy/net/woggioni/plugins/jpms/check/JPMSCheckPlugin.groovy diff --git a/build.gradle b/build.gradle index 054eda4..e926245 100644 --- a/build.gradle +++ b/build.gradle @@ -23,6 +23,11 @@ subprojects { useJUnitPlatform() } + java { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + publishing { repositories { maven { diff --git a/dependency-export/src/main/java/net/woggioni/gradle/dependency/export/ExportDependencies.java b/dependency-export/src/main/java/net/woggioni/gradle/dependency/export/ExportDependencies.java index f4acc4c..14e995d 100644 --- a/dependency-export/src/main/java/net/woggioni/gradle/dependency/export/ExportDependencies.java +++ b/dependency-export/src/main/java/net/woggioni/gradle/dependency/export/ExportDependencies.java @@ -10,12 +10,16 @@ import org.gradle.api.artifacts.component.ModuleComponentIdentifier; import org.gradle.api.artifacts.component.ProjectComponentIdentifier; import org.gradle.api.artifacts.result.*; import org.gradle.api.file.FileCollection; +import org.gradle.api.file.RegularFile; +import org.gradle.api.file.RegularFileProperty; import org.gradle.api.model.ObjectFactory; +import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPluginConvention; import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputFiles; +import org.gradle.api.tasks.Internal; import org.gradle.api.tasks.OutputFile; import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.options.Option; @@ -32,24 +36,28 @@ import java.util.stream.Stream; public class ExportDependencies extends DefaultTask { - @Getter - @Setter - @Input - private Property configurationName; + @Getter(onMethod_ = { @Input }) + private final Property configurationName; @Getter - @Setter + @Internal + private final RegularFileProperty outputFile; + + @Input + public String getDestination() { + return outputFile.map(RegularFile::getAsFile).map(File::getAbsolutePath).get(); + } + @OutputFile - private Property outputFile; + public Provider getResult() { + return outputFile.map(RegularFile::getAsFile); + } - @Getter - @Setter - @Input - private Property showArtifacts; + @Getter(onMethod_ = { @Input }) + private final Property showArtifacts; private final JavaPluginConvention javaPluginConvention; - @InputFiles public Provider getConfigurationFiles() { return configurationName.flatMap(name -> getProject().getConfigurations().named(name)); @@ -62,7 +70,8 @@ public class ExportDependencies extends DefaultTask { @Option(option = "output", description = "Set the output file name") public void setOutput(String outputFile) { - this.outputFile.set(getProject().file(outputFile)); + Provider fileProvider = getProject().provider(() -> new File(outputFile)); + this.outputFile.set(getProject().getLayout().file(fileProvider)); } @Option(option = "showArtifacts", description = "Show artifacts") @@ -73,9 +82,10 @@ public class ExportDependencies extends DefaultTask { @Inject public ExportDependencies(ObjectFactory objects) { javaPluginConvention = getProject().getConvention().getPlugin(JavaPluginConvention.class); - configurationName = objects.property(String.class).convention("runtimeClasspath"); - outputFile = objects.property(File.class).convention( - getProject().provider(() -> new File(javaPluginConvention.getDocsDir(), "dependencies.dot"))); + configurationName = objects.property(String.class).convention(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME); + Provider defaultOutputFileProvider = + getProject().provider(() -> new File(javaPluginConvention.getDocsDir(), "dependencies.dot")); + outputFile = objects.fileProperty().convention(getProject().getLayout().file(defaultOutputFileProvider)); showArtifacts = objects.property(Boolean.class).convention(false); } @@ -85,9 +95,7 @@ public class ExportDependencies extends DefaultTask { @TaskAction @SneakyThrows - void run() { - Map map = new HashMap<>(); - + public void run() { Configuration requestedConfiguration = Optional.ofNullable(getProject().getConfigurations().named(configurationName.get()).getOrNull()).orElseThrow(() -> { String resolvableConfigurations = '[' + getProject().getConfigurations().stream() .filter(Configuration::isCanBeResolved) @@ -97,13 +105,13 @@ public class ExportDependencies extends DefaultTask { "resolvable configurations in this project are %s", configurationName.get(), resolvableConfigurations)); }); ResolutionResult resolutionResult = requestedConfiguration.getIncoming().getResolutionResult(); - Path destination = outputFile.map(it -> { - if (it.isAbsolute()) { - return it; - } else { - return new File(javaPluginConvention.getDocsDir(), it.toString()); - } - }).map(File::toPath).get(); + Path destination = outputFile.map(RegularFile::getAsFile).map(File::toPath).get(); + doStuff(requestedConfiguration, resolutionResult, destination); + } + + @SneakyThrows + private void doStuff(Configuration requestedConfiguration, ResolutionResult resolutionResult, Path destination) { + Map map = new HashMap<>(); Files.createDirectories(destination.getParent()); try(Writer writer = Files.newBufferedWriter(destination)) { writer.write("digraph G {"); @@ -160,17 +168,17 @@ public class ExportDependencies extends DefaultTask { throw new IllegalArgumentException(id.getClass().getName()); } Map attrs = Stream.of( - new AbstractMap.SimpleEntry<>("label", label), - new AbstractMap.SimpleEntry<>("shape", quote(shape)), - new AbstractMap.SimpleEntry<>("style", quote("filled")), - artifacts.map(it -> new AbstractMap.SimpleEntry<>("margin", quote("0"))).orElse(null), - new AbstractMap.SimpleEntry<>("fillcolor", quote(color)) + new AbstractMap.SimpleEntry<>("label", label), + new AbstractMap.SimpleEntry<>("shape", quote(shape)), + new AbstractMap.SimpleEntry<>("style", quote("filled")), + artifacts.map(it -> new AbstractMap.SimpleEntry<>("margin", quote("0"))).orElse(null), + new AbstractMap.SimpleEntry<>("fillcolor", quote(color)) ).filter(Objects::nonNull).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); writer.write(" node_" + map.get(component) + " [" + - attrs.entrySet().stream() - .map(it -> it.getKey() + '=' + it.getValue()) - .collect(Collectors.joining(", ")) + - "];"); + attrs.entrySet().stream() + .map(it -> it.getKey() + '=' + it.getValue()) + .collect(Collectors.joining(", ")) + + "];"); writer.write('\n'); } diff --git a/dependency-export/src/main/java/net/woggioni/gradle/dependency/export/RenderDependencies.java b/dependency-export/src/main/java/net/woggioni/gradle/dependency/export/RenderDependencies.java index e0ca4f2..c116f05 100644 --- a/dependency-export/src/main/java/net/woggioni/gradle/dependency/export/RenderDependencies.java +++ b/dependency-export/src/main/java/net/woggioni/gradle/dependency/export/RenderDependencies.java @@ -5,11 +5,17 @@ import lombok.Setter; import lombok.SneakyThrows; import org.gradle.api.DefaultTask; import org.gradle.api.GradleException; +import org.gradle.api.file.RegularFile; +import org.gradle.api.file.RegularFileProperty; import org.gradle.api.model.ObjectFactory; import org.gradle.api.plugins.JavaPluginConvention; import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputFile; +import org.gradle.api.tasks.Internal; +import org.gradle.api.tasks.Optional; +import org.gradle.api.tasks.OutputFile; import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.options.Option; @@ -21,27 +27,36 @@ import java.util.List; public class RenderDependencies extends DefaultTask { - @Getter - @InputFile + @Getter(onMethod_ = { @InputFile }) private Provider sourceFile; - @Getter - @Setter - private Property format; + @Getter(onMethod_ = { @Input}) + private final Property format; + + @Getter(onMethod_ = { @Input }) + private final Property graphvizExecutable; @Getter - @Setter - private Property graphvizExecutable; + @Internal + private final RegularFileProperty outputFile; - @Getter - @Setter - private Property outputFile; + @Input + @Optional + public String getDestination() { + return outputFile.map(RegularFile::getAsFile).map(File::getAbsolutePath).getOrNull(); + } + + @OutputFile + public Provider getResult() { + return outputFile.map(RegularFile::getAsFile); + } private final JavaPluginConvention javaPluginConvention; @Option(option = "output", description = "Set the output file name") public void setOutputCli(String outputFile) { - this.outputFile.set(getProject().file(outputFile)); + Provider fileProvider = getProject().provider(() -> new File(outputFile)); + this.outputFile.set(getProject().getLayout().file(fileProvider)); } @Option(option = "format", description = "Set output format (see https://graphviz.org/doc/info/output.html)") @@ -50,7 +65,7 @@ public class RenderDependencies extends DefaultTask { } public void setExportTask(Provider taskProvider) { - sourceFile = taskProvider.flatMap(ExportDependencies::getOutputFile); + sourceFile = taskProvider.flatMap(ExportDependencies::getResult); } @Inject @@ -59,24 +74,22 @@ public class RenderDependencies extends DefaultTask { javaPluginConvention = getProject().getConvention().getPlugin(JavaPluginConvention.class); format = objects.property(String.class).convention("xlib"); graphvizExecutable = objects.property(String.class).convention("dot"); - outputFile = objects.property(File.class) - .convention(new File(javaPluginConvention.getDocsDir(), "renderedDependencies")); + Provider defaultOutputFileProvider = + getProject().provider(() -> new File(javaPluginConvention.getDocsDir(), "renderedDependencies")); + outputFile = objects.fileProperty().convention(getProject().getLayout().file(defaultOutputFileProvider)); } @TaskAction @SneakyThrows void run() { - Path destination = outputFile.map(it -> { - if (it.isAbsolute()) { - return it; - } else { - return new File(javaPluginConvention.getDocsDir(), it.toString()); - } - }).map(File::toPath).get(); + Path destination = outputFile + .map(RegularFile::getAsFile) + .map(File::toPath) + .get(); List cmd = Arrays.asList( graphvizExecutable.get(), "-T" + format.get(), - "-o" + destination.toString(), + "-o" + destination, sourceFile.get().toString() ); int returnCode = new ProcessBuilder(cmd).inheritIO().start().waitFor(); diff --git a/jpms-check/build.gradle b/jpms-check/build.gradle index f839fb0..843fd3b 100644 --- a/jpms-check/build.gradle +++ b/jpms-check/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'java-gradle-plugin' + id 'groovy-gradle-plugin' } version = "0.1" diff --git a/jpms-check/build.gradle.kts b/jpms-check/build.gradle.kts deleted file mode 100644 index 7eb6714..0000000 --- a/jpms-check/build.gradle.kts +++ /dev/null @@ -1,14 +0,0 @@ -plugins { - `maven-publish` - `groovy-gradle-plugin` - id("com.gradle.plugin-publish") -} - -gradlePlugin { - plugins { - create("JPMSCheckPlugin") { - id = "net.woggioni.plugins.jpms-check" - implementationClass = "net.woggioni.plugins.jpms.check.JPMSCheckPlugin" - } - } -} \ No newline at end of file diff --git a/jpms-check/src/main/groovy/net/woggioni/gradle/jpms/check/JPMSCheckPlugin.groovy b/jpms-check/src/main/groovy/net/woggioni/gradle/jpms/check/JPMSCheckPlugin.groovy index 6e95a31..598937d 100644 --- a/jpms-check/src/main/groovy/net/woggioni/gradle/jpms/check/JPMSCheckPlugin.groovy +++ b/jpms-check/src/main/groovy/net/woggioni/gradle/jpms/check/JPMSCheckPlugin.groovy @@ -69,10 +69,10 @@ class JPMSCheckPlugin implements Plugin { String automaticModuleName = jarFile.manifest?.with {it.mainAttributes.getValue("Automatic-Module-Name") } def moduleInfoEntry = jarFile.getJarEntry("module-info.class") new CheckResult( - dep: resolvedArtifact, - moduleInfo: moduleInfoEntry != null, - automaticModuleName: automaticModuleName, - multiReleaseJar: jarFile.isMultiRelease() + resolvedArtifact, + automaticModuleName, + jarFile.isMultiRelease(), + moduleInfoEntry != null ) } } @@ -140,6 +140,7 @@ class JPMSCheckPlugin implements Plugin { } } + @CompileStatic private createJsonReport(Stream checkResults, Writer writer) { def builder = new JsonBuilder() builder (checkResults.map { diff --git a/jpms-check/src/main/groovy/net/woggioni/plugins/jpms/check/JPMSCheckPlugin.groovy b/jpms-check/src/main/groovy/net/woggioni/plugins/jpms/check/JPMSCheckPlugin.groovy deleted file mode 100644 index eea9dfd..0000000 --- a/jpms-check/src/main/groovy/net/woggioni/plugins/jpms/check/JPMSCheckPlugin.groovy +++ /dev/null @@ -1,215 +0,0 @@ -package net.woggioni.plugins.jpms.check - -import groovy.json.JsonBuilder -import groovy.transform.Canonical -import groovy.transform.CompileStatic -import groovy.xml.MarkupBuilder -import org.gradle.api.GradleException -import org.gradle.api.Plugin -import org.gradle.api.Project -import org.gradle.api.artifacts.Configuration -import org.gradle.api.artifacts.result.ResolvedArtifactResult - -import java.nio.file.Files -import java.nio.file.Path -import java.nio.file.Paths -import java.util.jar.JarFile -import java.util.stream.Collectors -import java.util.zip.ZipFile -import java.util.stream.Stream - -class JPMSCheckPlugin implements Plugin { - - @Canonical - @CompileStatic - private class CheckResult { - ResolvedArtifactResult dep - String automaticModuleName - boolean multiReleaseJar - boolean moduleInfo - - boolean getJpmsFriendly() { - return automaticModuleName != null || moduleInfo - } - - @Override - boolean equals(Object other) { - if(other == null) { - return false - } else if(other.class == CheckResult.class) { - return dep?.id?.componentIdentifier == ((CheckResult) other).dep?.id?.componentIdentifier - } else { - return false - } - } - - @Override - int hashCode() { - return dep.id.componentIdentifier.hashCode() - } - } - - @CompileStatic - private Stream computeResults(Stream artifacts) { - return artifacts.filter { ResolvedArtifactResult res -> - res.file.exists() && res.file.name.endsWith(".jar") - }.map { resolvedArtifact -> - JarFile jarFile = new JarFile(resolvedArtifact.file).with { - if (it.isMultiRelease()) { - new JarFile( - resolvedArtifact.file, - false, - ZipFile.OPEN_READ, - Runtime.version() - ) - } else { - it - } - } - String automaticModuleName = jarFile.manifest?.with {it.mainAttributes.getValue("Automatic-Module-Name") } - def moduleInfoEntry = jarFile.getJarEntry("module-info.class") - new CheckResult( - dep: resolvedArtifact, - moduleInfo: moduleInfoEntry != null, - automaticModuleName: automaticModuleName, - multiReleaseJar: jarFile.isMultiRelease() - ) - } - } - - private void createHtmlReport(Project project, Stream checkResults, Writer writer) { - def builder = new MarkupBuilder(writer) - int friendly = 0 - int total = 0 - def results = checkResults.peek { CheckResult res -> - total += 1 - if(res.jpmsFriendly) friendly += 1 - }.collect(Collectors.toList()) - builder.html { - head { - meta name: "viewport", content: "width=device-width, initial-scale=1" - InputStream resourceStream = getClass().classLoader.getResourceAsStream('net/woggioni/plugins/jpms/check/github-markdown.css') - resourceStream.withReader { Reader reader -> - style reader.text - } - body { - article(class: 'markdown-body') { - h1 "Project ${project.group}:${project.name}:${project.version}", style: "text-align: center;" - div { - table { - thead { - tr { - th "JPMS friendly" - th "Not JPMS friendly", colspan: 2 - th "Total", colspan: 2 - } - } - tbody { - tr { - td friendly, style: "text-align: center;" - td total - friendly, style: "text-align: center;", colspan: 2 - td total, style: "text-align: center;", colspan: 2 - } - } - thead { - th "Name" - th "Multi-release jar" - th "Automatic-Module-Name" - th "Module descriptor" - th "JPMS friendly" - } - tbody { - results.forEach {res -> - String color = res.jpmsFriendly ? "#dfd" : "fdd" - tr(style: "background-color:$color;") { - td res.dep.id.displayName - td style: "text-align: center;", res.multiReleaseJar ? "✓" : "✕" - td style: "text-align: center;", res.automaticModuleName ?: "n/a" - td style: "text-align: center;", res.moduleInfo ? "✓" : "✕" - td style: "text-align: center;", res.jpmsFriendly ? "✓" : "✕" - } - total += 1 - if(res.jpmsFriendly) friendly += 1 - } - } - } - } - } - } - } - } - } - - private createJsonReport(Stream checkResults, Writer writer) { - def builder = new JsonBuilder() - builder (checkResults.map { - [ - name: it.dep.id.componentIdentifier.displayName, - automaticModuleName: it.automaticModuleName, - isMultiReleaseJar: it.multiReleaseJar, - hasModuleInfo: it.moduleInfo, - jpmsFriendly: it.jpmsFriendly - ] - }.collect(Collectors.toList())) - builder.writeTo(writer) - } - - @Override - @CompileStatic - void apply(Project project) { - project.tasks.register("jpms-check") {task -> - boolean recursive = project.properties["jpms-check.recursive"]?.with(Boolean.&parseBoolean) ?: false - String cfgName = project.properties["jpms-check.configurationName"] ?: "default" - String outputFormat = project.properties["jpms-check.outputFormat"] ?: "html" - Path outputFile = project.properties["jpms-check.outputFile"]?.with { - Paths.get(it as String) - } ?: with { - switch(outputFormat) { - case "html": - Paths.get(project.buildDir.path, "jpms-report.html") - break - case "json": - Paths.get(project.buildDir.path, "jpms-report.json") - break - default: - throw new IllegalArgumentException("Unsupported output format: $outputFormat") - } - } - task.doLast { - Stream projects = Stream.of(project) - if(recursive) { - projects = Stream.concat(projects, project.subprojects.stream()) - } - Set results = projects.flatMap { - Configuration requestedConfiguration = (project.configurations.find { Configuration cfg -> - cfg.canBeResolved && cfg.name == cfgName - } ?: { - def resolvableConfigurations = "[" + project.configurations - .grep { Configuration cfg -> cfg.canBeResolved } - .collect { "'${it.name}'" } - .join(",") + "]" - throw new GradleException("Configuration '$cfgName' doesn't exist or cannot be resolved, " + - "resolvable configurations in this project are " + resolvableConfigurations) - }) as Configuration - computeResults(requestedConfiguration.incoming.artifacts.artifacts.stream()) - }.collect(Collectors.toSet()) - Files.createDirectories(outputFile.parent) - Files.newBufferedWriter(outputFile).withWriter { - Stream resultStream = results.stream().sorted(Comparator.comparing { CheckResult res -> - res.dep.id.componentIdentifier.displayName - }) - switch(outputFormat) { - case "html": - createHtmlReport(project, resultStream, it) - break - case "json": - createJsonReport(resultStream, it) - break - default: - throw new IllegalArgumentException("Unsupported output format: $outputFormat") - } - } - } - } - } -}