Cleaned up and fixed version parsing. added graceful fallback if version parsing fails. improved performance
This commit is contained in:
parent
1e6088c7bb
commit
333732e1a4
@ -21,7 +21,6 @@ import org.gradle.api.Project
|
|||||||
import org.gradle.api.tasks.TaskAction
|
import org.gradle.api.tasks.TaskAction
|
||||||
import java.io.InputStreamReader
|
import java.io.InputStreamReader
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.util.*
|
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import java.util.concurrent.Future
|
import java.util.concurrent.Future
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock
|
import java.util.concurrent.locks.ReentrantReadWriteLock
|
||||||
@ -30,11 +29,13 @@ import kotlin.concurrent.write
|
|||||||
open class
|
open class
|
||||||
GetVersionInfoTask : DefaultTask() {
|
GetVersionInfoTask : DefaultTask() {
|
||||||
private data class VersionHolder(var release: String?, val versions: MutableSet<String>) {
|
private data class VersionHolder(var release: String?, val versions: MutableSet<String>) {
|
||||||
|
var dirtyVersions = false
|
||||||
|
|
||||||
fun updateReleaseVersion(version: String) {
|
fun updateReleaseVersion(version: String) {
|
||||||
if (release == null) {
|
if (release == null) {
|
||||||
release = version
|
release = version
|
||||||
} else {
|
} else {
|
||||||
// there can be errors when parsing version info, since not versions follow loose-semantic versioning
|
// there can be errors when parsing version info, since not all version strings follow semantic versioning
|
||||||
try {
|
try {
|
||||||
val currentVersion = Version.from(release)
|
val currentVersion = Version.from(release)
|
||||||
val releaseVer = Version.from(version)
|
val releaseVer = Version.from(version)
|
||||||
@ -49,45 +50,77 @@ GetVersionInfoTask : DefaultTask() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun addVersion(ver: String) {
|
fun addVersion(ver: String) {
|
||||||
|
if (!versions.contains(ver)) {
|
||||||
versions.add(ver)
|
versions.add(ver)
|
||||||
|
} else {
|
||||||
|
dirtyVersions = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getVersionOptions(currentVersion: String): List<String> {
|
fun getVersionOptions(currentVersion: String): List<String> {
|
||||||
// there can be errors when parsing version info, since not versions follow loose-semantic versioning
|
// there can be errors when parsing version info, since not all version strings follow semantic versioning
|
||||||
val myVersionIndex = versions.indexOfFirst { it == currentVersion }
|
// first! try version sorting. This may fail!
|
||||||
|
|
||||||
return if (myVersionIndex >= 0) {
|
// there are no duplicates in this list
|
||||||
return versions.filterIndexed { index, _ -> index <= myVersionIndex }
|
try {
|
||||||
|
// this creates a LOT of version objects. Probably better to store these in a list, however we want all backing data
|
||||||
|
// structures to be strings.
|
||||||
|
val curVersion = Version.from(currentVersion)
|
||||||
|
return versions.sortedWith { o1, o2 ->
|
||||||
|
Version.from(o1).compareTo(Version.from(o2))
|
||||||
|
}.filter {
|
||||||
|
Version.from(it).greaterThan(curVersion)
|
||||||
|
}.toList()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// WHOOPS! There was an invalid version number! Instead of just crashing, try a different way...
|
||||||
|
if (dirtyVersions) {
|
||||||
|
// no idea, honestly... the list might not even be in order! Just return the entire thing and let the user sort it out
|
||||||
|
return versions.toMutableList().apply { add(0, "Error parsing!") }.toList()
|
||||||
} else {
|
} else {
|
||||||
versions.toList()
|
// fortunately for us, the usually the maven order of version data is IN-ORDER, so we can "cheat" the system and look at
|
||||||
|
// indexing instead
|
||||||
|
val myVersionIndex = versions.indexOfFirst { it == currentVersion }
|
||||||
|
// println("INDEX: ${myVersionIndex}" )
|
||||||
|
return if (myVersionIndex >= 0) {
|
||||||
|
return versions.filterIndexed { index, _ -> index > myVersionIndex }
|
||||||
|
} else {
|
||||||
|
versions.toMutableList().apply { add(0, "Error parsing!") }.toList()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val releaseMatcher = """^.*(<release>)(.*)(<\/release>)""".toRegex()
|
private val releaseMatcher = """(<release>)(.*)(<\/release>)""".toRegex()
|
||||||
private val versionMatcher = """^.*(<version>)(.*)(<\/version>)""".toRegex()
|
|
||||||
|
|
||||||
private val httpDispatcher = Executors.newFixedThreadPool(8)
|
private val httpDispatcher = Executors.newFixedThreadPool(8)
|
||||||
|
|
||||||
private fun getLatestVersionInfo(
|
private fun getLatestVersionInfo(
|
||||||
repositories: List<String>,
|
repositories: List<String>,
|
||||||
mergedDeps: MutableMap<DependencyScanner.Maven, MutableSet<DependencyScanner.Maven>>,
|
mergedDeps: MutableMap<DependencyScanner.Maven, MutableSet<DependencyScanner.Maven>>,
|
||||||
): MutableMap<DependencyScanner.Maven, VersionHolder> {
|
mergedVersionInfo: MutableMap<DependencyScanner.Maven, VersionHolder> = mutableMapOf(),
|
||||||
|
forGradleScripts: Boolean = false,
|
||||||
|
): Pair<MutableList<Future<*>>, MutableMap<DependencyScanner.Maven, VersionHolder>> {
|
||||||
|
|
||||||
// first get all version information across ALL projects.
|
// first get all version information across ALL projects.
|
||||||
// do this in parallel with coroutines!
|
// do this in parallel with coroutines!
|
||||||
val futures = mutableListOf<Future<*>>()
|
val futures = mutableListOf<Future<*>>()
|
||||||
val mergedVersionInfo = mutableMapOf<DependencyScanner.Maven, VersionHolder>()
|
|
||||||
val downloadLock = ReentrantReadWriteLock()
|
val downloadLock = ReentrantReadWriteLock()
|
||||||
|
|
||||||
// mergedDeps now has all deps for all projects. now we want to resolve (but only if we don't already have it)
|
// mergedDeps now has all deps for all projects. now we want to resolve (but only if we don't already have it)
|
||||||
mergedDeps.forEach { (mergedDep, _) ->
|
mergedDeps.forEach { (mergedDep, _) ->
|
||||||
val metadataUrl = "${mergedDep.group.replace(".", "/")}/${mergedDep.name}/maven-metadata.xml"
|
val metadataUrl = if (forGradleScripts)
|
||||||
|
// we also have to ADD a prefix to the group ID, because a gradle plugin is **SLIGHTLY** different in how it works.
|
||||||
|
"gradle/plugin/${mergedDep.group.replace(".", "/")}/maven-metadata.xml"
|
||||||
|
else
|
||||||
|
"${mergedDep.group.replace(".", "/")}/${mergedDep.name}/maven-metadata.xml"
|
||||||
|
|
||||||
|
val mavenIdKey = DependencyScanner.Maven(mergedDep.group, mergedDep.name)
|
||||||
|
|
||||||
// version info is per dependency
|
// version info is per dependency
|
||||||
val depVersionInfo = downloadLock.write {
|
downloadLock.write {
|
||||||
mergedVersionInfo.getOrPut(DependencyScanner.Maven(mergedDep.group, mergedDep.name)) {
|
mergedVersionInfo.getOrPut(mavenIdKey) {
|
||||||
VersionHolder(null, mutableSetOf())
|
VersionHolder(null, mutableSetOf())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -97,21 +130,46 @@ GetVersionInfoTask : DefaultTask() {
|
|||||||
try {
|
try {
|
||||||
val url = URL(repoUrl + metadataUrl)
|
val url = URL(repoUrl + metadataUrl)
|
||||||
// println("Trying: $url")
|
// println("Trying: $url")
|
||||||
|
|
||||||
with(url.openConnection() as java.net.HttpURLConnection) {
|
with(url.openConnection() as java.net.HttpURLConnection) {
|
||||||
|
var inVersioningSection = false
|
||||||
InputStreamReader(inputStream).readLines().forEach { line ->
|
InputStreamReader(inputStream).readLines().forEach { line ->
|
||||||
var matchResult = releaseMatcher.find(line)
|
val trimmed = line.trim()
|
||||||
|
|
||||||
|
if (!inVersioningSection) {
|
||||||
|
if (trimmed == "<versioning>") {
|
||||||
|
inVersioningSection = true
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (trimmed == "</versioning>") {
|
||||||
|
inVersioningSection = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// only match version info when we are in the "<versioning>" section
|
||||||
|
val matchResult = releaseMatcher.find(trimmed)
|
||||||
if (matchResult != null) {
|
if (matchResult != null) {
|
||||||
val (_, ver, _) = matchResult.destructured
|
val (_, ver, _) = matchResult.destructured
|
||||||
|
// println("Release: ${mergedDep.group}:${mergedDep.name} $ver")
|
||||||
downloadLock.write {
|
downloadLock.write {
|
||||||
depVersionInfo.updateReleaseVersion(ver)
|
mergedVersionInfo[mavenIdKey]!!.updateReleaseVersion(ver)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
matchResult = versionMatcher.find(line)
|
// not using regex, because this becomes complex.
|
||||||
if (matchResult != null) {
|
// There can be a SINGLE version per line, or MULTIPLE versions per line.
|
||||||
val (_, ver, _) = matchResult.destructured
|
// This handles both cases.
|
||||||
|
if (trimmed.startsWith("<version>")) {
|
||||||
|
// list out 1 or more versions
|
||||||
|
val versions = trimmed.split("<version>").filter {it.isNotEmpty()}
|
||||||
|
.map { it.substring(0, it.indexOf('<')) }
|
||||||
|
|
||||||
downloadLock.write {
|
downloadLock.write {
|
||||||
depVersionInfo.addVersion(ver)
|
versions.forEach { ver ->
|
||||||
|
// println("Version: ${mergedDep.group}:${mergedDep.name} $ver")
|
||||||
|
mergedVersionInfo[mavenIdKey]!!.addVersion(ver)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -124,16 +182,12 @@ GetVersionInfoTask : DefaultTask() {
|
|||||||
futures.add(future)
|
futures.add(future)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mergedDeps.isNotEmpty()) {
|
|
||||||
println("\tGetting version data for ${mergedDeps.size} dependencies...")
|
|
||||||
futures.forEach {
|
|
||||||
it.get()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadLock.write {
|
downloadLock.write {
|
||||||
// println("SIZE: " + mergedVersionInfo)
|
// mergedVersionInfo.forEach { t, u ->
|
||||||
return mergedVersionInfo
|
// println("$t :: ${u.versions}")
|
||||||
|
// }
|
||||||
|
|
||||||
|
return Pair(futures, mergedVersionInfo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,25 +207,36 @@ GetVersionInfoTask : DefaultTask() {
|
|||||||
deps.add(dep)
|
deps.add(dep)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// for script dependencies, ALWAYS add the gradle plugin repo!
|
// for script dependencies, ALWAYS add the gradle plugin repo!
|
||||||
// (we hardcode the value, this is not likely to change, but easy enough to fix if it does...)
|
// (we hardcode the value, this is not likely to change, but easy enough to fix if it does...)
|
||||||
val newRepos = mutableSetOf<String>()
|
val (futures, versionHolders) = getLatestVersionInfo(listOf("https://plugins.gradle.org/m2/"), mergedDeps, forGradleScripts = true)
|
||||||
newRepos.add("https://plugins.gradle.org/m2/")
|
val (futures2, _) = getLatestVersionInfo(repositories, mergedDeps, versionHolders)
|
||||||
newRepos.addAll(repositories)
|
|
||||||
|
|
||||||
|
if (mergedDeps.isNotEmpty()) {
|
||||||
|
// suppress duplicate messages when initially parsing gradle scripts (since it's a redundant message )
|
||||||
|
println("\tGetting version data for ${mergedDeps.size} dependencies...")
|
||||||
|
}
|
||||||
|
|
||||||
|
(futures + futures2).forEach {
|
||||||
|
// wait for all of them to finish
|
||||||
|
it.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
// versionHolders.forEach { (t, u) ->
|
||||||
|
// println("$t :: ${u.versions}")
|
||||||
|
// }
|
||||||
|
|
||||||
val mergedVersionInfo = getLatestVersionInfo(newRepos.toList(), mergedDeps)
|
|
||||||
mergedDeps.forEach { (mergedDep, existingVersions) ->
|
mergedDeps.forEach { (mergedDep, existingVersions) ->
|
||||||
val latestInfo: VersionHolder = mergedVersionInfo[mergedDep]!!
|
val versionHolder: VersionHolder = versionHolders[mergedDep]!!
|
||||||
|
|
||||||
existingVersions.forEach { dep ->
|
existingVersions.forEach { dep ->
|
||||||
if (latestInfo.release == null) {
|
if (versionHolder.release == null) {
|
||||||
unknownVersionInfo.add(dep)
|
unknownVersionInfo.add(dep)
|
||||||
} else {
|
} else {
|
||||||
if (dep.version == latestInfo.release) {
|
if (dep.version == versionHolder.release) {
|
||||||
latestVersionInfo.add(dep)
|
latestVersionInfo.add(dep)
|
||||||
} else {
|
} else {
|
||||||
oldVersionInfo.add(Pair(dep, latestInfo))
|
oldVersionInfo.add(Pair(dep, versionHolder))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -192,20 +257,29 @@ GetVersionInfoTask : DefaultTask() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (hasOld) {
|
if (hasOld) {
|
||||||
|
if (hasLatest) {
|
||||||
println()
|
println()
|
||||||
|
}
|
||||||
println("\tThe following build script dependencies need updates:")
|
println("\tThe following build script dependencies need updates:")
|
||||||
|
|
||||||
oldVersionInfo.forEach { (dep, versionHolder) ->
|
oldVersionInfo.forEach { (dep, versionHolder) ->
|
||||||
// list release version AND all other versions greater than my version
|
// list release version AND all other versions greater than my version
|
||||||
val versionOptions = versionHolder.getVersionOptions(dep.version)
|
val possibleVersionChoices = versionHolder.getVersionOptions(dep.version)
|
||||||
|
if (possibleVersionChoices.size > 1) {
|
||||||
// BUILD SCRIPT DEPS HAVE FUNNY NOTATION!
|
// BUILD SCRIPT DEPS HAVE FUNNY NOTATION!
|
||||||
println("\t - ${dep.group}:${dep.version} -> $versionOptions")
|
println("\t - ${dep.group}:${dep.version} -> ${versionHolder.release} $possibleVersionChoices")
|
||||||
|
} else {
|
||||||
|
// BUILD SCRIPT DEPS HAVE FUNNY NOTATION!
|
||||||
|
println("\t - ${dep.group}:${dep.version} -> ${versionHolder.release}")
|
||||||
|
}
|
||||||
|
// println("\t - ${dep.group}:${dep.version} -> ${versionHolder.versions}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasUnknown) {
|
if (hasUnknown) {
|
||||||
|
if (hasLatest || hasOld) {
|
||||||
println()
|
println()
|
||||||
|
}
|
||||||
println("\tThe following build script dependencies have unknown updates:")
|
println("\tThe following build script dependencies have unknown updates:")
|
||||||
unknownVersionInfo.forEach { dep ->
|
unknownVersionInfo.forEach { dep ->
|
||||||
// BUILD SCRIPT DEPS HAVE FUNNY NOTATION!
|
// BUILD SCRIPT DEPS HAVE FUNNY NOTATION!
|
||||||
@ -230,8 +304,19 @@ GetVersionInfoTask : DefaultTask() {
|
|||||||
mergedRepos.addAll(staticMethodsAndTools.getProjectRepositoryUrls(subProject))
|
mergedRepos.addAll(staticMethodsAndTools.getProjectRepositoryUrls(subProject))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// first get all version information across ALL projects.
|
// first get all version information across ALL projects.
|
||||||
val mergedVersionInfo = getLatestVersionInfo(mergedRepos.toList(), mergedDeps)
|
val (futures, versionHolders) = getLatestVersionInfo(mergedRepos.toList(), mergedDeps)
|
||||||
|
if (mergedDeps.isNotEmpty()) {
|
||||||
|
// suppress duplicate messages when initially parsing gradle scripts (since it's a redundant message )
|
||||||
|
println("\tGetting version data for ${mergedDeps.size} dependencies...")
|
||||||
|
}
|
||||||
|
|
||||||
|
futures.forEach {
|
||||||
|
// wait for all of them to finish
|
||||||
|
it.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
val latestVersionInfo = mutableListOf<DependencyScanner.Maven>()
|
val latestVersionInfo = mutableListOf<DependencyScanner.Maven>()
|
||||||
@ -255,13 +340,14 @@ GetVersionInfoTask : DefaultTask() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mergedDeps.forEach { (mergedDep, existingVersions) ->
|
mergedDeps.forEach { (mergedDep, existingVersions) ->
|
||||||
val latestInfo: VersionHolder = mergedVersionInfo[mergedDep]!!
|
val versionHolder: VersionHolder = versionHolders[mergedDep]!!
|
||||||
|
|
||||||
existingVersions.forEach { dep ->
|
existingVersions.forEach { dep ->
|
||||||
if (latestInfo.release != null) {
|
if (versionHolder.release != null) {
|
||||||
if (dep.version == latestInfo.release) {
|
if (dep.version == versionHolder.release) {
|
||||||
latestVersionInfo.add(dep)
|
latestVersionInfo.add(dep)
|
||||||
} else {
|
} else {
|
||||||
oldVersionInfo.add(Pair(dep, latestInfo))
|
oldVersionInfo.add(Pair(dep, versionHolder))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
unknownVersionInfo.add(dep)
|
unknownVersionInfo.add(dep)
|
||||||
@ -293,21 +379,26 @@ GetVersionInfoTask : DefaultTask() {
|
|||||||
|
|
||||||
|
|
||||||
if (hasOld) {
|
if (hasOld) {
|
||||||
|
if (hasLatest) {
|
||||||
println()
|
println()
|
||||||
|
}
|
||||||
println("\tThe following project$projectName dependencies need updates:")
|
println("\tThe following project$projectName dependencies need updates:")
|
||||||
|
|
||||||
oldVersionInfo.forEach { (dep, versionHolder) ->
|
oldVersionInfo.forEach { (dep, versionHolder) ->
|
||||||
// list release version AND all other versions greater than my version
|
// list release version AND all other versions greater than my version
|
||||||
val possibleVersionChoices = versionHolder.getVersionOptions(dep.version)
|
val possibleVersionChoices = versionHolder.getVersionOptions(dep.version)
|
||||||
|
if (possibleVersionChoices.size > 1) {
|
||||||
|
println("\t - ${dep.group}:${dep.name}:${dep.version} -> ${versionHolder.release} $possibleVersionChoices")
|
||||||
|
} else {
|
||||||
println("\t - ${dep.group}:${dep.name}:${dep.version} -> ${versionHolder.release}")
|
println("\t - ${dep.group}:${dep.name}:${dep.version} -> ${versionHolder.release}")
|
||||||
println("\t - ${dep.group}:${dep.name}:${dep.version} -> ${versionHolder.versions}")
|
}
|
||||||
println("\t - ${dep.group}:${dep.name}:${dep.version} -> $possibleVersionChoices")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasUnknown) {
|
if (hasUnknown) {
|
||||||
|
if (hasLatest || hasOld) {
|
||||||
println()
|
println()
|
||||||
|
}
|
||||||
println("\tThe following project$projectName dependencies have unknown updates:")
|
println("\tThe following project$projectName dependencies have unknown updates:")
|
||||||
unknownVersionInfo.forEach { dep ->
|
unknownVersionInfo.forEach { dep ->
|
||||||
println("\t - ${dep.group}:${dep.name}:${dep.version}")
|
println("\t - ${dep.group}:${dep.name}:${dep.version}")
|
||||||
|
Loading…
Reference in New Issue
Block a user