main.name.remal.gradle_plugins.plugins.dependencies.NebulaResolutionRules.kt Maven / Gradle / Ivy
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