main.name.remal.gradle_plugins.plugins.check_updates.CheckDependencyUpdatesPlugin.kt Maven / Gradle / Ivy
package name.remal.gradle_plugins.plugins.check_updates
import name.remal.findAll
import name.remal.gradle_plugins.api.BuildTimeConstants.getClassSimpleName
import name.remal.gradle_plugins.dsl.ApplyPluginClasses
import name.remal.gradle_plugins.dsl.BaseReflectiveProjectPlugin
import name.remal.gradle_plugins.dsl.BuildTask
import name.remal.gradle_plugins.dsl.CreateConfigurationsPluginAction
import name.remal.gradle_plugins.dsl.GradleEnumVersion.GRADLE_VERSION_4_10
import name.remal.gradle_plugins.dsl.Plugin
import name.remal.gradle_plugins.dsl.PluginAction
import name.remal.gradle_plugins.dsl.SimpleTestAdditionalGradleScript
import name.remal.gradle_plugins.dsl.extensions.classifier
import name.remal.gradle_plugins.dsl.extensions.compareTo
import name.remal.gradle_plugins.dsl.extensions.createFromNotation
import name.remal.gradle_plugins.dsl.extensions.extension
import name.remal.gradle_plugins.dsl.extensions.get
import name.remal.gradle_plugins.dsl.extensions.getOrCreate
import name.remal.gradle_plugins.dsl.extensions.getOrNull
import name.remal.gradle_plugins.dsl.extensions.logDebug
import name.remal.gradle_plugins.dsl.extensions.logLifecycle
import name.remal.gradle_plugins.dsl.extensions.logTrace
import name.remal.gradle_plugins.dsl.extensions.makeNotTransitive
import name.remal.gradle_plugins.dsl.extensions.matches
import name.remal.gradle_plugins.dsl.extensions.notation
import name.remal.gradle_plugins.dsl.extensions.skipIfOffline
import name.remal.gradle_plugins.dsl.utils.DependencyNotation
import name.remal.gradle_plugins.dsl.utils.DependencyNotationMatcher
import name.remal.gradle_plugins.plugins.dependencies.DependencyVersionsExtension
import name.remal.gradle_plugins.plugins.dependencies.DependencyVersionsPlugin
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ConfigurationContainer
import org.gradle.api.artifacts.Dependency
import org.gradle.api.artifacts.ExternalModuleDependency
import org.gradle.api.plugins.HelpTasksPlugin.HELP_GROUP
import org.gradle.api.tasks.Console
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.TaskContainer
import org.gradle.internal.resolve.ModuleVersionNotFoundException
import org.gradle.util.GradleVersion
const val CHECK_DEPENDENCY_UPDATES_TASK_NAME = "checkDependencyUpdates"
const val CHECK_DEPENDENCY_UPDATES_CONFIGURATION_NAME = "checkDependencyUpdates"
@Deprecated(message = "Use automatic dependency updates software like Renovate, Dependabot, etc...")
@Plugin(
id = "name.remal.check-dependency-updates",
description = "Plugin that provides task for discovering dependency updates",
tags = ["versions", "dependency-updates"]
)
@ApplyPluginClasses(DependencyVersionsPlugin::class)
@SimpleTestAdditionalGradleScript(
"""
project.setDefaultTasks(project.getDefaultTasks() + ['$CHECK_DEPENDENCY_UPDATES_TASK_NAME'])
dependencies {
$CHECK_DEPENDENCY_UPDATES_CONFIGURATION_NAME 'junit:junit:4.0'
}
"""
)
class CheckDependencyUpdatesPlugin : BaseReflectiveProjectPlugin() {
@CreateConfigurationsPluginAction
fun ConfigurationContainer.`Create 'checkDependencyUpdates' configuration for project and all subprojects`() {
getOrCreate(CHECK_DEPENDENCY_UPDATES_CONFIGURATION_NAME) {
it.isCanBeConsumed = false
it.description = "Configuration for check dependency updates"
it.makeNotTransitive()
}
}
@PluginAction
fun TaskContainer.`Create 'checkDependencyUpdates' task`() {
create(CHECK_DEPENDENCY_UPDATES_TASK_NAME, CheckDependencyUpdates::class.java)
}
}
val ConfigurationContainer.checkDependencyUpdates: Configuration get() = this[CHECK_DEPENDENCY_UPDATES_CONFIGURATION_NAME]
@BuildTask
class CheckDependencyUpdates : DefaultTask() {
companion object {
private val isVersionConstraintStrictVersionAvailable = GradleVersion.current() >= GRADLE_VERSION_4_10
}
init {
group = HELP_GROUP
description = "Displays dependency updates for the project"
skipIfOffline()
}
@Console
var failIfUpdatesFound: Boolean = false
@Input
@get:Optional
var notCheckedDependencies: MutableSet = mutableSetOf()
@Input
@get:Optional
var checkAllVersionsDependencies: MutableSet = mutableSetOf()
private val dependencyVersions = project[DependencyVersionsExtension::class.java]
@TaskAction
protected fun doCheckDependencyUpdatesSequentially() {
synchronized(CheckDependencyUpdates::class.java) {
doCheckDependencyUpdates()
}
}
@Suppress("ComplexMethod", "LongMethod")
private fun doCheckDependencyUpdates() {
val skipMatchers = notCheckedDependencies.mapTo(mutableSetOf(), ::DependencyNotationMatcher)
project.extensions.getOrNull(DependencyVersionsExtension::class.java)
?.rejectVersions
?.mapTo(skipMatchers, ::DependencyNotationMatcher)
val allVersionsMatchers = checkAllVersionsDependencies.mapTo(mutableSetOf(), ::DependencyNotationMatcher)
project.extensions.getOrNull(DependencyVersionsExtension::class.java)
?.allowAllVersionsFor
?.mapTo(allVersionsMatchers, ::DependencyNotationMatcher)
val resolvedNewVersionNotations = sortedMapOf()
sequenceOf(
project.buildscript.configurations,
project.configurations
).forEach forEachConfigurationContainer@{ configurations ->
val processedNotations = hashSetOf()
val notations = configurations.asSequence()
.map {
it.copy().apply {
isCanBeResolved = true
dependencies.retainAll { it.doCheckForNewVersions }
dependencies.retainAll { dep -> skipMatchers.none { it.matches(dep) } }
}
}
.filter { it.dependencies.isNotEmpty() }
.onEach { logTrace("Processing configuration: {}", it) }
.flatMap { it.resolvedConfiguration.lenientConfiguration.getFirstLevelModuleDependencies { it.doCheckForNewVersions }.asSequence() }
.map { dep ->
DependencyNotation(
group = dep.moduleGroup,
module = dep.moduleName,
version = dep.moduleVersion,
classifier = dep.classifier,
extension = dep.extension
)
}
.filter(processedNotations::add)
.filter { notation -> skipMatchers.none { it.matches(notation) } }
.toSet()
if (notations.isEmpty()) return@forEachConfigurationContainer
notations.forEach forEachNotation@{ notation ->
val notationToResolve = notation.withLatestVersion()
val dependency = project.dependencies.createFromNotation(notationToResolve)
val resolveConf = configurations.detachedConfiguration(dependency).also {
it.makeNotTransitive()
it.resolutionStrategy {
it.componentSelection {
it.all { selection ->
with(selection) {
skipMatchers.firstOrNull { it.matches(candidate) }?.let {
reject(it.toString())
return@all
}
if (allVersionsMatchers.any { it.matches(candidate) }) {
return@all
}
dependencyVersions.getFirstInvalidToken(candidate.version)?.let { token ->
reject("Invalid version token: $token")
return@all
}
}
}
}
}
}
val lenientConfiguration = try {
resolveConf.resolvedConfiguration.lenientConfiguration
} catch (e: Throwable) {
if (e.isFullyIgnorable) {
// do nothing
return@forEachNotation
} else {
throw e
}
}
lenientConfiguration.firstLevelModuleDependencies.forEach {
val resolvedNotation = it.notation
if (notation.compareVersions(resolvedNotation) < 0) {
val keyNotation = notation.withoutClassifier().withoutExtension()
val normalizedNotation = resolvedNotation.withoutClassifier().withoutExtension()
resolvedNewVersionNotations.compute(keyNotation) { _, prev ->
if (prev == null || prev.compareVersions(normalizedNotation) < 0) {
normalizedNotation
} else {
prev
}
}
}
}
return@forEachNotation
}
}
resolvedNewVersionNotations.forEach { notation, resolvedNotation ->
if (notation.group == resolvedNotation.group && notation.module == resolvedNotation.module) {
logLifecycle("New dependency version: {}: {} -> {}", notation.withoutVersion(), notation.version, resolvedNotation.version)
} else {
logLifecycle("New dependency version: {} -> {}", notation, resolvedNotation)
}
}
if (failIfUpdatesFound) {
if (resolvedNewVersionNotations.isNotEmpty()) {
throw GradleException("${resolvedNewVersionNotations.size} new dependency versions found")
}
}
didWork = true
}
private val Dependency.doCheckForNewVersions: Boolean
get() {
if (this !is ExternalModuleDependency) {
logDebug("Skipping as it's not instance of ExternalModuleDependency: {}", this)
return false
}
if (isForce) {
logDebug("Skipping as version of this dependency should be enforced: {}", this)
return false
}
if (isVersionConstraintStrictVersionAvailable) {
if (!versionConstraint.strictVersion.isNullOrEmpty()) {
logDebug("Skipping as version of this dependency should be enforced by strict version constraint: {}", this)
return false
}
}
if (version == "+") {
logDebug("Skipping as the latest version is used (+): {}", this)
return false
}
return true
}
private val Throwable.isFullyIgnorable: Boolean
get() = findAll(Throwable::class.java).any {
it.javaClass.simpleName == getClassSimpleName(ModuleVersionNotFoundException::class.java)
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy