From 8129965281d6a49e5acf2cb48447ac3a14a5d028 Mon Sep 17 00:00:00 2001 From: winter-love-dev Date: Sun, 2 Nov 2025 14:46:50 +0900 Subject: [PATCH] Refactor dependencyGraph generator --- build.gradle.kts | 5 +- gradle/dependencyGraph.gradle | 135 ++++++++++++++++++++ gradle/jetifier_disable.gradle.kts | 27 ---- gradle/projectInverseDependencyGraph.gradle | 76 ----------- 4 files changed, 137 insertions(+), 106 deletions(-) create mode 100644 gradle/dependencyGraph.gradle delete mode 100644 gradle/jetifier_disable.gradle.kts delete mode 100644 gradle/projectInverseDependencyGraph.gradle diff --git a/build.gradle.kts b/build.gradle.kts index 64001ad..bf1f0ea 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,9 +10,8 @@ plugins { alias(libs.plugins.detekt) } -apply(from = "${rootDir}/gradle/jetifier_disable.gradle.kts") -apply(from = "${rootDir}/gradle/projectInverseDependencyGraph.gradle") - tasks.register("clean", Delete::class) { delete(rootProject.layout.buildDirectory) } + +apply(from = "${rootDir}/gradle/dependencyGraph.gradle") diff --git a/gradle/dependencyGraph.gradle b/gradle/dependencyGraph.gradle new file mode 100644 index 0000000..f2056c2 --- /dev/null +++ b/gradle/dependencyGraph.gradle @@ -0,0 +1,135 @@ +// from: https://github.com/DroidKaigi/conference-app-2021/blob/main/gradle/dependencyGraph.gradle +// from: https://github.com/JakeWharton/SdkSearch/blob/3351cad9bfacb0a364858e843774147143f58c7a/gradle/projectDependencyGraph.gradle +// brew install graphviz +// JAVA_HOME="/Applications/Android Studio.app/Contents/jbr/Contents/Home" ./gradlew projectDependencyGraph --no-configuration-cache +tasks.register('projectDependencyGraph') { + doLast { + def dotFileName = 'project.dot' + def dot = new File(rootProject.rootDir, dotFileName) + dot.parentFile.mkdirs() + dot.delete() + + dot << 'digraph {\n' + dot << " graph [label=\"${rootProject.name}\\n \",labelloc=t,fontsize=30,ranksep=1.4];\n" + dot << ' node [style=filled, fillcolor="#bbbbbb"];\n' + dot << ' rankdir=TB;\n' + + def rootProjects = [] + def queue = [rootProject] + while (!queue.isEmpty()) { + def project = queue.remove(0) + rootProjects.add(project) + queue.addAll(project.childProjects.values()) + } + + def projects = new LinkedHashSet() + def dependencies = new LinkedHashMap, List>() + def multiplatformProjects = [] + def jsProjects = [] + def androidProjects = [] + def androidLibraryProjects = [] + def androidDynamicFeatureProjects = [] + def javaProjects = [] + + queue = [rootProject] + while (!queue.isEmpty()) { + def project = queue.remove(0) + queue.addAll(project.childProjects.values()) + + if (project.plugins.hasPlugin('org.jetbrains.kotlin.multiplatform')) { + multiplatformProjects.add(project) + } + if (project.plugins.hasPlugin('kotlin2js')) { + jsProjects.add(project) + } + if (project.plugins.hasPlugin('com.android.application')) { + androidProjects.add(project) + } + if (project.plugins.hasPlugin('com.android.library')) { + androidLibraryProjects.add(project) + } + if (project.plugins.hasPlugin('com.android.dynamic-feature')) { + androidDynamicFeatureProjects.add(project) + } + if (project.plugins.hasPlugin('java-library') || project.plugins.hasPlugin('java')) { + javaProjects.add(project) + } + + project.configurations.configureEach { config -> + if (config.name.toLowerCase().contains("test")) return + config.dependencies + .withType(ProjectDependency) + .collect { it.dependencyProject } + .each { dependency -> + projects.add(project) + projects.add(dependency) + rootProjects.remove(dependency) + + def graphKey = new Tuple2(project, dependency) + def traits = dependencies.computeIfAbsent(graphKey) { new ArrayList() } + + if (config.name.toLowerCase().endsWith('implementation')) { + traits.add('style=dotted') + } + } + } + } + + projects = projects.sort { it.path } + + dot << '\n # Projects\n\n' + for (project in projects) { + def traits = [] + + if (rootProjects.contains(project)) { + traits.add('shape=box') + } + + if (multiplatformProjects.contains(project)) { + traits.add('fillcolor="#ffd2b3"') + } else if (jsProjects.contains(project)) { + traits.add('fillcolor="#ffffba"') + } else if (androidProjects.contains(project)) { + traits.add('fillcolor="#baffc9"') + } else if (androidLibraryProjects.contains(project)) { + traits.add('fillcolor="#81D4FA"') + } else if (androidDynamicFeatureProjects.contains(project)) { + traits.add('fillcolor="#c9baff"') + } else if (javaProjects.contains(project)) { + traits.add('fillcolor="#ffb3ba"') + } else { + traits.add('fillcolor="#eeeeee"') + } + + dot << " \"${project.path}\" [${traits.join(", ")}];\n" + } + + dot << '\n {rank = same;' + for (project in projects) { + if (rootProjects.contains(project)) { + dot << " \"${project.path}\";" + } + } + dot << '}\n' + + dot << '\n # Dependencies\n\n' + dependencies.forEach { key, traits -> + dot << " \"${key.first.path}\" -> \"${key.second.path}\"" + if (!traits.isEmpty()) { + dot << " [${traits.join(", ")}]" + } + dot << '\n' + } + + dot << '}\n' + + def p = "dot -Tpng -O ${dotFileName}".execute([], dot.parentFile) + p.waitFor() + if (p.exitValue() != 0) { + throw new RuntimeException(p.errorStream.text) + } + dot.delete() + + println("Project module dependency graph created at ${dot.absolutePath}.png") + } +} \ No newline at end of file diff --git a/gradle/jetifier_disable.gradle.kts b/gradle/jetifier_disable.gradle.kts deleted file mode 100644 index 23ae375..0000000 --- a/gradle/jetifier_disable.gradle.kts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Disable Jetifier and check for legacy support libraries - * Throws GradleException if any com.android.support.* or android.arch.* libraries are found - */ -subprojects { - afterEvaluate { - configurations.all { - resolutionStrategy.eachDependency { - // minimum version of GMS Libraries which is migrated with AndroidX - val minGooglePlayBaseVersion = "17.3.0" - if (requested.group == "com.google.android.gms" && - (requested.name == "play-services-basement" || - requested.name == "play-services-base") && - requested.version!! < minGooglePlayBaseVersion - ) { - useVersion(minGooglePlayBaseVersion) - } - - if (target.group.startsWith("com.android.support") || - target.group.startsWith("android.arch") - ) { - throw GradleException("$target library was included") - } - } - } - } -} \ No newline at end of file diff --git a/gradle/projectInverseDependencyGraph.gradle b/gradle/projectInverseDependencyGraph.gradle deleted file mode 100644 index 3fef1c4..0000000 --- a/gradle/projectInverseDependencyGraph.gradle +++ /dev/null @@ -1,76 +0,0 @@ -// Origin : https://github.com/JakeWharton/SdkSearch/blob/master/gradle/projectDependencyGraph.gradle -/** - * Check dependency graph (requires Java 21) - * Usage: - * ./gradlew projectInverseDependencyGraph - * or - * JAVA_HOME="/Applications/Android Studio.app/Contents/jbr/Contents/Home" ./gradlew projectInverseDependencyGraph - */ -tasks.register('projectInverseDependencyGraph') { - doLast { - def inverseGraph = inverseGenerateGraph() - - println("[All graph]") - inverseGraph.forEach { key, value -> - println "${key}" - println "> ${value.join(", ")}" - } - - // find module - if (project.hasProperty("findModule")) { - def findModule = ":" + project.property("findModule") - println "" - println "===============" - println "Find '[${findModule}]' module" - - List checkModule = inverseGraph.find { - findModule == it.key - }.value.collectNested { - inverseGraph[it] ?: [] - }.flatten().toSet().sort() - - checkModule.forEach { - println "> ${it}" - } - println "===============" - println "" - } - } -} - -private LinkedHashMap> inverseGenerateGraph() { - def rootProjects = [] - def queue = [rootProject] - while (!queue.isEmpty()) { - def project = queue.remove(0) - rootProjects.add(project.path) - queue.addAll(project.childProjects.values()) - } - - def projects = new LinkedHashSet() - def dependencies = new LinkedHashMap>() - - queue = [rootProject] - while (!queue.isEmpty()) { - def project = queue.remove(0) - queue.addAll(project.childProjects.values()) - project.configurations.configureEach { config -> - config.dependencies - .withType(ProjectDependency) - .collect { it.path } - .each { dependency -> - projects.add(project.path) - projects.add(dependency) - rootProjects.remove(dependency) - - def value = dependencies.computeIfAbsent(dependency) { new ArrayList() } - if (config.name.toLowerCase().endsWith('implementation')) { - value.add(project.path) - } - } - } - } - - projects = projects.sort { it } - return dependencies.findAll { !it.value.isEmpty() } -} \ No newline at end of file