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

main.name.remal.gradle_plugins.plugins.publish.MavenPublishSettingsPlugin.kt Maven / Gradle / Ivy

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

import name.remal.appendElement
import name.remal.asString
import name.remal.debug
import name.remal.getChildElements
import name.remal.gradle_plugins.dsl.AfterProjectEvaluation
import name.remal.gradle_plugins.dsl.ApplyPluginClasses
import name.remal.gradle_plugins.dsl.ApplyPlugins
import name.remal.gradle_plugins.dsl.BaseReflectiveProjectPlugin
import name.remal.gradle_plugins.dsl.BuildTask
import name.remal.gradle_plugins.dsl.CreateExtensionsPluginAction
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.WithPlugins
import name.remal.gradle_plugins.dsl.extensions.ResolvedDependencyMapping
import name.remal.gradle_plugins.dsl.extensions.addPlugin
import name.remal.gradle_plugins.dsl.extensions.afterEvaluateOrNow
import name.remal.gradle_plugins.dsl.extensions.all
import name.remal.gradle_plugins.dsl.extensions.atTheEndOfAfterEvaluationOrNow
import name.remal.gradle_plugins.dsl.extensions.compileClasspath
import name.remal.gradle_plugins.dsl.extensions.contains
import name.remal.gradle_plugins.dsl.extensions.convention
import name.remal.gradle_plugins.dsl.extensions.dependsOn
import name.remal.gradle_plugins.dsl.extensions.forEach
import name.remal.gradle_plugins.dsl.extensions.get
import name.remal.gradle_plugins.dsl.extensions.getResolvedDependencyMappings
import name.remal.gradle_plugins.dsl.extensions.invoke
import name.remal.gradle_plugins.dsl.extensions.isPluginApplied
import name.remal.gradle_plugins.dsl.extensions.mustRunAfter
import name.remal.gradle_plugins.dsl.extensions.runtimeClasspath
import name.remal.gradle_plugins.dsl.extensions.setupTasksDependenciesAfterEvaluateOrNow
import name.remal.gradle_plugins.dsl.extensions.useDefault
import name.remal.gradle_plugins.plugins.common.CommonSettingsPlugin
import name.remal.gradle_plugins.plugins.java.JavaPluginId
import name.remal.plusAssign
import name.remal.remove
import name.remal.setNoOpEntityResolver
import name.remal.setNoValidatingXMLReaderFactory
import org.gradle.api.DefaultTask
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ConfigurationContainer
import org.gradle.api.artifacts.Dependency.ARCHIVES_CONFIGURATION
import org.gradle.api.artifacts.DependencyArtifact.DEFAULT_TYPE
import org.gradle.api.artifacts.ExcludeRule
import org.gradle.api.artifacts.ModuleDependency
import org.gradle.api.artifacts.dsl.ArtifactHandler
import org.gradle.api.artifacts.repositories.ArtifactRepository
import org.gradle.api.plugins.BasePlugin.BUILD_GROUP
import org.gradle.api.plugins.ExtensionContainer
import org.gradle.api.plugins.JavaBasePlugin.BUILD_TASK_NAME
import org.gradle.api.plugins.JavaPlugin.JAR_TASK_NAME
import org.gradle.api.publish.PublishingExtension
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.api.publish.maven.plugins.MavenPublishPlugin.PUBLISH_LOCAL_LIFECYCLE_TASK_NAME
import org.gradle.api.publish.maven.tasks.AbstractPublishToMaven
import org.gradle.api.publish.maven.tasks.PublishToMavenRepository
import org.gradle.api.publish.plugins.PublishingPlugin.PUBLISH_LIFECYCLE_TASK_NAME
import org.gradle.api.publish.plugins.PublishingPlugin.PUBLISH_TASK_GROUP
import org.gradle.api.tasks.SourceSetContainer
import org.gradle.api.tasks.TaskCollection
import org.gradle.api.tasks.TaskContainer
import org.jdom2.input.SAXBuilder
import org.w3c.dom.Element
import org.w3c.dom.Node
import java.io.StringReader
import java.util.TreeSet
import kotlin.LazyThreadSafetyMode.NONE

const val BUILD_INSTALL_TASK_NAME = "buildInstall"

@Plugin(
    id = "name.remal.maven-publish-settings",
    description = "Plugin that configures 'maven-publish' plugin if it's applied.",
    tags = ["java", "publish", "publication", "maven", "maven-publish"]
)
@WithPlugins(MavenPublishPluginId::class)
@ApplyPlugins(JavaPluginId::class)
@ApplyPluginClasses(CommonSettingsPlugin::class)
@SimpleTestAdditionalGradleScript(
    """
    publishing.publications {
        mavenDefault()
        mavenBom()
    }
"""
)
class MavenPublishSettingsPlugin : BaseReflectiveProjectPlugin() {

    companion object {
        private const val DEFAULT_SCOPE = "compile"
    }


    @PluginAction
    fun ArtifactHandler.`Add 'jar' task to 'archives' configuration artifacts`(tasks: TaskContainer) {
        add(ARCHIVES_CONFIGURATION, tasks[JAR_TASK_NAME])
    }

    private val TaskContainer.publishToMavenTasks: TaskCollection
        get() = matching {
            it is AbstractPublishToMaven
        }

    @PluginAction
    fun TaskContainer.`Make all publish tasks depend on 'build' task`(tasks: TaskContainer) {
        publishToMavenTasks.all { task ->
            task.dependsOn {
                sequenceOf(BUILD_TASK_NAME)
                    .mapNotNull(tasks::findByName)
                    .toList()
            }
        }
    }

    @PluginAction
    fun TaskContainer.`Make all publish tasks run after 'build' task of all projects`(project: Project) {
        publishToMavenTasks.all { publishTask ->
            publishTask.mustRunAfter {
                project.rootProject.allprojects.asSequence()
                    .filter { it.isPluginApplied(JavaPluginId) }
                    .mapNotNull { it.tasks.findByName(BUILD_TASK_NAME) }
                    .toList()
            }
        }
    }

    @PluginAction(order = -10)
    fun TaskContainer.`Create 'buildInstall' task`(tasks: TaskContainer) {
        create(BUILD_INSTALL_TASK_NAME, BuildInstall::class.java) { task ->
            task.group = BUILD_GROUP
            task.dependsOn {
                sequenceOf(BUILD_TASK_NAME, PUBLISH_LOCAL_LIFECYCLE_TASK_NAME)
                    .mapNotNull(tasks::findByName)
                    .toList()
            }
        }
    }

    @PluginAction(order = 101)
    @Suppress("ComplexMethod", "LongMethod")
    fun ExtensionContainer.`Setup POM file dependencies`(configurations: ConfigurationContainer, project: Project) {
        invoke(PublishingExtension::class.java) {
            it.publications.all(MavenPublication::class.java) {
                it.pom.withXml {
                    val root: Element = it.asElement()

                    root.getChildElements("dependencyManagement").forEach { it.remove() }
                    root.getChildElements("dependencies").forEach { it.remove() }

                    val managementNode: Element by lazy(NONE) { root.appendElement("dependencyManagement").appendElement("dependencies") }
                    val dependenciesNode: Element by lazy(NONE) { root.appendElement("dependencies") }

                    fun addMavenDependencies(scope: String, configuration: Configuration, dependencyConfigurationFilter: (configuration: Configuration) -> Boolean) {
                        val resolvedDependencyMappings = configuration.getResolvedDependencyMappings(project)
                            .filter { dependencyConfigurationFilter(it.configuration) }

                        val shouldBeExplicitlyDefined = resolvedDependencyMappings
                            .filter(ResolvedDependencyMapping::isShouldBeExplicitlyDefined)
                            .filter { it.dependency is ModuleDependency }

                        val commonExclusions = resolvedDependencyMappings
                            .filter(ResolvedDependencyMapping::isSubstituted)
                            .filter { it.dependency is ModuleDependency }

                        operator fun Node.plusAssign(resolvedDependencyMapping: ResolvedDependencyMapping) {
                            appendElement("groupId") += resolvedDependencyMapping.group
                            appendElement("artifactId") += resolvedDependencyMapping.module
                            resolvedDependencyMapping.classifier.let { classifier ->
                                if (classifier.isNotEmpty()) appendElement("classifier") += classifier
                            }
                            resolvedDependencyMapping.type.let { type ->
                                if (type.isNotEmpty() && DEFAULT_TYPE != type) appendElement("type") += type
                            }
                        }

                        shouldBeExplicitlyDefined.forEach { resolvedDependencyMapping ->
                            managementNode.appendElement("dependency").apply {
                                this += resolvedDependencyMapping
                                appendElement("version") += resolvedDependencyMapping.version

                                TreeSet(Comparator(::compareExcludeRules))
                                    .apply excludeRulesApply@{
                                        resolvedDependencyMapping.dependency.let { dependency ->
                                            if (dependency is ModuleDependency) {
                                                if (!dependency.isTransitive) {
                                                    add(ExcludeRuleImpl.create(null, null))
                                                    return@excludeRulesApply
                                                }
                                            }
                                        }
                                        resolvedDependencyMapping.dependency.let { dependency ->
                                            if (dependency is ModuleDependency) {
                                                dependency.excludeRules.forEach {
                                                    add(ExcludeRuleImpl.create(it.group, it.module))
                                                }
                                            }
                                        }
                                        commonExclusions.forEach {
                                            add(ExcludeRuleImpl.create(it.requestedGroup, it.requestedModule))
                                        }
                                    }
                                    .filter { it.group != resolvedDependencyMapping.group || it.module != resolvedDependencyMapping.module }
                                    .let { excludeRules ->
                                        if (excludeRules.isNotEmpty()) {
                                            appendElement("exclusions").apply {
                                                excludeRules.forEach { excludeRule ->
                                                    appendElement("exclusion").apply {
                                                        appendElement("groupId") += excludeRule.group
                                                        appendElement("artifactId") += excludeRule.module
                                                    }
                                                }
                                            }
                                        }
                                    }
                            }

                            if (resolvedDependencyMapping.isFirstLevel || resolvedDependencyMapping.isSubstituted) {
                                dependenciesNode.appendElement("dependency").apply {
                                    this += resolvedDependencyMapping
                                    if (scope != DEFAULT_SCOPE) {
                                        appendElement("scope") += scope
                                    }
                                }
                            }
                        }
                    }

                    val compileClasspath = configurations.compileClasspath
                    val runtimeClasspath = configurations.runtimeClasspath

                    run {
                        val runtimeClasspathHierarchy = runtimeClasspath.hierarchy
                        addMavenDependencies(
                            "compile",
                            compileClasspath,
                            { it in runtimeClasspathHierarchy }
                        )
                    }

                    run {
                        val compileClasspathHierarchy = compileClasspath.hierarchy
                        addMavenDependencies(
                            "runtime",
                            runtimeClasspath,
                            { it !in compileClasspathHierarchy }
                        )
                    }
                }
            }
        }
    }

    @PluginAction(order = 102)
    fun ExtensionContainer.`Pretty print POM xml`(project: Project) {
        project.atTheEndOfAfterEvaluationOrNow { _ ->
            invoke(PublishingExtension::class.java) {
                it.publications.forEach(MavenPublication::class.java) {
                    it.pom.withXml {
                        try {
                            val stringBuilder = it.asString()

                            val saxBuilder = SAXBuilder().apply {
                                setNoValidatingXMLReaderFactory()
                                setNoOpEntityResolver()
                            }
                            val document = saxBuilder.build(StringReader(stringBuilder.toString()))

                            stringBuilder.setLength(0)
                            stringBuilder.append(document.document.asString(true))

                        } catch (e: Exception) {
                            logger.debug(e)
                        }
                    }
                }
            }
        }
    }

    @PluginAction
    @AfterProjectEvaluation
    fun ExtensionContainer.`Setup POM packaging from artifacts`(project: Project) {
        invoke(PublishingExtension::class.java) {
            it.publications.all(MavenPublication::class.java) { publication ->
                publication.artifacts.singleOrNull { it.classifier.isNullOrEmpty() }?.let { mainArtifact ->
                    val extension = mainArtifact.extension
                    if (!extension.isNullOrEmpty()) {
                        publication.pom.packaging = extension
                    }
                }
            }
        }
    }

    @CreateExtensionsPluginAction("Add ${PublishingExtension.NAME}.publications extension methods")
    fun ExtensionContainer.addPublicationsExtensionMethods(tasks: TaskContainer, sourceSets: SourceSetContainer, project: Project) {
        invoke(PublishingExtension::class.java) {
            it.publications.apply {
                convention.addPlugin(MavenPublicationsExtensions(this, tasks, sourceSets, project))
            }
        }
    }

    @PluginAction
    fun Project.`Create publish-to-repository tasks`(tasks: TaskContainer) {
        setupTasksDependenciesAfterEvaluateOrNow { _ ->
            fun forRepositoryTasks(repository: ArtifactRepository, publishTasks: List) {
                val publishToRepositoryTaskName = "publishTo" + repository.name.capitalize()
                if (publishToRepositoryTaskName !in tasks) {
                    tasks.create(publishToRepositoryTaskName) { task ->
                        tasks.all(PUBLISH_LIFECYCLE_TASK_NAME) { it.dependsOn(task) }
                        task.dependsOn(publishTasks)
                        task.group = PUBLISH_TASK_GROUP
                    }
                }
            }

            tasks.asSequence()
                .filterIsInstance(PublishToMavenRepository::class.java)
                .groupBy(PublishToMavenRepository::getRepository)
                .forEach { repository, publishTasks ->
                    forRepositoryTasks(repository, publishTasks)
                }

            tasks.asSequence()
                .filterIsInstance(BasePublishToMavenRepository::class.java)
                .groupBy(BasePublishToMavenRepository<*>::repository)
                .forEach { repository, publishTasks ->
                    forRepositoryTasks(repository, publishTasks)
                }
        }
    }

    @PluginAction
    fun ExtensionContainer.`Setup default maven publication`(project: Project) {
        project.afterEvaluateOrNow {
            invoke(PublishingExtension::class.java) {
                it.publications.useDefault(MavenPublication::class.java) {
                    convention[MavenPublicationsExtensions::class.java].mavenDefault()
                }
            }
        }
    }

}


private fun String?.defaultExcludeRule(): String = if (this == null || this.isEmpty()) "*" else this

private fun compareExcludeRules(o1: ExcludeRule, o2: ExcludeRule): Int {
    o1.group.defaultExcludeRule().compareTo(o2.group.defaultExcludeRule()).let { if (it != 0) return it }
    o1.module.defaultExcludeRule().compareTo(o2.module.defaultExcludeRule()).let { if (it != 0) return it }
    return 0
}

private data class ExcludeRuleImpl private constructor(val groupId: String, val artifactId: String) : ExcludeRule {

    companion object {
        fun create(groupId: String?, artifactId: String?): ExcludeRule = ExcludeRuleImpl(
            groupId.defaultExcludeRule(),
            artifactId.defaultExcludeRule()
        )
    }

    override fun getGroup() = groupId
    override fun getModule() = artifactId

}


@BuildTask
private class BuildInstall : DefaultTask()




© 2015 - 2024 Weber Informatics LLC | Privacy Policy