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

nebula.plugin.resolutionrules.plugin.kt Maven / Gradle / Ivy

There is a newer version: 11.4.1
Show newest version
/*
 * Copyright 2015-2016 Netflix, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package nebula.plugin.resolutionrules

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import com.netflix.nebula.dependencybase.DependencyBasePlugin
import com.netflix.nebula.dependencybase.DependencyManagement
import com.netflix.nebula.interop.onExecute
import com.netflix.nebula.interop.onResolve
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ConfigurationContainer
import org.gradle.api.logging.Logger
import org.gradle.api.logging.Logging
import java.io.File
import java.util.*
import java.util.zip.ZipEntry
import java.util.zip.ZipFile

const val RESOLUTION_RULES_CONFIG_NAME = "resolutionRules"

class ResolutionRulesPlugin : Plugin {
    private val logger: Logger = Logging.getLogger(ResolutionRulesPlugin::class.java)
    private val ruleSet: RuleSet by lazy {
        rulesFromConfiguration(project, extension)
    }

    private lateinit var project: Project
    private lateinit var configurations: ConfigurationContainer
    private lateinit var extension: NebulaResolutionRulesExtension
    private lateinit var mapper: ObjectMapper
    private lateinit var insight: DependencyManagement

    companion object Constants {
        const val SPRING_VERSION_MANAGEMENT_CONFIG_NAME = "versionManagement"
        const val JSON_EXT = ".json"
        const val JAR_EXT = ".jar"
        const val ZIP_EXT = ".zip"
        const val OPTIONAL_PREFIX = "optional-"
    }

    override fun apply(project: Project) {
        this.project = project
        project.plugins.apply(DependencyBasePlugin::class.java)
        configurations = project.configurations
        insight = project.extensions.extraProperties.get("nebulaDependencyBase") as DependencyManagement
        extension = project.extensions.create("nebulaResolutionRules", NebulaResolutionRulesExtension::class.java)
        mapper = objectMapper()

        val rootProject = project.rootProject
        rootProject.configurations.maybeCreate(RESOLUTION_RULES_CONFIG_NAME)
        if (rootProject.extensions.findByType(NebulaResolutionRulesExtension::class.java) == null) {
            rootProject.extensions.create("nebulaResolutionRules", NebulaResolutionRulesExtension::class.java)
        }

        project.configurations.all { config ->
            if (config.name == RESOLUTION_RULES_CONFIG_NAME || config.name == SPRING_VERSION_MANAGEMENT_CONFIG_NAME) {
                return@all
            }

            var dependencyRulesApplied = false
            project.onExecute {
                when {
                    config.state != Configuration.State.UNRESOLVED -> logger.warn("Dependency resolution rules will not be applied to $config, it was resolved before the project was executed")
                    config.allDependencies.isEmpty() -> logger.debug("Skipping dependency rules for $config - No dependencies are configured")
                    else -> {
                        ruleSet.dependencyRules().forEach { rule ->
                            rule.apply(project, config, config.resolutionStrategy, extension, insight)
                        }
                        dependencyRulesApplied = true
                    }
                }
            }

            config.onResolve {
                if (config.allDependencies.isEmpty()) {
                    logger.debug("Skipping resolve rules for $config - No dependencies are configured")
                } else if (!dependencyRulesApplied) {
                    logger.debug("Skipping resolve rules for $config - dependency rules have not been applied")
                } else {
                    ruleSet.resolveRules().forEach { rule ->
                        rule.apply(project, config, config.resolutionStrategy, extension, insight)
                    }
                }
            }
        }
    }

    private fun rulesFromConfiguration(project: Project, extension: NebulaResolutionRulesExtension): RuleSet {
        val rules = LinkedHashMap()
        val files = extension.ruleFiles(project)
        for (file in files) {
            insight.addPluginMessage("nebula.resolution-rules uses: ${file.name}")
            if (isIncludedRuleFile(file.name, extension)) {
                rules.putRules(parseJsonFile(file))
            } else if (file.name.endsWith(JAR_EXT) || file.name.endsWith(ZIP_EXT)) {
                ZipFile(file).use { zip ->
                    val entries = zip.entries()
                    while (entries.hasMoreElements()) {
                        val entry = entries.nextElement()
                        if (isIncludedRuleFile(entry.name, extension)) {
                            rules.putRules(parseJsonStream(zip, entry))
                        }
                    }
                }
            } else {
                logger.debug("Unsupported rules file extension for $file")
            }
        }
        return rules.values.flatten()
    }

    private fun MutableMap.putRules(ruleSet: RuleSet) {
        if (put(ruleSet.name!!, ruleSet) != null) {
            logger.info("Found rules with the same name. Overriding existing ruleset ${ruleSet.name}")
        }
    }

    private fun isIncludedRuleFile(filename: String, extension: NebulaResolutionRulesExtension): Boolean {
        if (filename.endsWith(JSON_EXT)) {
            val ruleSet = ruleSetName(filename)
            return if (ruleSet.startsWith(OPTIONAL_PREFIX)) {
                val ruleSetWithoutPrefix = ruleSet.substring(OPTIONAL_PREFIX.length)
                extension.optional.contains(ruleSetWithoutPrefix)
            } else if (!extension.include.isEmpty()) {
                extension.include.contains(ruleSet)
            } else {
                !extension.exclude.contains(ruleSet)
            }
        }
        return false
    }

    private fun ruleSetName(filename: String) = filename.substring(0, filename.lastIndexOf(JSON_EXT))

    private fun parseJsonFile(file: File): RuleSet {
        val ruleSetName = ruleSetName(file.name)
        logger.debug("Using $ruleSetName (${file.name}) a dependency rules source")
        return mapper.readValue(file).withName(ruleSetName)
    }

    private fun parseJsonStream(zip: ZipFile, entry: ZipEntry): RuleSet {
        val ruleSetName = ruleSetName(File(entry.name).name)
        logger.debug("Using $ruleSetName (${zip.name}) a dependency rules source")
        return mapper.readValue(zip.getInputStream(entry)).withName(ruleSetName)
    }
}

open class NebulaResolutionRulesExtension {
    var include = ArrayList()
    var optional = ArrayList()
    var exclude = ArrayList()

    private lateinit var rootProject: Project
    private val ruleFiles by lazy {
        val configuration = rootProject.configurations.getByName(RESOLUTION_RULES_CONFIG_NAME)
        rootProject.copyConfiguration(configuration).resolve()
    }

    fun ruleFiles(project: Project): Set {
        return if (project == project.rootProject) {
            rootProject = project
            ruleFiles
        } else {
            project.rootProject.extensions.getByType(NebulaResolutionRulesExtension::class.java).ruleFiles(project.rootProject)
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy