|
| 1 | +import org.gradle.api.DefaultTask |
| 2 | +import org.gradle.api.Project |
| 3 | +import org.gradle.api.artifacts.ProjectDependency |
| 4 | +import org.gradle.api.tasks.TaskAction |
| 5 | +import java.util.Locale |
| 6 | + |
| 7 | +@Suppress("unused") |
| 8 | +open class ProjectDependencyGraphTask : DefaultTask() { |
| 9 | + @TaskAction |
| 10 | + fun run() { |
| 11 | + val dot = project.rootDir.resolve("gradle/dependency-graph/project.dot") |
| 12 | + dot.parentFile.mkdirs() |
| 13 | + dot.delete() |
| 14 | + |
| 15 | + dot.appendText( |
| 16 | + """ |
| 17 | + |digraph { |
| 18 | + | graph [label="${project.rootProject.name}\n ",labelloc=t,fontsize=30,ranksep=1.4]; |
| 19 | + | node [style=filled, fillcolor="#bbbbbb"]; |
| 20 | + | rankdir=TB; |
| 21 | + | |
| 22 | + """.trimMargin() |
| 23 | + ) |
| 24 | + |
| 25 | + val rootProjects = mutableListOf<Project>() |
| 26 | + val queue = mutableListOf(project.rootProject) |
| 27 | + while (queue.isNotEmpty()) { |
| 28 | + val project = queue.removeAt(0) |
| 29 | + rootProjects.add(project) |
| 30 | + queue.addAll(project.childProjects.values) |
| 31 | + } |
| 32 | + |
| 33 | + val projects = LinkedHashSet<Project>() |
| 34 | + val dependencies = LinkedHashMap<Pair<Project, Project>, MutableList<String>>() |
| 35 | + val multiplatformProjects = mutableListOf<Project>() |
| 36 | + val jsProjects = mutableListOf<Project>() |
| 37 | + val androidProjects = mutableListOf<Project>() |
| 38 | + val javaProjects = mutableListOf<Project>() |
| 39 | + val rankAndroid = mutableListOf<Project>() |
| 40 | + val rankDomain = mutableListOf<Project>() |
| 41 | + val rankRepository = mutableListOf<Project>() |
| 42 | + |
| 43 | + queue.clear() |
| 44 | + queue.add(project.rootProject) |
| 45 | + while (queue.isNotEmpty()) { |
| 46 | + val project = queue.removeAt(0) |
| 47 | + queue.addAll(project.childProjects.values) |
| 48 | + |
| 49 | + if (project.plugins.hasPlugin("org.jetbrains.kotlin.multiplatform")) { |
| 50 | + multiplatformProjects.add(project) |
| 51 | + } |
| 52 | + if (project.plugins.hasPlugin("kotlin2js")) { |
| 53 | + jsProjects.add(project) |
| 54 | + } |
| 55 | + if (project.plugins.hasPlugin("com.android.library") || project.plugins.hasPlugin("com.android.application")) { |
| 56 | + androidProjects.add(project) |
| 57 | + } |
| 58 | + if (project.plugins.hasPlugin("java-library") || project.plugins.hasPlugin("java")) { |
| 59 | + javaProjects.add(project) |
| 60 | + } |
| 61 | + |
| 62 | + if (!project.path.startsWith(":core:") && project.path.endsWith(":android")) { |
| 63 | + rankAndroid.add(project) |
| 64 | + } |
| 65 | + if (!project.path.startsWith(":core:") && project.path.endsWith(":repository")) { |
| 66 | + rankRepository.add(project) |
| 67 | + } |
| 68 | + |
| 69 | + project.configurations.all { |
| 70 | + getDependencies() |
| 71 | + .filterIsInstance<ProjectDependency>() |
| 72 | + .filter { project != it.dependencyProject } |
| 73 | + .map { it.dependencyProject } |
| 74 | + .forEach { dependency -> |
| 75 | + projects.add(project) |
| 76 | + projects.add(dependency) |
| 77 | + rootProjects.remove(dependency) |
| 78 | + |
| 79 | + val graphKey = Pair(project, dependency) |
| 80 | + val traits = dependencies.computeIfAbsent(graphKey) { mutableListOf() } |
| 81 | + |
| 82 | + if (name.toLowerCase(Locale.getDefault()).endsWith("implementation")) { |
| 83 | + traits.add("style=dotted") |
| 84 | + } |
| 85 | + } |
| 86 | + } |
| 87 | + } |
| 88 | + |
| 89 | + projects.sortedBy { it.path }.also { |
| 90 | + projects.clear() |
| 91 | + projects.addAll(it) |
| 92 | + } |
| 93 | + |
| 94 | + dot.appendText("\n # Projects\n\n") |
| 95 | + for (project in projects) { |
| 96 | + val traits = mutableListOf<String>() |
| 97 | + |
| 98 | + if (rootProjects.contains(project)) { |
| 99 | + traits.add("shape=box") |
| 100 | + } |
| 101 | + |
| 102 | + if (multiplatformProjects.contains(project)) { |
| 103 | + if (androidProjects.contains(project)) { |
| 104 | + traits.add("fillcolor=\"#f7ffad\"") |
| 105 | + } else { |
| 106 | + traits.add("fillcolor=\"#ffd2b3\"") |
| 107 | + } |
| 108 | + } else if (jsProjects.contains(project)) { |
| 109 | + traits.add("fillcolor=\"#ffffba\"") |
| 110 | + } else if (androidProjects.contains(project)) { |
| 111 | + traits.add("fillcolor=\"#baffc9\"") |
| 112 | + } else if (javaProjects.contains(project)) { |
| 113 | + traits.add("fillcolor=\"#ffb3ba\"") |
| 114 | + } else { |
| 115 | + traits.add("fillcolor=\"#eeeeee\"") |
| 116 | + } |
| 117 | + |
| 118 | + dot.appendText(" \"${project.path}\" [${traits.joinToString(", ")}];\n") |
| 119 | + } |
| 120 | + |
| 121 | + dot.appendText("\n {rank = same;") |
| 122 | + for (project in projects) { |
| 123 | + if (rootProjects.contains(project)) { |
| 124 | + dot.appendText(" \"${project.path}\";") |
| 125 | + } |
| 126 | + } |
| 127 | + dot.appendText("}\n") |
| 128 | + |
| 129 | + for (sameRank in listOf(rankAndroid, rankDomain, rankRepository)) { |
| 130 | + dot.appendText("\n {rank = same;") |
| 131 | + for (project in sameRank) { |
| 132 | + dot.appendText(" \"${project.path}\";") |
| 133 | + } |
| 134 | + dot.appendText("}\n") |
| 135 | + } |
| 136 | + |
| 137 | + dot.appendText("\n # Dependencies\n\n") |
| 138 | + dependencies.forEach { (key, traits) -> |
| 139 | + dot.appendText(" \"${key.first.path}\" -> \"${key.second.path}\"") |
| 140 | + if (traits.isNotEmpty()) { |
| 141 | + dot.appendText(" [${traits.joinToString(", ")}]") |
| 142 | + } |
| 143 | + dot.appendText("\n") |
| 144 | + } |
| 145 | + |
| 146 | + dot.appendText("}\n") |
| 147 | + |
| 148 | + project.exec { |
| 149 | + commandLine = listOf("sh", "-c", "cd \"${dot.parentFile}\"; dot -Tpng -O project.dot") |
| 150 | + } |
| 151 | + println("Project module dependency graph created at ${dot.absolutePath}.png") |
| 152 | + } |
| 153 | +} |
| 154 | + |
0 commit comments