diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..90b8cfe --- /dev/null +++ b/build.gradle @@ -0,0 +1,30 @@ +allprojects { + apply plugin: 'java-library' + repositories { + mavenLocal() + mavenCentral() + jcenter() + } + group = "net.woggioni.gradle" + version = 0.1 + + dependencies { + ['compileOnly', 'annotationProcessor', 'testCompileOnly', 'testAnnotationProcessor'].each { conf -> + add(conf, [group: "org.projectlombok", name: "lombok", version: project['version.lombok']]) + } + add("testImplementation", create(group: "org.junit.jupiter", name:"junit-jupiter-api", version: project["version.junitJupiter"])) + add("testRuntimeOnly", create(group: "org.junit.jupiter", name: "junit-jupiter-engine", version: project["version.junitJupiter"])) + add("testImplementation", gradleTestKit()) + } + + tasks.named("test", Test) { + useJUnitPlatform() + } +} + +wrapper { + gradleVersion = "6.8" + distributionType = Wrapper.DistributionType.ALL +} + + diff --git a/build.gradle.kts b/build.gradle.kts deleted file mode 100644 index 412c6b1..0000000 --- a/build.gradle.kts +++ /dev/null @@ -1,26 +0,0 @@ -allprojects { - apply() - repositories { - mavenLocal() - mavenCentral() - jcenter() - } - group = "net.woggioni.plugins" - version = 0.1 - - dependencies { - add("testImplementation", create(group="org.junit.jupiter", name="junit-jupiter-api", version=project["version.junitJupiter"])) - add("testRuntimeOnly", create(group="org.junit.jupiter", name="junit-jupiter-engine", version=project["version.junitJupiter"])) - add("testImplementation", gradleTestKit()) - } - - tasks.named("test") { - useJUnitPlatform() - } -} - -tasks.withType().configureEach { - gradleVersion = "6.7" - distributionType = Wrapper.DistributionType.ALL -} - diff --git a/dependency-export/README.md b/dependency-export/README.md index 32a061e..5515cfa 100644 --- a/dependency-export/README.md +++ b/dependency-export/README.md @@ -36,14 +36,12 @@ plugins { } ``` -You can also enable it globally on your machine just create a `~/.gradle/init.gradle` file with this content +You can also enable it globally on your machine, just create a `~/.gradle/init.gradle` file with this content ```groovy initscript { repositories { mavenLocal() - mavenCentral() - jcenter() } dependencies { classpath "net.woggioni.plugins:dependency-export:0.1" @@ -82,18 +80,15 @@ renderDependencies { and using Kotlin DSL ```kotlin -import net.woggioni.plugins.ExportDependenciesPluginExtension -import net.woggioni.plugins.RenderDependenciesPluginExtension - -configure { - configurationName = "default" - outputFile = "dependencies.dot" +tasks.named("exportDependencies") { + configurationName.set("compileClasspath") + outputFile.set(project.file("dependencies.dot")) } -configure { - format = "xlib" - outputFile = "renderedDependencies" - graphvizExecutable = "graphviz" +tasks.named("renderDependencies") { + format.set("xlib") + outputFile.set(project.file("renderedDependencies")) + graphvizExecutable.set("graphviz") } ``` @@ -102,24 +97,30 @@ or using its correspondent Gradle's [project properties](https://docs.gradle.org For example to override the output format of the `renderDependencies` task from the CLI: ```bash -gradle -PrenderDependencies.format=svg renderDependencies +gradle exportDependencies --configuration=compileClasspath renderDependencies --format=svg ``` ### Parameter description -- `exportDependencies.configurationName` will select the Gradle's configuration -(that word you put in the `dependencies` section of your build before `groupId:artifactId:versionId` tuple) - that will be represented in the graph -- `exportDependencies.outputFile` will specify the location of the generated `.dot` file -(note that if a relative path is provided, it will be interpreted as relative to the project's build directory) -- `renderDependencies.format` will specify the format of the file generated by Graphviz. +#### Attributes of task `net.woggioni.gradle.dependency.export.ExportDependencies` +- `configurationName` will select the Gradle's configuration + (that word you put in the `dependencies` section of your build before `groupId:artifactId:versionId` tuple) + that will be represented in the graph. It can also be specified from CLI using `--configuration`. +- `outputFile` will specify the location of the generated `.dot` file +(note that if a relative path is provided, it will be interpreted as relative to the project's build directory). + It can also be specified from CLI using `--output`. + +#### Attributes of task `net.woggioni.gradle.dependency.export.RenderDependencies` +- `format` will specify the format of the file generated by Graphviz. The default output format is `xlib` which, on a linux machine with a GUI, will open a window with an interactive (with zoom and pan) view of your dependencies (this is a special format that will not output any file). Otherwise you can choose between any other output format supported by Graphviz, refer to [its official documentation](https://graphviz.gitlab.io/_pages/doc/info/output.html) for more details. -- `renderDependencies.outputFile` will specify the location of the generated file (note that if a -relative path is provided, it will be interpreted as relative to the project's build directory) -- `renderDependencies.graphvizExecutable` will set the executable that will be launched to invoke + It can also be specified from CLI using `--format`. +- `outputFile` will specify the location of the generated file (note that if a +relative path is provided, it will be interpreted as relative to the project's build directory). + It can also be specified from CLI using `--output`. +- `graphvizExecutable` will set the executable that will be launched to invoke Graphviz so that, if you have it installed in an exotic location outside of your `PATH` or, for any reason, you renamed it in some way, you can configure it here. diff --git a/dependency-export/build.gradle.kts b/dependency-export/build.gradle.kts index cd79cdf..d696881 100644 --- a/dependency-export/build.gradle.kts +++ b/dependency-export/build.gradle.kts @@ -1,16 +1,10 @@ plugins { `java-gradle-plugin` `maven-publish` - id("org.jetbrains.kotlin.jvm") id("com.gradle.plugin-publish") } dependencies { - // Align versions of all Kotlin components - implementation(platform("org.jetbrains.kotlin:kotlin-bom")) - - // Use the Kotlin JDK 8 standard library. - implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") } java { @@ -27,19 +21,8 @@ arrayOf("apiElements", "runtimeElements").forEach { name : String -> gradlePlugin { - plugins { - create("DependencyExportPlugin") { - id = "net.woggioni.plugins.dependency-export" - implementationClass = "net.woggioni.plugins.dependency.export.DependencyExportPlugin" - } + val dependencyExportPlugin by plugins.creating { + id = "net.woggioni.gradle.dependency-export" + implementationClass = "net.woggioni.gradle.dependency.export.DependencyExportPlugin" } } - -tasks.withType().configureEach { - kotlinOptions { - languageVersion = "1.3" - apiVersion = "1.3" - jvmTarget = "1.8" - javaParameters = true // Useful for reflection. - } -} \ No newline at end of file diff --git a/dependency-export/sample/groovy/build.gradle b/dependency-export/sample/groovy/build.gradle index b3a9d89..6f69a01 100644 --- a/dependency-export/sample/groovy/build.gradle +++ b/dependency-export/sample/groovy/build.gradle @@ -4,13 +4,12 @@ buildscript { } dependencies { - classpath "net.woggioni.plugins:dependency-export:0.1" + classpath "net.woggioni.gradle:dependency-export:0.1" } } plugins { - id "org.jetbrains.kotlin.jvm" version "1.3.61" - id "net.woggioni.plugins.dependency-export" version "0.1" + id "net.woggioni.gradle.dependency-export" version "0.1" } repositories { @@ -18,9 +17,8 @@ repositories { mavenLocal() } - exportDependencies { - configurationName = 'runtime' + configurationName = 'runtimeCl' } renderDependencies { diff --git a/dependency-export/sample/kotlin/build.gradle.kts b/dependency-export/sample/kotlin/build.gradle.kts index 64f3c71..55355a7 100644 --- a/dependency-export/sample/kotlin/build.gradle.kts +++ b/dependency-export/sample/kotlin/build.gradle.kts @@ -5,16 +5,12 @@ buildscript { mavenCentral() } dependencies { - classpath("net.woggioni.plugins:dependency-export:0.1") + classpath("net.woggioni.gradle:dependency-export:0.1") } } -import net.woggioni.plugins.ExportDependenciesPluginExtension -import net.woggioni.plugins.RenderDependenciesPluginExtension - plugins { - kotlin("jvm") version "1.3.71" - id("net.woggioni.plugins.dependency-export") version "0.1" + id("net.woggioni.gradle.dependency-export") version "0.1" } repositories { diff --git a/dependency-export/src/main/java/net/woggioni/gradle/dependency/export/DependencyExportPlugin.java b/dependency-export/src/main/java/net/woggioni/gradle/dependency/export/DependencyExportPlugin.java new file mode 100644 index 0000000..848ec75 --- /dev/null +++ b/dependency-export/src/main/java/net/woggioni/gradle/dependency/export/DependencyExportPlugin.java @@ -0,0 +1,32 @@ +package net.woggioni.gradle.dependency.export; + +import org.gradle.api.Action; +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.plugins.JavaBasePlugin; +import org.gradle.api.plugins.ObjectConfigurationAction; +import org.gradle.api.provider.Provider; + +public class DependencyExportPlugin implements Plugin { + @Override + public void apply(Project project) { + project.apply(new Action() { + @Override + public void execute(ObjectConfigurationAction objectConfigurationAction) { + objectConfigurationAction.plugin(JavaBasePlugin.class); + } + }); + Provider exportDependenciesTask = + project.getTasks().register("exportDependencies", ExportDependencies.class); + Provider renderDependenciesTask = + project.getTasks().register("renderDependencies", RenderDependencies.class, new Action() { + @Override + public void execute(RenderDependencies renderDependencies) { + renderDependencies.setExportTask(exportDependenciesTask); + } + }); + +// project.getExtensions().getExtraProperties().set(ExportDependencies.class.getSimpleName(), ExportDependencies.class); + project.getExtensions().getExtraProperties().set(RenderDependencies.class.getSimpleName(), RenderDependencies.class); + } +} 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 new file mode 100644 index 0000000..f4acc4c --- /dev/null +++ b/dependency-export/src/main/java/net/woggioni/gradle/dependency/export/ExportDependencies.java @@ -0,0 +1,204 @@ +package net.woggioni.gradle.dependency.export; + +import lombok.*; +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.component.ComponentIdentifier; +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.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.InputFiles; +import org.gradle.api.tasks.OutputFile; +import org.gradle.api.tasks.TaskAction; +import org.gradle.api.tasks.options.Option; + +import javax.inject.Inject; +import java.io.File; +import java.io.Writer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.stream.Collector; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class ExportDependencies extends DefaultTask { + + @Getter + @Setter + @Input + private Property configurationName; + + @Getter + @Setter + @OutputFile + private Property outputFile; + + @Getter + @Setter + @Input + private Property showArtifacts; + + private final JavaPluginConvention javaPluginConvention; + + + @InputFiles + public Provider getConfigurationFiles() { + return configurationName.flatMap(name -> getProject().getConfigurations().named(name)); + } + + @Option(option = "configuration", description = "Set the configuration name") + public void setConfiguration(String configurationName) { + this.configurationName.set(configurationName); + } + + @Option(option = "output", description = "Set the output file name") + public void setOutput(String outputFile) { + this.outputFile.set(getProject().file(outputFile)); + } + + @Option(option = "showArtifacts", description = "Show artifacts") + public void setArtifacts(boolean value) { + showArtifacts.set(value); + } + + @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"))); + showArtifacts = objects.property(Boolean.class).convention(false); + } + + private static String quote(String s) { + return "\"" + s + "\""; + } + + @TaskAction + @SneakyThrows + void run() { + Map map = new HashMap<>(); + + Configuration requestedConfiguration = Optional.ofNullable(getProject().getConfigurations().named(configurationName.get()).getOrNull()).orElseThrow(() -> { + String resolvableConfigurations = '[' + getProject().getConfigurations().stream() + .filter(Configuration::isCanBeResolved) + .map(it -> '\'' + it.getName() + '\'') + .collect(Collectors.joining(", ")) + ']'; + throw new GradleException(String.format("Configuration '%s' doesn't exist or cannot be resolved, " + + "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(); + Files.createDirectories(destination.getParent()); + try(Writer writer = Files.newBufferedWriter(destination)) { + writer.write("digraph G {"); + writer.write('\n'); + writer.write(" #rankdir=\"LR\";"); + writer.write('\n'); + Optional>> artifactMap = Optional.empty(); + if(showArtifacts.get()) { + artifactMap = Optional.of(requestedConfiguration.getResolvedConfiguration().getResolvedArtifacts().stream().map(it -> + new AbstractMap.SimpleEntry<>(it.getId().getComponentIdentifier(), it) + ).collect(Collectors.groupingBy(Map.Entry::getKey, + Collector.of(ArrayList::new, + (list, entry) -> list.add(entry.getValue()), + (l1, l2) -> { l1.addAll(l2); return l1; })))); + } + final int[] sequence = new int[1]; + for (ResolvedComponentResult component : resolutionResult.getAllComponents()) { + map.computeIfAbsent(component, it -> sequence[0]++); + ComponentIdentifier id = component.getId(); + Optional> artifacts = artifactMap + .flatMap(it -> Optional.ofNullable(it.get(id))); + String componentName = id.getDisplayName(); + String label = artifacts.map(it -> { + String rows = it.stream().map(resolvedArtifact -> { + String artifactDescription = Stream.of( + new AbstractMap.SimpleEntry<>("type", resolvedArtifact.getType()), + new AbstractMap.SimpleEntry<>("classifier", resolvedArtifact.getClassifier()), + new AbstractMap.SimpleEntry<>("extension", + !Objects.equals(resolvedArtifact.getExtension(), resolvedArtifact.getType()) ? + resolvedArtifact.getExtension() : null) + ).map(entry -> { + if (entry.getValue() == null || entry.getValue().isEmpty()) return null; + else return entry.getKey() + ": " + entry.getValue(); + }).collect(Collectors.joining(", ")); + return "" + artifactDescription + ""; + }).collect(Collectors.joining()); + return "<" + + " " + + " " + + " " + + " " + rows + + "
" + id.getDisplayName() + "
>"; + }).orElse(quote(componentName)); + + String shape; + String color; + if(id instanceof ProjectComponentIdentifier) { + shape = artifacts.isEmpty() ? "box" : "none"; + color = "#88ff88"; + } else if(id instanceof ModuleComponentIdentifier) { + shape = artifacts.isEmpty() ? "oval" : "none"; + color = "#ffff88"; + } else { + 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)) + ).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(", ")) + + "];"); + writer.write('\n'); + } + + @EqualsAndHashCode + @RequiredArgsConstructor + class Link { + final ComponentIdentifier id1; + final ComponentIdentifier id2; + } + Set linkCache = new HashSet<>(); + for (ResolvedComponentResult component : resolutionResult.getAllComponents()) { + for(DependencyResult dependency : component.getDependencies()) { + if(dependency instanceof ResolvedDependencyResult) { + ResolvedComponentResult child = + ((ResolvedDependencyResult) dependency).getSelected(); + if(linkCache.add(new Link(component.getId(), child.getId()))) { + writer.write(" node_" + map.get(component) + " -> node_" + map.get(child) + ";"); + writer.write('\n'); + } + } else if(dependency instanceof UnresolvedDependencyResult) { + throw ((UnresolvedDependencyResult) dependency).getFailure(); + } else { + throw new IllegalArgumentException(dependency.getClass().getName()); + } + } + } + writer.write('}'); + 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 new file mode 100644 index 0000000..e0ca4f2 --- /dev/null +++ b/dependency-export/src/main/java/net/woggioni/gradle/dependency/export/RenderDependencies.java @@ -0,0 +1,87 @@ +package net.woggioni.gradle.dependency.export; + +import lombok.Getter; +import lombok.Setter; +import lombok.SneakyThrows; +import org.gradle.api.DefaultTask; +import org.gradle.api.GradleException; +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.InputFile; +import org.gradle.api.tasks.TaskAction; +import org.gradle.api.tasks.options.Option; + +import javax.inject.Inject; +import java.io.File; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; + +public class RenderDependencies extends DefaultTask { + + @Getter + @InputFile + private Provider sourceFile; + + @Getter + @Setter + private Property format; + + @Getter + @Setter + private Property graphvizExecutable; + + @Getter + @Setter + private Property outputFile; + + private final JavaPluginConvention javaPluginConvention; + + @Option(option = "output", description = "Set the output file name") + public void setOutputCli(String outputFile) { + this.outputFile.set(getProject().file(outputFile)); + } + + @Option(option = "format", description = "Set output format (see https://graphviz.org/doc/info/output.html)") + public void setFormatCli(String format) { + this.format.set(format); + } + + public void setExportTask(Provider taskProvider) { + sourceFile = taskProvider.flatMap(ExportDependencies::getOutputFile); + } + + @Inject + public RenderDependencies(ObjectFactory objects) { + sourceFile = objects.property(File.class); + 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")); + } + + @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(); + List cmd = Arrays.asList( + graphvizExecutable.get(), + "-T" + format.get(), + "-o" + destination.toString(), + sourceFile.get().toString() + ); + int returnCode = new ProcessBuilder(cmd).inheritIO().start().waitFor(); + if (returnCode != 0) { + throw new GradleException("Error invoking graphviz"); + } + } +} diff --git a/dependency-export/src/main/kotlin/net/woggioni/plugins/dependency/export/DependencyExportPlugin.kt b/dependency-export/src/main/kotlin/net/woggioni/plugins/dependency/export/DependencyExportPlugin.kt deleted file mode 100644 index 78c3f80..0000000 --- a/dependency-export/src/main/kotlin/net/woggioni/plugins/dependency/export/DependencyExportPlugin.kt +++ /dev/null @@ -1,220 +0,0 @@ -package net.woggioni.plugins.dependency.export - -import org.gradle.api.GradleException -import org.gradle.api.Plugin -import org.gradle.api.Project -import org.gradle.api.artifacts.ResolvedArtifact -import org.gradle.api.artifacts.component.ComponentIdentifier -import org.gradle.api.artifacts.component.ModuleComponentIdentifier -import org.gradle.api.artifacts.component.ProjectComponentIdentifier -import org.gradle.api.artifacts.result.ResolvedComponentResult -import org.gradle.api.artifacts.result.ResolvedDependencyResult -import org.gradle.api.artifacts.result.UnresolvedDependencyResult -import java.io.BufferedWriter -import java.io.OutputStreamWriter -import java.nio.file.Files -import java.nio.file.Path -import java.nio.file.Paths -import kotlin.reflect.KMutableProperty0 - - -open class ExportDependenciesPluginExtension(project: Project) { - var configurationName: String = "default" - var outputFile = project.buildDir.toPath().resolve("dependencies.dot") - var showArtifacts = false -} - -open class RenderDependenciesPluginExtension(project: Project) { - var format: String = "xlib" - var outputFile = project.buildDir.toPath().resolve("renderedDependencies") - var graphvizExecutable: String = "dot" -} - -private class Overrider(private val properties: Map, private val prefix: String) { - inline fun identity(arg: T): T { - return arg - } - - fun overrideProperty(property: KMutableProperty0) { - overrideProperty(property, ::identity) - } - - inline fun overrideProperty( - property: KMutableProperty0, - valueFactory: (String) -> V) { - val propertyKey = prefix + "." + property.name - val propertyValue = properties[propertyKey] as String? - if (propertyValue != null) { - property.set(valueFactory(propertyValue)) - } - } -} - -object DependencyExporter { - - private fun quote(s : String) = "\"" + s + "\"" - - fun exportDot(project: Project, ext: ExportDependenciesPluginExtension) { - val overrider = Overrider(project.properties, "exportDependencies") - overrider.overrideProperty(ext::configurationName) - overrider.overrideProperty(ext::outputFile) { value -> Paths.get(value) } - overrider.overrideProperty(ext::showArtifacts) { value -> value.toBoolean() } - - var sequence = 0 - val map = HashMap() - - val requestedConfiguration = project.configurations.singleOrNull { - it.name == ext.configurationName - }?.takeIf { it.isCanBeResolved } ?: let { - val resolvableConfigurations = "[" + project.configurations.asSequence() - .filter { it.isCanBeResolved } - .map { "'${it.name}'" } - .joinToString(",") + "]" - throw GradleException("Configuration '${ext.configurationName}' doesn't exist or cannot be resolved, " + - "resolvable configurations in this project are " + resolvableConfigurations) - } - - val resolutionResult = requestedConfiguration.incoming.resolutionResult - if (!ext.outputFile.isAbsolute) { - ext.outputFile = project.buildDir.toPath().resolve(ext.outputFile) - } - Files.createDirectories(ext.outputFile.parent) - BufferedWriter( - OutputStreamWriter( - Files.newOutputStream(ext.outputFile))).use { writer -> - writer.write("digraph G {") - writer.newLine() - writer.write(" #rankdir=\"LR\";") - writer.newLine() - - val artifactMap = if(ext.showArtifacts) { - requestedConfiguration.resolvedConfiguration.resolvedArtifacts.asSequence().map { - it.id.componentIdentifier to it - }.groupBy( - Pair::first, - Pair::second - ) - } else { - null - } - - for (component in resolutionResult.allComponents) { - map.computeIfAbsent(component) { - sequence++ - } - val artifacts = artifactMap?.let { it[component.id] } - val (shape, color) = when (component.id) { - is ProjectComponentIdentifier -> (artifacts?.let { "none" } ?: "box") to "#88ff88" - is ModuleComponentIdentifier -> (artifacts?.let { "none" } ?: "oval") to "#ffff88" - else -> throw NotImplementedError("${component.id::class}") - } - - - val componentName = component.id.displayName - val label = artifacts?.let { - val rows = it.asSequence().map { resolvedArtifact -> - val artifactDescription = sequenceOf( - "type" to resolvedArtifact.type, - "classifier" to resolvedArtifact.classifier, - "extension" to resolvedArtifact.extension.takeIf { - resolvedArtifact.extension != resolvedArtifact.type - } - ).mapNotNull { pair -> - when { - pair.second == null || pair.second.isEmpty() -> null - else -> "${pair.first}: ${pair.second}" - } - }.joinToString(", ") - "$artifactDescription" - }.joinToString() - """ - < - - - - $rows -
${component.id.displayName}
> - """.trimIndent() - } ?: quote(componentName) - - val attrs = sequenceOf( - "label" to label, - "shape" to quote(shape), - "style" to quote("filled"), - artifacts?.let { "margin" to quote(0.toString()) }, - "fillcolor" to quote(color) - ).mapNotNull { it }.toMap() - writer.write(" node_${map[component]} [" + - attrs.entries - .asSequence() - .map { "${it.key}=${it.value}" }.joinToString(", ") + - "];") - writer.newLine() - } - - for (component in resolutionResult.allComponents) { - component.dependencies.map { dependency -> - when (dependency) { - is ResolvedDependencyResult -> dependency - is UnresolvedDependencyResult -> { - throw dependency.failure - } - else -> { - throw NotImplementedError("${dependency::class}") - } - } - }.map(ResolvedDependencyResult::getSelected).forEach { child -> - writer.write(" node_${map[component]} -> node_${map[child]};") - writer.newLine() - } - } - writer.write("}") - writer.newLine() - } - } -} - -object DependencyRenderer { - fun render(project: Project, ext: RenderDependenciesPluginExtension, sourceFile: Path) { - val overrider = Overrider(project.properties, "renderDependencies") - overrider.overrideProperty(ext::format) - overrider.overrideProperty(ext::graphvizExecutable) - overrider.overrideProperty(ext::outputFile) { value -> Paths.get(value) } - - if (!ext.outputFile.isAbsolute) { - ext.outputFile = project.buildDir.toPath().resolve(ext.outputFile) - } - val cmd: List = listOf( - ext.graphvizExecutable, - "-T${ext.format}", - "-o${ext.outputFile}", - sourceFile.toString() - - ) - val returnCode = ProcessBuilder(cmd).inheritIO().start().waitFor() - if (returnCode != 0) { - throw GradleException("Error invoking graphviz") - } - } -} - -class DependencyExportPlugin : Plugin { - override fun apply(project: Project) { - val dependencyExportExtension = ExportDependenciesPluginExtension(project) - project.extensions.add(ExportDependenciesPluginExtension::class.java, "exportDependencies", dependencyExportExtension) - val exportDependenciesTask = project.tasks.register("exportDependencies") { - it.doLast { - DependencyExporter.exportDot(project, dependencyExportExtension) - } - }.get() - - val renderDependenciesPluginExtension = RenderDependenciesPluginExtension(project) - project.extensions.add(RenderDependenciesPluginExtension::class.java, "renderDependencies", renderDependenciesPluginExtension) - project.tasks.register("renderDependencies") { - it.dependsOn(exportDependenciesTask) - it.doLast { - DependencyRenderer.render(project, renderDependenciesPluginExtension, dependencyExportExtension.outputFile) - } - }.get() - } -} diff --git a/dependency-export/src/test/java/net/woggioni/gradle/dependency/export/DependencyExportPluginTest.java b/dependency-export/src/test/java/net/woggioni/gradle/dependency/export/DependencyExportPluginTest.java new file mode 100644 index 0000000..a06f25a --- /dev/null +++ b/dependency-export/src/test/java/net/woggioni/gradle/dependency/export/DependencyExportPluginTest.java @@ -0,0 +1,93 @@ +package net.woggioni.gradle.dependency.export; + +import lombok.SneakyThrows; +import org.gradle.testkit.runner.GradleRunner; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.Objects; +import java.util.Optional; +import java.util.function.BiFunction; +import java.util.stream.Stream; + +public class DependencyExportPluginTest { + + private static InputStream resourceFromClass(Class cls, String resourceName) { + return cls.getResourceAsStream(resourceName); + } + + private static InputStream resourceFromClassLoader(Class cls, String resourceName) { + return cls.getClassLoader().getResourceAsStream(resourceName); + } + + @SneakyThrows + private static void installResource(Class cls, String resourceName, Path destination) { + Path outputFile; + Path realDestination; + if (Files.isSymbolicLink(destination)) { + realDestination = destination.toRealPath(); + } else { + realDestination = destination; + } + if(!Files.exists(realDestination)) { + Files.createDirectories(realDestination.getParent()); + outputFile = realDestination; + } else if(Files.isDirectory(realDestination)) { + outputFile = realDestination.resolve(resourceName.substring(1 + resourceName.lastIndexOf('/'))); + } else if(Files.isRegularFile(realDestination)) { + outputFile = realDestination; + } else throw new IllegalStateException("Path '${realDestination}' is neither a file nor a directory"); + Optional inputStreamOptional = Stream., String, InputStream>>of( + DependencyExportPluginTest::resourceFromClass, + DependencyExportPluginTest::resourceFromClassLoader + ).map(f -> f.apply(cls, resourceName)).filter(Objects::nonNull).findFirst(); + try(InputStream inputStream = inputStreamOptional.orElseThrow(() -> new FileNotFoundException(resourceName))){ + Files.copy(inputStream, outputFile, StandardCopyOption.REPLACE_EXISTING); + } + } + + @TempDir + public Path testGradleHomeDir; + + @TempDir + public Path testProjectDir; + + public Path buildFile; + + @BeforeEach + public void setup() { + buildFile = testProjectDir.resolve("build.gradle.kts"); + } + + public GradleRunner getStandardGradleRunnerFor(String taskName) { + return GradleRunner.create() + .withDebug(true) + .withProjectDir(testProjectDir.toFile()) + .withArguments(taskName, "-s", "--info", "-g", testGradleHomeDir.toString()) + .withPluginClasspath(); + } + + @Test + public void testKotlin() { + installResource(getClass(), "build.gradle.kts", testProjectDir); + installResource(getClass(),"settings.gradle.kts", testProjectDir); + installResource(getClass(),"gradle.properties", testProjectDir); + GradleRunner runner = getStandardGradleRunnerFor("exportDependencies"); + runner.build(); + } + + @Test + public void testGroovy() { + installResource(getClass(),"build.gradle", testProjectDir); + installResource(getClass(),"settings.gradle.kts", testProjectDir); + installResource(getClass(),"gradle.properties", testProjectDir); + GradleRunner runner = getStandardGradleRunnerFor("exportDependencies"); + runner.build(); + } +} diff --git a/dependency-export/src/test/kotlin/net/woggioni/plugins/dependency/export/DependencyExportPluginTest.kt b/dependency-export/src/test/kotlin/net/woggioni/plugins/dependency/export/DependencyExportPluginTest.kt deleted file mode 100644 index 98ceea2..0000000 --- a/dependency-export/src/test/kotlin/net/woggioni/plugins/dependency/export/DependencyExportPluginTest.kt +++ /dev/null @@ -1,52 +0,0 @@ -/* - * This Kotlin source file was generated by the Gradle 'init' task. - */ -package net.woggioni.plugins.dependency.export - -import org.gradle.testkit.runner.GradleRunner -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.io.TempDir -import java.nio.file.Path - - -class DependencyExportPluginTest { - - @TempDir - lateinit var testGradleHomeDir : Path - - @TempDir - lateinit var testProjectDir : Path - lateinit var buildFile : Path - - @BeforeEach - fun setup() { - buildFile = testProjectDir.resolve("build.gradle.kts") - } - - fun getStandardGradleRunnerFor(taskName: String): GradleRunner { - return GradleRunner.create() - .withDebug(true) - .withProjectDir(testProjectDir.toFile()) - .withArguments(taskName, "-s", "--info", "-g", testGradleHomeDir.toString()) - .withPluginClasspath() - } - - @Test - fun testKotlin() { - installResource("build.gradle.kts", testProjectDir) - installResource("settings.gradle.kts", testProjectDir) - installResource("gradle.properties", testProjectDir) - val runner = getStandardGradleRunnerFor("exportDependencies") - runner.build() - } - - @Test - fun testGroovy() { - installResource("build.gradle", testProjectDir) - installResource("settings.gradle.kts", testProjectDir) - installResource("gradle.properties", testProjectDir) - val runner = getStandardGradleRunnerFor("exportDependencies") - runner.build() - } -} diff --git a/dependency-export/src/test/kotlin/net/woggioni/plugins/dependency/export/Powerup.kt b/dependency-export/src/test/kotlin/net/woggioni/plugins/dependency/export/Powerup.kt deleted file mode 100644 index b9a3c4b..0000000 --- a/dependency-export/src/test/kotlin/net/woggioni/plugins/dependency/export/Powerup.kt +++ /dev/null @@ -1,33 +0,0 @@ -package net.woggioni.plugins.dependency.export - -import java.io.FileNotFoundException -import java.io.IOException -import java.nio.file.Files -import java.nio.file.Path -import java.nio.file.StandardCopyOption - - -@Throws(IOException::class) -fun Any.installResource(resourceName: String, destination: Path) { - val outputFile = run { - val realDestination = if (Files.isSymbolicLink(destination)) { - destination.toRealPath() - } else { - destination - } - when { - !Files.exists(realDestination) -> { - Files.createDirectories(realDestination.parent) - realDestination - } - Files.isDirectory(realDestination) -> - realDestination.resolve(resourceName.substring(1 + resourceName.lastIndexOf('/'))) - Files.isRegularFile(realDestination) -> realDestination - else -> throw IllegalStateException("Path '${realDestination}' is neither a file nor a directory") - } - } - (javaClass.getResourceAsStream(resourceName) - ?: javaClass.classLoader.getResourceAsStream(resourceName))?.use { inputStream -> - Files.copy(inputStream, outputFile, StandardCopyOption.REPLACE_EXISTING) - } ?: throw FileNotFoundException(resourceName) -} diff --git a/dependency-export/src/test/resources/net/woggioni/gradle/dependency/export/build.gradle b/dependency-export/src/test/resources/net/woggioni/gradle/dependency/export/build.gradle new file mode 100644 index 0000000..51fc278 --- /dev/null +++ b/dependency-export/src/test/resources/net/woggioni/gradle/dependency/export/build.gradle @@ -0,0 +1,18 @@ +plugins { + id 'java-library' + id "net.woggioni.gradle.dependency-export" +} + +exportDependencies { + configurationName = 'compileClasspath' +} + +repositories { + jcenter() + mavenLocal() +} + +dependencies { + runtimeOnly("org.hibernate:hibernate-core:5.4.13.Final") +} + diff --git a/dependency-export/src/test/resources/net/woggioni/gradle/dependency/export/build.gradle.kts b/dependency-export/src/test/resources/net/woggioni/gradle/dependency/export/build.gradle.kts new file mode 100644 index 0000000..c516eab --- /dev/null +++ b/dependency-export/src/test/resources/net/woggioni/gradle/dependency/export/build.gradle.kts @@ -0,0 +1,17 @@ +plugins { + id("java-library") + id("net.woggioni.gradle.dependency-export") +} + +repositories { + jcenter() + mavenLocal() +} + +dependencies { + runtimeOnly("org.hibernate:hibernate-core:5.4.13.Final") +} + +tasks.named("exportDependencies") { + configurationName.set("compileClasspath") +} diff --git a/dependency-export/src/test/resources/net/woggioni/plugins/dependency/export/build.gradle b/dependency-export/src/test/resources/net/woggioni/plugins/dependency/export/build.gradle deleted file mode 100644 index 477a53d..0000000 --- a/dependency-export/src/test/resources/net/woggioni/plugins/dependency/export/build.gradle +++ /dev/null @@ -1,17 +0,0 @@ -plugins { - id "org.jetbrains.kotlin.jvm" version "1.3.61" - id "net.woggioni.plugins.dependency-export" -} - -exportDependencies { - configurationName = 'runtime' -} - -repositories { - jcenter() - mavenLocal() -} - -dependencies { - runtime("org.hibernate:hibernate-core:5.4.13.Final") -} \ No newline at end of file diff --git a/dependency-export/src/test/resources/net/woggioni/plugins/dependency/export/build.gradle.kts b/dependency-export/src/test/resources/net/woggioni/plugins/dependency/export/build.gradle.kts deleted file mode 100644 index 71b1bbb..0000000 --- a/dependency-export/src/test/resources/net/woggioni/plugins/dependency/export/build.gradle.kts +++ /dev/null @@ -1,14 +0,0 @@ -plugins { - kotlin("jvm") version "1.3.71" - id("net.woggioni.plugins.dependency-export") -} - -repositories { - jcenter() - mavenLocal() -} - -dependencies { - runtime("org.hibernate:hibernate-core:5.4.13.Final") -} - diff --git a/gradle.properties b/gradle.properties index 303ffd5..95a9653 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,6 @@ +version.kotlin=1.3.72 +version.gradlePublish=0.10.1 +version.lombok=1.18.16 +version.slf4j=1.7.30 version.junitJupiter=5.7.0 version.junitPlatform=1.7.0 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 62d4c05..e708b1c 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index bca17f3..80cf08e 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.6-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index fbd7c51..4f906e0 100755 --- a/gradlew +++ b/gradlew @@ -130,7 +130,7 @@ fi if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath diff --git a/gradlew.bat b/gradlew.bat index 5093609..107acd3 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -54,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -64,21 +64,6 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line @@ -86,7 +71,7 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..f589015 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,15 @@ +pluginManagement { + repositories { + gradlePluginPortal() + } + + plugins { + id 'org.jetbrains.kotlin.jvm' version this['version.kotlin'] + id 'com.gradle.plugin-publish' version this['version.gradlePublish'] + } +} + +rootProject.name = "my-gradle-plugins" +include("dependency-export") +include("jpms-check") +include("multi-release-jar") diff --git a/settings.gradle.kts b/settings.gradle.kts deleted file mode 100644 index c90f1b5..0000000 --- a/settings.gradle.kts +++ /dev/null @@ -1,11 +0,0 @@ -pluginManagement { - plugins { - id("org.jetbrains.kotlin.jvm") version "1.3.72" apply false - id("com.gradle.plugin-publish") version "0.10.1" apply false - } -} - -rootProject.name = "my-gradle-plugins" -include("dependency-export") -include("jpms-check") -include("multi-release-jar")