All Downloads are FREE. Search and download functionalities are using the official Maven repository.

main.name.remal.gradle_plugins.plugins.check_updates.CheckDependencyUpdatesPlugin.kt Maven / Gradle / Ivy

There is a newer version: 1.9.2
Show newest version
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