Skip to content

Commit 5f66c58

Browse files
authored
API: Extended mapping to node attributes (#267)
1 parent f363f99 commit 5f66c58

File tree

11 files changed

+331
-67
lines changed

11 files changed

+331
-67
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
Version 0.9.0 *(In development)*
44
--------------------------------
55

6+
- 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))
7+
68
Version 0.8.0 *(2022-06-21)*
79
----------------------------
810

@@ -99,4 +101,4 @@ Version 0.2.0 *(2018-03-07)*
99101
Version 0.1.0 *(2018-03-04)*
100102
----------------------------
101103

102-
- Initial release
104+
- Initial release

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ dependencies {
6969

7070
testImplementation 'junit:junit:4.13.2'
7171
testImplementation "com.android.tools.build:gradle:8.10.0"
72+
testImplementation "org.jetbrains.kotlin.native.cocoapods:org.jetbrains.kotlin.native.cocoapods.gradle.plugin:$kotlinVersion"
7273
testImplementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
7374

7475
// https://github.com/gradle/gradle/issues/16774#issuecomment-893493869

src/main/kotlin/com/vanniktech/dependency/graph/generator/DependencyGraphGenerator.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ internal class DependencyGraphGenerator(
8989
.add(Label.of(dependency.getDisplayName()))
9090
.add(Shape.RECTANGLE)
9191

92+
val type = generator.dependencyMapper.invoke(dependency)
93+
type?.color?.let(node::add)
94+
type?.shape?.let(node::add)
95+
9296
val mutated = generator.dependencyNode.invoke(node, dependency)
9397
nodes[identifier] = mutated
9498
graph.add(mutated)

src/main/kotlin/com/vanniktech/dependency/graph/generator/DependencyGraphGeneratorExtension.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ open class DependencyGraphGeneratorExtension(project: Project) {
8484
@get:Nested var graph: (MutableGraph) -> MutableGraph = { it },
8585
/** Allows you to configure the [Graphviz] instance. */
8686
@get:Nested var graphviz: (Graphviz) -> Graphviz = { it },
87+
/** Determines the color/shape of the project node on the output diagram */
88+
@get:Nested var dependencyMapper: DependencyMapper = { null },
8789
) {
8890
/** Gradle task name that is associated with this generator. */
8991
@get:Internal val gradleTaskName = "generateDependencyGraph${
@@ -138,6 +140,8 @@ open class DependencyGraphGeneratorExtension(project: Project) {
138140
@get:Nested var graph: (MutableGraph) -> MutableGraph = { it },
139141
/** Allows you to configure the [Graphviz] instance. */
140142
@get:Nested var graphviz: (Graphviz) -> Graphviz = { it },
143+
/** Determines the color/shape of the project node on the output diagram */
144+
@get:Nested var projectMapper: ProjectMapper = DefaultProjectMapper,
141145
) {
142146
/** Gradle task name that is associated with this generator. */
143147
@get:Internal val gradleTaskName = "generateProjectDependencyGraph${
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package com.vanniktech.dependency.graph.generator
2+
3+
import guru.nidi.graphviz.attribute.Color
4+
import guru.nidi.graphviz.attribute.Shape
5+
import org.gradle.api.Project
6+
import org.gradle.api.artifacts.ResolvedDependency
7+
8+
interface NodeType {
9+
val color: Color?
10+
val shape: Shape?
11+
}
12+
13+
data class BasicNodeType(
14+
override val color: Color?,
15+
override val shape: Shape?,
16+
) : NodeType {
17+
constructor(rgbHex: String, shape: Shape?) : this(Color.rgb(rgbHex).fill(), shape)
18+
constructor(rgbInt: Int, shape: Shape?) : this(Color.rgb(rgbInt).fill(), shape)
19+
}
20+
21+
typealias ProjectMapper = (Project) -> NodeType?
22+
23+
typealias DependencyMapper = (ResolvedDependency) -> NodeType?
24+
25+
/**
26+
* Use this to apply a semi-random fill color onto each node, where the same color is used on every dependency of the
27+
* same group. E.g. "a.b:c" will have the same color as "a.b:d", but different to "e.f:c"
28+
*/
29+
val TintDependencyByGroup: DependencyMapper = { dependency ->
30+
BasicNodeType(dependency.moduleGroup.hashCode(), shape = null)
31+
}
32+
33+
internal enum class DefaultProjectType(
34+
override val color: Color,
35+
override val shape: Shape? = null,
36+
) : NodeType {
37+
ANDROID(color = Color.rgb("#66BB6A").fill()), // green
38+
JVM(color = Color.rgb("#FF7043").fill()), // orange
39+
IOS(color = Color.rgb("#42A5F5").fill()), // blue
40+
JS(color = Color.rgb("#FFCA28").fill()), // yellow
41+
MULTIPLATFORM(color = Color.rgb("#A280FF").fill()), // lilac
42+
OTHER(color = Color.rgb("#BDBDBD").fill()), // grey
43+
;
44+
}
45+
46+
val DefaultProjectMapper: ProjectMapper = { project ->
47+
when {
48+
project.hasAnyPlugin("org.jetbrains.kotlin.multiplatform") ->
49+
DefaultProjectType.MULTIPLATFORM
50+
51+
project.hasAnyPlugin(
52+
"com.android.library",
53+
"com.android.application",
54+
"com.android.test",
55+
"com.android.feature",
56+
"com.android.instantapp",
57+
) -> DefaultProjectType.ANDROID
58+
59+
project.hasAnyPlugin(
60+
"java-library",
61+
"java",
62+
"java-gradle-plugin",
63+
"application",
64+
"org.jetbrains.kotlin.jvm",
65+
) -> DefaultProjectType.JVM
66+
67+
project.hasAnyPlugin("org.jetbrains.kotlin.native.cocoapods") ->
68+
DefaultProjectType.IOS
69+
70+
project.hasAnyPlugin("com.eriwen.gradle.js") ->
71+
DefaultProjectType.JS
72+
73+
else ->
74+
DefaultProjectType.OTHER
75+
}
76+
}

src/main/kotlin/com/vanniktech/dependency/graph/generator/ProjectDependencyGraphGenerator.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,14 @@ internal class ProjectDependencyGraphGenerator(
5959
node.add(Shape.RECTANGLE)
6060
}
6161

62-
node.add(project.target().color)
62+
val type = projectGenerator.projectMapper.invoke(project)
63+
type?.color?.let(node::add)
64+
type?.shape?.let(node::add)
65+
66+
if (node.attrs().isEmpty) {
67+
project.logger.warn("Node '${node.name()}' has no attributes, it won't be visible on the diagram")
68+
}
69+
6370
graph.add(projectGenerator.projectNode(node, project))
6471
}
6572

src/main/kotlin/com/vanniktech/dependency/graph/generator/ProjectTarget.kt

Lines changed: 0 additions & 34 deletions
This file was deleted.
Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.vanniktech.dependency.graph.generator
22

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

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

23-
fun Project.isCommonsProject() = plugins.hasPlugin("org.jetbrains.kotlin.platform.common")
24-
25-
internal fun Project.target(): ProjectTarget {
26-
val targets = ProjectTarget.values()
27-
.filter { target -> target.ids.any { plugins.hasPlugin(it) } }
28-
29-
val withoutMultiplatform = targets.minus(MULTIPLATFORM)
30-
31-
return when {
32-
targets.contains(MULTIPLATFORM) -> MULTIPLATFORM
33-
else -> withoutMultiplatform.firstOrNull() ?: ProjectTarget.OTHER
34-
}
35-
}
36-
3722
internal fun Configuration.isImplementation() = name.lowercase().endsWith("implementation")
3823

3924
internal val Project.buildDirectory: File
4025
get() = layout.buildDirectory.asFile.get()
26+
27+
internal fun Project.hasAnyPlugin(vararg ids: String) = ids.any { pluginManager.hasPlugin(it) }
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package com.vanniktech.dependency.graph.generator
2+
3+
import com.vanniktech.dependency.graph.generator.DependencyGraphGeneratorExtension.Generator.Companion.ALL
4+
import guru.nidi.graphviz.attribute.Shape
5+
import org.gradle.api.Project
6+
import org.gradle.api.plugins.JavaLibraryPlugin
7+
import org.junit.Assert.assertEquals
8+
import org.junit.Before
9+
import org.junit.Test
10+
11+
class DependencyGraphGeneratorMapperTest {
12+
private lateinit var project: Project
13+
14+
@Before
15+
fun setUp() {
16+
project = buildTestProject(name = "root")
17+
with(project) {
18+
plugins.apply(JavaLibraryPlugin::class.java)
19+
repositories.run { add(mavenCentral()) }
20+
dependencies.add("api", "org.jetbrains.kotlin:kotlin-stdlib:1.2.30")
21+
dependencies.add("api", "org.jetbrains.kotlin:kotlin-reflect:1.2.30")
22+
dependencies.add("implementation", "io.reactivex.rxjava2:rxjava:2.1.10")
23+
}
24+
}
25+
26+
@Test
27+
fun tintedByGroupDependencyMapperTest() {
28+
assertEquals(
29+
// language=dot
30+
"""
31+
digraph "G" {
32+
edge ["dir"="forward"]
33+
graph ["dpi"="100"]
34+
"root" ["label"="root","shape"="rectangle"]
35+
"orgjetbrainskotlinkotlinstdlib" ["label"="kotlin-stdlib","shape"="rectangle","fillcolor"="#9b5ba3"]
36+
"orgjetbrainsannotations" ["label"="jetbrains-annotations","shape"="rectangle","fillcolor"="#504d8c"]
37+
"orgjetbrainskotlinkotlinreflect" ["label"="kotlin-reflect","shape"="rectangle","fillcolor"="#9b5ba3"]
38+
"ioreactivexrxjava2rxjava" ["label"="rxjava","shape"="rectangle","fillcolor"="#0afeb3"]
39+
"orgreactivestreamsreactivestreams" ["label"="reactive-streams","shape"="rectangle","fillcolor"="#ea70d0"]
40+
{
41+
edge ["dir"="none"]
42+
graph ["rank"="same"]
43+
"root"
44+
}
45+
"root" -> "orgjetbrainskotlinkotlinstdlib"
46+
"root" -> "orgjetbrainskotlinkotlinreflect"
47+
"root" -> "ioreactivexrxjava2rxjava"
48+
"orgjetbrainskotlinkotlinstdlib" -> "orgjetbrainsannotations"
49+
"orgjetbrainskotlinkotlinreflect" -> "orgjetbrainskotlinkotlinstdlib"
50+
"ioreactivexrxjava2rxjava" -> "orgreactivestreamsreactivestreams"
51+
}
52+
""".trimIndent(),
53+
DependencyGraphGenerator(
54+
project = project,
55+
generator = ALL.copy(dependencyMapper = TintDependencyByGroup),
56+
).generateGraph().toString(),
57+
)
58+
}
59+
60+
@Test
61+
fun customDependencyMapperTest() {
62+
val mapper: DependencyMapper = { dependency ->
63+
println("${dependency.moduleGroup} ${dependency.name} ${dependency.moduleName}")
64+
when {
65+
dependency.moduleName.contains("x") -> BasicNodeType("#ABC123", Shape.EGG)
66+
dependency.moduleName.contains("k") -> BasicNodeType("#DEF789", Shape.POINT)
67+
else -> null
68+
}
69+
}
70+
assertEquals(
71+
// language=dot
72+
"""
73+
digraph "G" {
74+
edge ["dir"="forward"]
75+
graph ["dpi"="100"]
76+
"root" ["label"="root","shape"="rectangle"]
77+
"orgjetbrainskotlinkotlinstdlib" ["label"="kotlin-stdlib","shape"="point","fillcolor"="#DEF789"]
78+
"orgjetbrainsannotations" ["label"="jetbrains-annotations","shape"="rectangle"]
79+
"orgjetbrainskotlinkotlinreflect" ["label"="kotlin-reflect","shape"="point","fillcolor"="#DEF789"]
80+
"ioreactivexrxjava2rxjava" ["label"="rxjava","shape"="egg","fillcolor"="#ABC123"]
81+
"orgreactivestreamsreactivestreams" ["label"="reactive-streams","shape"="rectangle"]
82+
{
83+
edge ["dir"="none"]
84+
graph ["rank"="same"]
85+
"root"
86+
}
87+
"root" -> "orgjetbrainskotlinkotlinstdlib"
88+
"root" -> "orgjetbrainskotlinkotlinreflect"
89+
"root" -> "ioreactivexrxjava2rxjava"
90+
"orgjetbrainskotlinkotlinstdlib" -> "orgjetbrainsannotations"
91+
"orgjetbrainskotlinkotlinreflect" -> "orgjetbrainskotlinkotlinstdlib"
92+
"ioreactivexrxjava2rxjava" -> "orgreactivestreamsreactivestreams"
93+
}
94+
""".trimIndent(),
95+
DependencyGraphGenerator(project, ALL.copy(dependencyMapper = mapper)).generateGraph().toString(),
96+
)
97+
}
98+
}

0 commit comments

Comments
 (0)