Skip to content

Instantly share code, notes, and snippets.

@kibotu
Last active January 14, 2026 17:28
Show Gist options
  • Select an option

  • Save kibotu/bcdc73cb60057ba1724ce787eedb1122 to your computer and use it in GitHub Desktop.

Select an option

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
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
}
}
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))
}
}
@kibotu
Copy link
Author

kibotu commented Jan 14, 2026

./gradlew logVersionOverrides --refresh-dependencies --no-daemon --no-configuration-cache --s

outputs easy copy & pastable snippet:

Configuration on demand is an incubating feature.

> Task :logVersionOverrides

================================================================================
DEPENDENCY VERSION ANALYSIS
================================================================================

Total dependencies with multiple versions: 99
Unresolved (need force): 4

Unresolved conflicts:

org.jetbrains.kotlin:kotlin-stdlib
  versions: 1.3.72 | 1.4.21 | 1.6.21 | 1.8.22 | 1.9.0 | 1.9.22 | 1.9.23 | 1.9.24 | 2.0.20 | 2.0.21 | 2.1.0 | 2.1.20 | 2.1.21 | 2.2.0 | 2.2.20 | 2.2.21 | 2.3.0
  resolved: 2.3.0

org.jetbrains.kotlin:kotlin-stdlib-common
  versions: 1.7.20 | 1.8.22 | 2.3.0
  resolved: 2.3.0

org.jetbrains.kotlin:kotlin-stdlib-jdk7
  versions: 1.8.0 | 1.8.22
  resolved: 1.8.22

org.jetbrains.kotlin:kotlin-stdlib-jdk8
  versions: 1.7.10 | 1.7.20 | 1.8.0 | 1.8.22
  resolved: 1.8.22

================================================================================
VERSION_OVERRIDES (copy & paste):
================================================================================

configurations.configureEach {
    resolutionStrategy {
        capabilitiesResolution.all { selectHighestVersion() }

        // Kotlin stdlib
        force("org.jetbrains.kotlin:kotlin-stdlib:2.3.0")
        force("org.jetbrains.kotlin:kotlin-stdlib-common:2.3.0")
        force("org.jetbrains.kotlin:kotlin-stdlib-jdk7:2.3.0")
        force("org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.3.0")
    }
}

================================================================================

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment