main.name.remal.gradle_plugins.plugins.dependencies.DependencyVersionsExtension.kt Maven / Gradle / Ivy
package name.remal.gradle_plugins.plugins.dependencies
import name.remal.asObservable
import name.remal.asSynchronized
import name.remal.concurrentMapOf
import name.remal.escapeRegex
import name.remal.findAll
import name.remal.gradle_plugins.api.BuildTimeConstants.getClassSimpleName
import name.remal.gradle_plugins.dsl.Extension
import name.remal.gradle_plugins.dsl.extensions.createFromNotation
import name.remal.gradle_plugins.dsl.extensions.makeNotTransitive
import name.remal.gradle_plugins.dsl.extensions.matches
import name.remal.gradle_plugins.dsl.utils.DependencyNotation
import name.remal.gradle_plugins.dsl.utils.DependencyNotationMatcher
import name.remal.gradle_plugins.dsl.utils.getGradleLogger
import name.remal.gradle_plugins.dsl.utils.parseDependencyNotation
import name.remal.version.Version
import org.gradle.api.artifacts.ConfigurationContainer
import org.gradle.api.artifacts.dsl.DependencyHandler
import org.gradle.api.artifacts.dsl.RepositoryHandler
import org.gradle.api.artifacts.repositories.MavenArtifactRepository
import org.gradle.internal.resolve.ModuleVersionNotFoundException
import java.net.URI
import java.util.Comparator.reverseOrder
import java.util.concurrent.ConcurrentMap
import java.util.concurrent.TimeUnit.SECONDS
import kotlin.text.RegexOption.IGNORE_CASE
@Extension
class DependencyVersionsExtension(
private val dependencies: DependencyHandler,
private val configurations: ConfigurationContainer,
private val repositories: RepositoryHandler
) {
companion object {
private val logger = getGradleLogger(DependencyVersionsExtension::class.java)
private val allCaches: ConcurrentMap = concurrentMapOf()
private val predefinedInvalidVersionPatterns = listOf(
Regex("maven-metadata(?:-[^.]+)?\\.xml")
)
}
private val projectCaches = Caches()
init {
repositories.whenObjectAdded { projectCaches.clear() }
repositories.whenObjectRemoved { projectCaches.clear() }
}
private fun MutableSet.asProjectCacheCleaner(): MutableSet {
return asObservable().apply {
registerCollectionChangedHandler { projectCaches.clear() }
}
}
var rejectVersions = mutableSetOf().asProjectCacheCleaner()
set(value) {
field = value.asProjectCacheCleaner()
}
fun rejectVersions(notationPattern: String) {
rejectVersions.add(notationPattern)
}
fun rejectVersions(vararg notationPatterns: String) {
rejectVersions.addAll(notationPatterns)
}
var allowAllVersionsFor = mutableSetOf().asProjectCacheCleaner()
set(value) {
field = value.asProjectCacheCleaner()
}
fun allowAllVersionsFor(notationPattern: String) {
allowAllVersionsFor.add(notationPattern)
}
fun allowAllVersionsFor(vararg notationPatterns: String) {
allowAllVersionsFor.addAll(notationPatterns)
}
var invalidVersionTokens = mutableSetOf(
"snapshot",
"nightly",
"rc", "cr",
"ea",
"milestone", "m",
"beta", "b",
"alpha", "a",
"dev",
"pr",
"redhat"
).asProjectCacheCleaner()
set(value) {
field = value.asProjectCacheCleaner()
}
fun invalidVersionToken(token: String) {
invalidVersionTokens.add(token)
}
fun invalidVersionTokens(vararg tokens: String) {
invalidVersionTokens.addAll(tokens)
}
fun getFirstInvalidToken(version: String): String? {
return invalidVersionTokens.toSortedSet(reverseOrder()).firstOrNull { Regex(".*[._-]${escapeRegex(it)}[._-]?\\d*(?:\\.\\d*)*(?:[._-].*)?", IGNORE_CASE).matches(version) }
}
fun resolveLatestVersion(notation: String): String {
return caches.resolveLatestVersionCache.computeIfAbsent(parseDependencyNotation(notation).withDefaultLatestVersion()) { dependencyNotation ->
val dependency = dependencies.createFromNotation(dependencyNotation)
val rejectMatchers = rejectVersions.mapTo(mutableSetOf(), ::DependencyNotationMatcher)
val allowMatchers = allowAllVersionsFor.mapTo(mutableSetOf(), ::DependencyNotationMatcher)
configurations.detachedConfiguration().let { conf ->
conf.resolutionStrategy { it.cacheDynamicVersionsFor(0, SECONDS); it.cacheChangingModulesFor(0, SECONDS) }
conf.makeNotTransitive()
conf.resolutionStrategy {
it.componentSelection {
it.all { selection ->
with(selection) {
val version = candidate.version
if (predefinedInvalidVersionPatterns.any { it.matches(version) }) {
reject(version)
return@all
}
rejectMatchers.firstOrNull { it.matches(candidate) }?.let { matcher ->
reject("Rejected version: $matcher")
return@all
}
if (allowMatchers.any { it.matches(candidate) }) {
return@all
}
getFirstInvalidToken(version)?.let { token ->
reject("Invalid version token: $token")
return@all
}
}
}
}
}
conf.dependencies.add(dependency)
return@computeIfAbsent conf.resolvedConfiguration.firstLevelModuleDependencies.first().moduleVersion.also {
logger.info("Dependency {} resolved with {} version", dependencyNotation, it)
}
}
}
}
fun resolveAllVersions(notation: String): List {
return caches.allVersionsCache.computeIfAbsent(parseDependencyNotation(notation).withDefaultLatestVersion()) { dependencyNotation ->
val dependency = dependencies.createFromNotation(dependencyNotation)
val rejectMatchers = rejectVersions.mapTo(mutableSetOf(), ::DependencyNotationMatcher)
val allowMatchers = allowAllVersionsFor.mapTo(mutableSetOf(), ::DependencyNotationMatcher)
val result = mutableSetOf().asSynchronized()
configurations.detachedConfiguration().also { conf ->
conf.resolutionStrategy { it.cacheDynamicVersionsFor(0, SECONDS); it.cacheChangingModulesFor(0, SECONDS) }
conf.makeNotTransitive()
conf.dependencies.add(dependency)
conf.resolutionStrategy {
it.componentSelection {
it.all { selection ->
with(selection) {
val version = candidate.version
if (predefinedInvalidVersionPatterns.any { it.matches(version) }) {
reject(version)
return@all
}
rejectMatchers.firstOrNull { it.matches(candidate) }?.let { matcher ->
reject("Rejected version: $matcher")
return@all
}
if (allowMatchers.none { it.matches(candidate) }) {
getFirstInvalidToken(version)?.let { token ->
reject("Invalid version token: $token")
return@all
}
}
result.add(version)
reject("Accepted: " + DependencyVersionsExtension::resolveAllVersions.name)
}
}
}
}
try {
conf.resolve()
} catch (e: Exception) {
if (e.isFullyIgnorable) {
// do nothing
} else if (e.isIgnorable) {
logger.warn(e.toString())
// do nothing
} else {
throw e
}
}
}
return@computeIfAbsent result.toSortedVersions()
}
}
private val caches: Caches
get() {
val uris = hashSetOf()
repositories.forEach {
if (it !is MavenArtifactRepository) {
return projectCaches
}
uris.add(it.url)
}
return allCaches.computeIfAbsent(
AllCachesKey(
rejectVersions.toSortedSet(),
allowAllVersionsFor.toSortedSet(),
invalidVersionTokens.toSortedSet(),
uris
),
{ Caches() }
)
}
private data class AllCachesKey(
val rejectVersions: Set,
val allowAllVersionsFor: Set,
val invalidVersionTokens: Set,
val repositoriesUris: Set
)
private class Caches {
val resolveLatestVersionCache: ConcurrentMap = concurrentMapOf()
val allVersionsCache: ConcurrentMap> = concurrentMapOf()
fun clear() {
resolveLatestVersionCache.clear()
allVersionsCache.clear()
}
}
private fun Collection.toSortedVersions(): List {
val versions = mapNotNullTo(mutableListOf(), Version::parseOrNull)
if (size == versions.size) {
return versions.sortedDescending().map(Version::toString)
} else {
return this.toList()
}
}
private val Throwable.isFullyIgnorable: Boolean
get() = findAll(Throwable::class.java).any {
it.javaClass.simpleName == getClassSimpleName(ModuleVersionNotFoundException::class.java)
}
private val Throwable.isIgnorable: Boolean
get() = isFullyIgnorable || findAll(Throwable::class.java).any {
false
}
}
fun DependencyVersionsExtension.resolveLatestVersion(notation: DependencyNotation) = resolveLatestVersion(notation.toString())
fun DependencyVersionsExtension.resolveAllVersions(notation: DependencyNotation) = resolveAllVersions(notation.toString())
inline fun DependencyVersionsExtension.withoutFilters(action: DependencyVersionsExtension.() -> R): R {
val rejectVersionsPrev = rejectVersions.toSet()
val allowAllVersionsForPrev = allowAllVersionsFor.toSet()
val invalidVersionTokensPrev = invalidVersionTokens.toSet()
rejectVersions = mutableSetOf()
allowAllVersionsFor = mutableSetOf()
invalidVersionTokens = mutableSetOf()
try {
return action(this)
} finally {
rejectVersions = rejectVersionsPrev.toMutableSet()
allowAllVersionsFor = allowAllVersionsForPrev.toMutableSet()
invalidVersionTokens = invalidVersionTokensPrev.toMutableSet()
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy