Last active
January 14, 2026 17:28
-
-
Save kibotu/bcdc73cb60057ba1724ce787eedb1122 to your computer and use it in GitHub Desktop.
Task to analyze dependencies and output necessary version overrides Similar to version-overrides/generate.py - finds actual duplicate versions
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| tasks.register("logVersionOverrides") { | |
| group = "help" | |
| description = "Analyzes dependencies for actual version conflicts (same dep with multiple versions)" | |
| doLast { | |
| // Track all versions seen for each dependency | |
| def depVersions = [:] | |
| def resolvedVersions = [:] | |
| // Analyze releaseRuntimeClasspath configuration | |
| def configName = "releaseRuntimeClasspath" | |
| def config = configurations.findByName(configName) | |
| if (config != null && config.canBeResolved) { | |
| config.incoming.resolutionResult.allDependencies.each { dependency -> | |
| if (dependency instanceof org.gradle.api.artifacts.result.ResolvedDependencyResult) { | |
| def requested = dependency.requested | |
| def selected = dependency.selected.moduleVersion | |
| if (requested instanceof org.gradle.api.artifacts.component.ModuleComponentSelector && selected != null) { | |
| def requestedVersion = requested.version | |
| def selectedVersion = selected.version | |
| def moduleId = "${requested.group}:${requested.module}" | |
| // Track ALL versions requested for this dependency | |
| if (requestedVersion && !requestedVersion.isEmpty()) { | |
| if (!depVersions.containsKey(moduleId)) { | |
| depVersions[moduleId] = [] as Set | |
| } | |
| depVersions[moduleId].add(requestedVersion) | |
| } | |
| resolvedVersions[moduleId] = selectedVersion | |
| } | |
| } | |
| } | |
| } | |
| // Compare versions - prefer stable over pre-release, then by numeric parts | |
| def compareVersions = { String a, String b -> | |
| // Pre-release versions (alpha, beta, rc) are LOWER than stable | |
| def aIsPreRelease = a =~ /(?i)(alpha|beta|rc|snapshot)/ | |
| def bIsPreRelease = b =~ /(?i)(alpha|beta|rc|snapshot)/ | |
| // Extract base version (before any suffix) | |
| def aBase = a.replaceAll(/[-+].*/, "").replaceAll(/[^0-9.]/, "") | |
| def bBase = b.replaceAll(/[-+].*/, "").replaceAll(/[^0-9.]/, "") | |
| def aParts = aBase.split(/\./).collect { it.isInteger() ? it.toInteger() : 0 } | |
| def bParts = bBase.split(/\./).collect { it.isInteger() ? it.toInteger() : 0 } | |
| def maxLen = Math.max(aParts.size(), bParts.size()) | |
| for (int i = 0; i < maxLen; i++) { | |
| def aVal = i < aParts.size() ? aParts[i] : 0 | |
| def bVal = i < bParts.size() ? bParts[i] : 0 | |
| if (aVal != bVal) return aVal <=> bVal | |
| } | |
| // Same base version - stable wins over pre-release | |
| if (aIsPreRelease && !bIsPreRelease) return -1 | |
| if (!aIsPreRelease && bIsPreRelease) return 1 | |
| return 0 | |
| } | |
| def getMaxVersion = { Set<String> versions -> | |
| versions.max { a, b -> compareVersions(a, b) } | |
| } | |
| def allConflicts = depVersions.findAll { it.value.size() > 1 } | |
| // A conflict is UNRESOLVED only if: | |
| // 1. Resolved version is LOWER than max requested version | |
| // 2. For Kotlin stdlib, we always flag to ensure consistency | |
| def unresolvedConflicts = allConflicts.findAll { moduleId, versions -> | |
| def resolved = resolvedVersions[moduleId] ?: "" | |
| def maxVersion = getMaxVersion(versions) | |
| // Always flag Kotlin stdlib for visibility | |
| if (moduleId.startsWith("org.jetbrains.kotlin:kotlin-stdlib")) { | |
| return true | |
| } | |
| // Only flag if resolved is LOWER than max | |
| compareVersions(resolved, maxVersion) < 0 | |
| }.collectEntries { moduleId, _ -> | |
| [(moduleId): resolvedVersions[moduleId] ?: "unknown"] | |
| }.sort() | |
| println "\n${"=" * 80}" | |
| println "DEPENDENCY VERSION ANALYSIS" | |
| println "=" * 80 | |
| println "" | |
| println "Total dependencies with multiple versions: ${allConflicts.size()}" | |
| println "Unresolved (need force): ${unresolvedConflicts.size()}" | |
| println "" | |
| if (unresolvedConflicts.isEmpty()) { | |
| println "✅ All Good! All version conflicts are properly resolved." | |
| println "" | |
| println "=" * 80 | |
| return | |
| } | |
| println "Unresolved conflicts:" | |
| println "" | |
| // Show unresolved conflicts with their versions | |
| unresolvedConflicts.each { moduleId, resolvedVersion -> | |
| def versions = depVersions[moduleId]?.sort()?.join(" | ") ?: "" | |
| def maxVersion = getMaxVersion(depVersions[moduleId] ?: [] as Set) | |
| println "$moduleId" | |
| println " versions: $versions" | |
| println " resolved: $resolvedVersion" + (resolvedVersion != maxVersion ? " (should be $maxVersion)" : "") | |
| println "" | |
| } | |
| // Generate VERSION_OVERRIDES snippet | |
| println "=" * 80 | |
| println "VERSION_OVERRIDES (copy & paste):" | |
| println "=" * 80 | |
| println "" | |
| println "configurations.configureEach {" | |
| println " resolutionStrategy {" | |
| println " capabilitiesResolution.all { selectHighestVersion() }" | |
| // Group by category | |
| def getCategory = { String module -> | |
| if (module.startsWith("org.jetbrains.kotlin:kotlin-stdlib")) return "Kotlin stdlib" | |
| if (module.startsWith("org.jetbrains.kotlin:")) return "Kotlin" | |
| if (module.startsWith("org.jetbrains.kotlinx:kotlinx-coroutines")) return "Kotlinx Coroutines" | |
| if (module.startsWith("org.jetbrains.kotlinx:kotlinx-serialization")) return "Kotlinx Serialization" | |
| if (module.startsWith("org.jetbrains.kotlinx:kotlinx-io")) return "Kotlinx IO" | |
| if (module.startsWith("org.jetbrains.kotlinx:")) return "Kotlinx" | |
| if (module.startsWith("org.jetbrains:")) return "JetBrains" | |
| if (module.startsWith("androidx.compose")) return "Compose" | |
| if (module.startsWith("androidx.lifecycle")) return "Lifecycle" | |
| if (module.startsWith("androidx.")) return "AndroidX" | |
| if (module.startsWith("com.google.firebase")) return "Firebase" | |
| if (module.startsWith("com.google.android.gms")) return "Play Services" | |
| if (module.startsWith("com.squareup.okhttp")) return "OkHttp" | |
| if (module.startsWith("com.squareup.okio")) return "Okio" | |
| return "Other" | |
| } | |
| // For output, use appropriate version for each conflict | |
| // For Kotlin stdlib, all should match the main kotlin-stdlib version | |
| def kotlinStdlibVersion = resolvedVersions["org.jetbrains.kotlin:kotlin-stdlib"] ?: "2.3.0" | |
| def toForce = unresolvedConflicts.collectEntries { moduleId, _ -> | |
| def version = moduleId.startsWith("org.jetbrains.kotlin:kotlin-stdlib") | |
| ? kotlinStdlibVersion | |
| : getMaxVersion(depVersions[moduleId] ?: [] as Set) | |
| [(moduleId): version] | |
| } | |
| def grouped = toForce.groupBy { getCategory(it.key) } | |
| grouped.each { category, entries -> | |
| println "" | |
| println " // $category" | |
| entries.each { module, version -> | |
| println " force '$module:$version'" | |
| } | |
| } | |
| println " }" | |
| println "}" | |
| println "" | |
| println "=" * 80 | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| tasks.register("logVersionOverrides") { | |
| group = "help" | |
| description = "Analyzes dependencies for actual version conflicts (same dep with multiple versions)" | |
| doLast { | |
| // Track all versions seen for each dependency | |
| val depVersions = mutableMapOf<String, MutableSet<String>>() | |
| val resolvedVersions = mutableMapOf<String, String>() | |
| // Analyze releaseRuntimeClasspath configuration | |
| val configName = "releaseRuntimeClasspath" | |
| val config = configurations.findByName(configName) | |
| if (config != null && config.isCanBeResolved) { | |
| config.incoming.resolutionResult.allDependencies.forEach { dependency -> | |
| if (dependency is org.gradle.api.artifacts.result.ResolvedDependencyResult) { | |
| val requested = dependency.requested | |
| val selected = dependency.selected.moduleVersion | |
| if (requested is org.gradle.api.artifacts.component.ModuleComponentSelector && selected != null) { | |
| val requestedVersion = requested.version | |
| val selectedVersion = selected.version | |
| val moduleId = "${requested.group}:${requested.module}" | |
| // Track ALL versions requested for this dependency | |
| if (requestedVersion.isNotEmpty()) { | |
| depVersions.getOrPut(moduleId) { mutableSetOf() }.add(requestedVersion) | |
| } | |
| resolvedVersions[moduleId] = selectedVersion | |
| } | |
| } | |
| } | |
| } | |
| // Find dependencies with multiple different versions (actual conflicts) | |
| // Compare versions - prefer stable over pre-release, then by numeric parts | |
| fun compareVersions(a: String, b: String): Int { | |
| // Pre-release versions (alpha, beta, rc) are LOWER than stable | |
| val aIsPreRelease = a.contains(Regex("(?i)(alpha|beta|rc|snapshot)")) | |
| val bIsPreRelease = b.contains(Regex("(?i)(alpha|beta|rc|snapshot)")) | |
| // Extract base version (before any suffix) | |
| val aBase = a.replace(Regex("[-+].*"), "").replace(Regex("[^0-9.]"), "") | |
| val bBase = b.replace(Regex("[-+].*"), "").replace(Regex("[^0-9.]"), "") | |
| val aParts = aBase.split(".").map { it.toIntOrNull() ?: 0 } | |
| val bParts = bBase.split(".").map { it.toIntOrNull() ?: 0 } | |
| val maxLen = maxOf(aParts.size, bParts.size) | |
| for (i in 0 until maxLen) { | |
| val aVal = aParts.getOrElse(i) { 0 } | |
| val bVal = bParts.getOrElse(i) { 0 } | |
| if (aVal != bVal) return aVal.compareTo(bVal) | |
| } | |
| // Same base version - stable wins over pre-release | |
| if (aIsPreRelease && !bIsPreRelease) return -1 | |
| if (!aIsPreRelease && bIsPreRelease) return 1 | |
| return 0 | |
| } | |
| fun getMaxVersion(versions: Set<String>): String { | |
| return versions.maxWithOrNull { a, b -> compareVersions(a, b) } ?: versions.first() | |
| } | |
| val allConflicts = depVersions.filter { it.value.size > 1 } | |
| // A conflict is UNRESOLVED only if: | |
| // 1. Resolved version is LOWER than max requested version | |
| // 2. For Kotlin stdlib, we always flag to ensure consistency | |
| val unresolvedConflicts = allConflicts.filter { (moduleId, versions) -> | |
| val resolved = resolvedVersions[moduleId] ?: "" | |
| val maxVersion = getMaxVersion(versions) | |
| // Always flag Kotlin stdlib for visibility (they need to match Kotlin plugin version) | |
| if (moduleId.startsWith("org.jetbrains.kotlin:kotlin-stdlib")) { | |
| return@filter true | |
| } | |
| // Only flag if resolved is LOWER than max (force might set it higher, that's fine) | |
| compareVersions(resolved, maxVersion) < 0 | |
| }.mapValues { (moduleId, _) -> resolvedVersions[moduleId] ?: "unknown" }.toSortedMap() | |
| println("\n" + "=".repeat(80)) | |
| println("DEPENDENCY VERSION ANALYSIS") | |
| println("=".repeat(80)) | |
| println() | |
| println("Total dependencies with multiple versions: ${allConflicts.size}") | |
| println("Unresolved (need force): ${unresolvedConflicts.size}") | |
| println() | |
| if (unresolvedConflicts.isEmpty()) { | |
| println("✅ All Good! All version conflicts are properly resolved.") | |
| println() | |
| println("=".repeat(80)) | |
| return@doLast | |
| } | |
| println("Unresolved conflicts:") | |
| println() | |
| // Show unresolved conflicts with their versions | |
| unresolvedConflicts.forEach { (moduleId, resolvedVersion) -> | |
| val versions = depVersions[moduleId]?.sorted()?.joinToString(" | ") ?: "" | |
| val maxVersion = getMaxVersion(depVersions[moduleId] ?: emptySet()) | |
| println("$moduleId") | |
| println(" versions: $versions") | |
| println(" resolved: $resolvedVersion" + if (resolvedVersion != maxVersion) " (should be $maxVersion)" else "") | |
| println() | |
| } | |
| // Generate VERSION_OVERRIDES snippet | |
| println("=".repeat(80)) | |
| println("VERSION_OVERRIDES (copy & paste):") | |
| println("=".repeat(80)) | |
| println() | |
| println("configurations.configureEach {") | |
| println(" resolutionStrategy {") | |
| println(" capabilitiesResolution.all { selectHighestVersion() }") | |
| // Group by category | |
| fun getCategory(module: String) = when { | |
| module.startsWith("org.jetbrains.kotlin:kotlin-stdlib") -> "Kotlin stdlib" | |
| module.startsWith("org.jetbrains.kotlin:") -> "Kotlin" | |
| module.startsWith("org.jetbrains.kotlinx:kotlinx-coroutines") -> "Kotlinx Coroutines" | |
| module.startsWith("org.jetbrains.kotlinx:kotlinx-serialization") -> "Kotlinx Serialization" | |
| module.startsWith("org.jetbrains.kotlinx:kotlinx-io") -> "Kotlinx IO" | |
| module.startsWith("org.jetbrains.kotlinx:") -> "Kotlinx" | |
| module.startsWith("org.jetbrains:") -> "JetBrains" | |
| module.startsWith("androidx.compose") -> "Compose" | |
| module.startsWith("androidx.lifecycle") -> "Lifecycle" | |
| module.startsWith("androidx.") -> "AndroidX" | |
| module.startsWith("com.google.firebase") -> "Firebase" | |
| module.startsWith("com.google.android.gms") -> "Play Services" | |
| module.startsWith("com.squareup.okhttp") -> "OkHttp" | |
| module.startsWith("com.squareup.okio") -> "Okio" | |
| else -> "Other" | |
| } | |
| // For output, use appropriate version for each conflict | |
| // For Kotlin stdlib, all should match the main kotlin-stdlib version | |
| val kotlinStdlibVersion = resolvedVersions["org.jetbrains.kotlin:kotlin-stdlib"] ?: "2.3.0" | |
| val toForce = unresolvedConflicts.mapValues { (moduleId, _) -> | |
| if (moduleId.startsWith("org.jetbrains.kotlin:kotlin-stdlib")) { | |
| kotlinStdlibVersion | |
| } else { | |
| getMaxVersion(depVersions[moduleId] ?: emptySet()) | |
| } | |
| } | |
| val grouped = toForce.entries.groupBy { getCategory(it.key) } | |
| grouped.forEach { (category, entries) -> | |
| println() | |
| println(" // $category") | |
| entries.forEach { (module, version) -> | |
| println(" force(\"$module:$version\")") | |
| } | |
| } | |
| println(" }") | |
| println("}") | |
| println() | |
| println("=".repeat(80)) | |
| } | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
outputs easy copy & pastable snippet: