Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
Version 0.9.0 *(In development)*
--------------------------------

- Add configurable node types to the project diagrams, with custom colors/shapes. [\#163](https://github.com/vanniktech/gradle-dependency-graph-generator-plugin/pull/266) ([jonapoul](https://github.com/jonapoul))

Version 0.8.0 *(2022-06-21)*
----------------------------

Expand Down Expand Up @@ -99,4 +101,4 @@ Version 0.2.0 *(2018-03-07)*
Version 0.1.0 *(2018-03-04)*
----------------------------

- Initial release
- Initial release
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ dependencies {

testImplementation 'junit:junit:4.13.2'
testImplementation "com.android.tools.build:gradle:8.10.0"
testImplementation "org.jetbrains.kotlin.native.cocoapods:org.jetbrains.kotlin.native.cocoapods.gradle.plugin:$kotlinVersion"
testImplementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"

// https://github.com/gradle/gradle/issues/16774#issuecomment-893493869
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ internal class DependencyGraphGenerator(
.add(Label.of(dependency.getDisplayName()))
.add(Shape.RECTANGLE)

val type = generator.dependencyMapper.invoke(dependency)
type?.color?.let(node::add)
type?.shape?.let(node::add)

val mutated = generator.dependencyNode.invoke(node, dependency)
nodes[identifier] = mutated
graph.add(mutated)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ open class DependencyGraphGeneratorExtension(project: Project) {
@get:Nested var graph: (MutableGraph) -> MutableGraph = { it },
/** Allows you to configure the [Graphviz] instance. */
@get:Nested var graphviz: (Graphviz) -> Graphviz = { it },
/** Determines the color/shape of the project node on the output diagram */
@get:Nested var dependencyMapper: DependencyMapper = { null },
) {
/** Gradle task name that is associated with this generator. */
@get:Internal val gradleTaskName = "generateDependencyGraph${
Expand Down Expand Up @@ -138,6 +140,8 @@ open class DependencyGraphGeneratorExtension(project: Project) {
@get:Nested var graph: (MutableGraph) -> MutableGraph = { it },
/** Allows you to configure the [Graphviz] instance. */
@get:Nested var graphviz: (Graphviz) -> Graphviz = { it },
/** Determines the color/shape of the project node on the output diagram */
@get:Nested var projectMapper: ProjectMapper = DefaultProjectMapper,
) {
/** Gradle task name that is associated with this generator. */
@get:Internal val gradleTaskName = "generateProjectDependencyGraph${
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.vanniktech.dependency.graph.generator

import guru.nidi.graphviz.attribute.Color
import guru.nidi.graphviz.attribute.Shape
import org.gradle.api.Project
import org.gradle.api.artifacts.ResolvedDependency

interface NodeType {
val color: Color?
val shape: Shape?
}

data class BasicNodeType(
override val color: Color?,
override val shape: Shape?,
) : NodeType {
constructor(rgbHex: String, shape: Shape?) : this(Color.rgb(rgbHex).fill(), shape)
constructor(rgbInt: Int, shape: Shape?) : this(Color.rgb(rgbInt).fill(), shape)
}

typealias ProjectMapper = (Project) -> NodeType?

typealias DependencyMapper = (ResolvedDependency) -> NodeType?

/**
* Use this to apply a semi-random fill color onto each node, where the same color is used on every dependency of the
* same group. E.g. "a.b:c" will have the same color as "a.b:d", but different to "e.f:c"
*/
val TintDependencyByGroup: DependencyMapper = { dependency ->
BasicNodeType(dependency.moduleGroup.hashCode(), shape = null)
}

internal enum class DefaultProjectType(
override val color: Color,
override val shape: Shape? = null,
) : NodeType {
ANDROID(color = Color.rgb("#66BB6A").fill()), // green
JVM(color = Color.rgb("#FF7043").fill()), // orange
IOS(color = Color.rgb("#42A5F5").fill()), // blue
JS(color = Color.rgb("#FFCA28").fill()), // yellow
MULTIPLATFORM(color = Color.rgb("#A280FF").fill()), // lilac
OTHER(color = Color.rgb("#BDBDBD").fill()), // grey
;
}

val DefaultProjectMapper: ProjectMapper = { project ->
when {
project.hasAnyPlugin("org.jetbrains.kotlin.multiplatform") ->
DefaultProjectType.MULTIPLATFORM

project.hasAnyPlugin(
"com.android.library",
"com.android.application",
"com.android.test",
"com.android.feature",
"com.android.instantapp",
) -> DefaultProjectType.ANDROID

project.hasAnyPlugin(
"java-library",
"java",
"java-gradle-plugin",
"application",
"org.jetbrains.kotlin.jvm",
) -> DefaultProjectType.JVM

project.hasAnyPlugin("org.jetbrains.kotlin.native.cocoapods") ->
DefaultProjectType.IOS

project.hasAnyPlugin("com.eriwen.gradle.js") ->
DefaultProjectType.JS

else ->
DefaultProjectType.OTHER
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,14 @@ internal class ProjectDependencyGraphGenerator(
node.add(Shape.RECTANGLE)
}

node.add(project.target().color)
val type = projectGenerator.projectMapper.invoke(project)
type?.color?.let(node::add)
type?.shape?.let(node::add)

if (node.attrs().isEmpty) {
project.logger.warn("Node '${node.name()}' has no attributes, it won't be visible on the diagram")
}

graph.add(projectGenerator.projectNode(node, project))
}

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.vanniktech.dependency.graph.generator

import com.vanniktech.dependency.graph.generator.ProjectTarget.MULTIPLATFORM
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ProjectDependency
Expand All @@ -20,21 +19,9 @@ internal fun String.toHyphenCase(): String {

fun Project.isDependingOnOtherProject() = configurations.any { configuration -> configuration.dependencies.any { it is ProjectDependency } }

fun Project.isCommonsProject() = plugins.hasPlugin("org.jetbrains.kotlin.platform.common")

internal fun Project.target(): ProjectTarget {
val targets = ProjectTarget.values()
.filter { target -> target.ids.any { plugins.hasPlugin(it) } }

val withoutMultiplatform = targets.minus(MULTIPLATFORM)

return when {
targets.contains(MULTIPLATFORM) -> MULTIPLATFORM
else -> withoutMultiplatform.firstOrNull() ?: ProjectTarget.OTHER
}
}

internal fun Configuration.isImplementation() = name.lowercase().endsWith("implementation")

internal val Project.buildDirectory: File
get() = layout.buildDirectory.asFile.get()

internal fun Project.hasAnyPlugin(vararg ids: String) = ids.any { pluginManager.hasPlugin(it) }
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package com.vanniktech.dependency.graph.generator

import com.vanniktech.dependency.graph.generator.DependencyGraphGeneratorExtension.Generator.Companion.ALL
import guru.nidi.graphviz.attribute.Shape
import org.gradle.api.Project
import org.gradle.api.plugins.JavaLibraryPlugin
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test

class DependencyGraphGeneratorMapperTest {
private lateinit var project: Project

@Before
fun setUp() {
project = buildTestProject(name = "root")
with(project) {
plugins.apply(JavaLibraryPlugin::class.java)
repositories.run { add(mavenCentral()) }
dependencies.add("api", "org.jetbrains.kotlin:kotlin-stdlib:1.2.30")
dependencies.add("api", "org.jetbrains.kotlin:kotlin-reflect:1.2.30")
dependencies.add("implementation", "io.reactivex.rxjava2:rxjava:2.1.10")
}
}

@Test
fun tintedByGroupDependencyMapperTest() {
assertEquals(
// language=dot
"""
digraph "G" {
edge ["dir"="forward"]
graph ["dpi"="100"]
"root" ["label"="root","shape"="rectangle"]
"orgjetbrainskotlinkotlinstdlib" ["label"="kotlin-stdlib","shape"="rectangle","fillcolor"="#9b5ba3"]
"orgjetbrainsannotations" ["label"="jetbrains-annotations","shape"="rectangle","fillcolor"="#504d8c"]
"orgjetbrainskotlinkotlinreflect" ["label"="kotlin-reflect","shape"="rectangle","fillcolor"="#9b5ba3"]
"ioreactivexrxjava2rxjava" ["label"="rxjava","shape"="rectangle","fillcolor"="#0afeb3"]
"orgreactivestreamsreactivestreams" ["label"="reactive-streams","shape"="rectangle","fillcolor"="#ea70d0"]
{
edge ["dir"="none"]
graph ["rank"="same"]
"root"
}
"root" -> "orgjetbrainskotlinkotlinstdlib"
"root" -> "orgjetbrainskotlinkotlinreflect"
"root" -> "ioreactivexrxjava2rxjava"
"orgjetbrainskotlinkotlinstdlib" -> "orgjetbrainsannotations"
"orgjetbrainskotlinkotlinreflect" -> "orgjetbrainskotlinkotlinstdlib"
"ioreactivexrxjava2rxjava" -> "orgreactivestreamsreactivestreams"
}
""".trimIndent(),
DependencyGraphGenerator(
project = project,
generator = ALL.copy(dependencyMapper = TintDependencyByGroup),
).generateGraph().toString(),
)
}

@Test
fun customDependencyMapperTest() {
val mapper: DependencyMapper = { dependency ->
println("${dependency.moduleGroup} ${dependency.name} ${dependency.moduleName}")
when {
dependency.moduleName.contains("x") -> BasicNodeType("#ABC123", Shape.EGG)
dependency.moduleName.contains("k") -> BasicNodeType("#DEF789", Shape.POINT)
else -> null
}
}
assertEquals(
// language=dot
"""
digraph "G" {
edge ["dir"="forward"]
graph ["dpi"="100"]
"root" ["label"="root","shape"="rectangle"]
"orgjetbrainskotlinkotlinstdlib" ["label"="kotlin-stdlib","shape"="point","fillcolor"="#DEF789"]
"orgjetbrainsannotations" ["label"="jetbrains-annotations","shape"="rectangle"]
"orgjetbrainskotlinkotlinreflect" ["label"="kotlin-reflect","shape"="point","fillcolor"="#DEF789"]
"ioreactivexrxjava2rxjava" ["label"="rxjava","shape"="egg","fillcolor"="#ABC123"]
"orgreactivestreamsreactivestreams" ["label"="reactive-streams","shape"="rectangle"]
{
edge ["dir"="none"]
graph ["rank"="same"]
"root"
}
"root" -> "orgjetbrainskotlinkotlinstdlib"
"root" -> "orgjetbrainskotlinkotlinreflect"
"root" -> "ioreactivexrxjava2rxjava"
"orgjetbrainskotlinkotlinstdlib" -> "orgjetbrainsannotations"
"orgjetbrainskotlinkotlinreflect" -> "orgjetbrainskotlinkotlinstdlib"
"ioreactivexrxjava2rxjava" -> "orgreactivestreamsreactivestreams"
}
""".trimIndent(),
DependencyGraphGenerator(project, ALL.copy(dependencyMapper = mapper)).generateGraph().toString(),
)
}
}
Loading