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

main.name.remal.gradle_plugins.plugins.dependencies.NebulaResolutionRules.kt Maven / Gradle / Ivy

There is a newer version: 1.9.2
Show newest version
package name.remal.gradle_plugins.plugins.dependencies

import io.github.classgraph.ClassGraph
import name.remal.gradle_plugins.dsl.extensions.notation
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.getPluginIdForLogging
import name.remal.gradle_plugins.dsl.utils.parseDependencyNotation
import name.remal.gradle_plugins.utils.JSON_OBJECT_MAPPER
import org.gradle.api.artifacts.ComponentMetadataDetails
import org.gradle.api.artifacts.ComponentSelectionRules
import org.gradle.api.artifacts.DependencySubstitutions
import org.gradle.api.artifacts.ModuleVersionIdentifier
import org.gradle.api.artifacts.component.ModuleComponentSelector
import org.gradle.api.capabilities.MutableCapabilitiesMetadata

internal object NebulaResolutionRules {

    private val logger = getGradleLogger(NebulaResolutionRules::class.java)

    private val resolutionRules: Map by lazy {
        val result = sortedMapOf()
        ClassGraph()
            .overrideClassLoaders(NebulaResolutionRules::class.java.classLoader)
            .acceptPathsNonRecursive("resolution-rules/nebula-gradle-resolution-rules")
            .scan().use {
                it.allResources.asSequence()
                    .filter { it.path.endsWith(".json") }
                    .filter { !it.path.substringAfterLast('/').let { it.startsWith('.') || it.startsWith("optional-") } }
                    .filter { !it.path.contains("/replace-bouncycastle-jdk") }
                    .forEach { resource ->
                        resource.open().use { inputStream ->
                            val rule = try {
                                JSON_OBJECT_MAPPER.readValue(inputStream, NebulaResolutionRule::class.java)
                            } catch (e: Exception) {
                                logger.warn(
                                    "Plugin {}/{}: deserialization error of {}: {}",
                                    getPluginIdForLogging(ComponentMetadataPlugin::class.java),
                                    getPluginIdForLogging(ComponentCapabilitiesPlugin::class.java),
                                    resource.pathRelativeToClasspathElement,
                                    e
                                )
                                null
                            }

                            rule?.let {
                                result[resource.path.substringAfterLast('/').substringBefore('.')] = it
                            }
                        }
                    }
            }

        if (result.isEmpty()) {
            logger.warn("Plugin {}: no Nebula Gradle Resolution Rules were parsed", getPluginIdForLogging(ComponentCapabilitiesPlugin::class.java))
        }

        return@lazy result.toMap()
    }


    fun addComponentCapabilities(id: ModuleVersionIdentifier, capabilities: MutableCapabilitiesMetadata) {
        val notation = id.notation
        resolutionRules.forEach { _, rule ->
            rule.replace.asSequence()
                .filter { it.matches(notation) }
                .forEach {
                    val replacementNotation = it.replacementNotation
                    capabilities.addCapability(replacementNotation.group, replacementNotation.module, id.version)
                }
        }
    }

    fun addComponentMetadata(id: ModuleVersionIdentifier, details: ComponentMetadataDetails) {
        val notation = id.notation
        resolutionRules.forEach { fileName, rule ->
            rule.align.asSequence()
                .filter { it.matches(notation) }
                .forEach {
                    details.belongsTo("nebula-gradle-resolution-rules:$fileName:${id.version}")
                }
        }
    }

    fun addDependencySubstitution(dependencySubstitutions: DependencySubstitutions) {
        dependencySubstitutions.all { substitution ->
            val notation = (substitution.requested as? ModuleComponentSelector)?.notation ?: return@all
            resolutionRules.forEach { (fileName, rule) ->
                val matchedRule = rule.substitute.asSequence().firstOrNull { it.matches(notation) }

                if (matchedRule != null) {
                    substitution.useTarget(
                        matchedRule.replacementNotation.toString(),
                        matchedRule.createFullReason(fileName)
                    )
                    return@all
                }
            }
        }
    }

    fun addComponentSelection(componentSelectionRules: ComponentSelectionRules) {
        componentSelectionRules.all { selection ->
            val notation = selection.candidate.notation
            resolutionRules.forEach { (fileName, rule) ->
                val matchedRule = rule.deny.asSequence().firstOrNull { it.matches(notation) }
                    ?: rule.reject.asSequence().firstOrNull { it.matches(notation) }

                if (matchedRule != null) {
                    selection.reject(matchedRule.createFullReason(fileName))
                    return@all
                }
            }
        }
    }

}

private data class NebulaResolutionRule(
    val align: List = emptyList(),
    val replace: List = emptyList(),
    val substitute: List = emptyList(),
    val deny: List = emptyList(),
    val reject: List = emptyList()
)

private interface WithReason {

    val reason: String

    fun createFullReason(fileName: String) = buildString {
        if (!reason.isBlank()) {
            append(reason).append(" (")
        }
        val isNotBlank = isNotEmpty()
        append("https://github.com/nebula-plugins/gradle-resolution-rules/blob/master/src/main/resources/").append(fileName).append(".json")
        if (isNotBlank) {
            append(')')
        }
    }

}

private data class NebulaResolutionAlign(
    val name: String? = null,
    val group: String,
    val includes: List = emptyList(),
    val excludes: List = emptyList(),
    override val reason: String = ""
) : WithReason {

    private val groupRegex = Regex(group)

    private val includeRegexps = includes.asSequence()
        .distinct()
        .map(::Regex)
        .toList()

    private val excludeRegexps = excludes.asSequence()
        .distinct()
        .map(::Regex)
        .toList()

    fun matches(notation: DependencyNotation): Boolean {
        if (!groupRegex.matches(notation.group)) return false
        if (includeRegexps.isNotEmpty() && includeRegexps.none { it.matches(notation.module) }) return false
        if (excludeRegexps.any { it.matches(notation.module) }) return false
        return true
    }

}

private data class NebulaResolutionReplace(
    val module: String,
    val with: String,
    override val reason: String = ""
) : WithReason {

    private val matcher = DependencyNotationMatcher(module)
    fun matches(notation: DependencyNotation) = matcher.matches(notation)

    val replacementNotation = parseDependencyNotation(with)

}

private data class NebulaResolutionSubstitute(
    val module: String,
    val with: String,
    override val reason: String = ""
) : WithReason {

    private val notation = parseDependencyNotation(module)
    fun matches(otherNotation: DependencyNotation) = notation == otherNotation

    val replacementNotation = parseDependencyNotation(with)

}

private data class NebulaResolutionDeny(
    val module: String,
    override val reason: String = ""
) : WithReason {

    private val matcher = DependencyNotationMatcher(module)
    fun matches(notation: DependencyNotation) = matcher.matches(notation)

}

private data class NebulaResolutionReject(
    val module: String,
    override val reason: String = ""
) : WithReason {

    private val matcher = DependencyNotationMatcher(module)
    fun matches(notation: DependencyNotation) = matcher.matches(notation)

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy