fixed dependency-export plugin for Gradle 7.0
This commit is contained in:
@@ -23,6 +23,11 @@ subprojects {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
publishing {
|
||||
repositories {
|
||||
maven {
|
||||
|
@@ -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<String> configurationName;
|
||||
@Getter(onMethod_ = { @Input })
|
||||
private final Property<String> configurationName;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Internal
|
||||
private final RegularFileProperty outputFile;
|
||||
|
||||
@Input
|
||||
public String getDestination() {
|
||||
return outputFile.map(RegularFile::getAsFile).map(File::getAbsolutePath).get();
|
||||
}
|
||||
|
||||
@OutputFile
|
||||
private Property<File> outputFile;
|
||||
public Provider<File> getResult() {
|
||||
return outputFile.map(RegularFile::getAsFile);
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Input
|
||||
private Property<Boolean> showArtifacts;
|
||||
@Getter(onMethod_ = { @Input })
|
||||
private final Property<Boolean> showArtifacts;
|
||||
|
||||
private final JavaPluginConvention javaPluginConvention;
|
||||
|
||||
|
||||
@InputFiles
|
||||
public Provider<FileCollection> 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<File> 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<File> 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<ResolvedComponentResult, Integer> 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<ResolvedComponentResult, Integer> 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<String, String> 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');
|
||||
}
|
||||
|
||||
|
@@ -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<File> sourceFile;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private Property<String> format;
|
||||
@Getter(onMethod_ = { @Input})
|
||||
private final Property<String> format;
|
||||
|
||||
@Getter(onMethod_ = { @Input })
|
||||
private final Property<String> graphvizExecutable;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private Property<String> graphvizExecutable;
|
||||
@Internal
|
||||
private final RegularFileProperty outputFile;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private Property<File> outputFile;
|
||||
@Input
|
||||
@Optional
|
||||
public String getDestination() {
|
||||
return outputFile.map(RegularFile::getAsFile).map(File::getAbsolutePath).getOrNull();
|
||||
}
|
||||
|
||||
@OutputFile
|
||||
public Provider<File> 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<File> 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<ExportDependencies> 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<File> 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<String> cmd = Arrays.asList(
|
||||
graphvizExecutable.get(),
|
||||
"-T" + format.get(),
|
||||
"-o" + destination.toString(),
|
||||
"-o" + destination,
|
||||
sourceFile.get().toString()
|
||||
);
|
||||
int returnCode = new ProcessBuilder(cmd).inheritIO().start().waitFor();
|
||||
|
@@ -1,5 +1,5 @@
|
||||
plugins {
|
||||
id 'java-gradle-plugin'
|
||||
id 'groovy-gradle-plugin'
|
||||
}
|
||||
|
||||
version = "0.1"
|
||||
|
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
@@ -69,10 +69,10 @@ class JPMSCheckPlugin implements Plugin<Project> {
|
||||
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<Project> {
|
||||
}
|
||||
}
|
||||
|
||||
@CompileStatic
|
||||
private createJsonReport(Stream<CheckResult> checkResults, Writer writer) {
|
||||
def builder = new JsonBuilder()
|
||||
builder (checkResults.map {
|
||||
|
@@ -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<Project> {
|
||||
|
||||
@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<CheckResult> computeResults(Stream<ResolvedArtifactResult> artifacts) {
|
||||
return artifacts.filter { ResolvedArtifactResult res ->
|
||||
res.file.exists() && res.file.name.endsWith(".jar")
|
||||
}.<CheckResult>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<CheckResult> 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<CheckResult> 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<Project> projects = Stream.of(project)
|
||||
if(recursive) {
|
||||
projects = Stream.concat(projects, project.subprojects.stream())
|
||||
}
|
||||
Set<CheckResult> results = projects.flatMap {
|
||||
Configuration requestedConfiguration = (project.configurations.<Configuration>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<CheckResult> resultStream = results.stream().sorted(Comparator.<CheckResult, String>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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user