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

main.name.remal.gradle_plugins.plugins.classes_relocation.ClassesRelocationPlugin.kt Maven / Gradle / Ivy

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

import name.remal.forceDelete
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.CreateExtensionsPluginAction
import name.remal.gradle_plugins.dsl.LowestPriorityPluginAction
import name.remal.gradle_plugins.dsl.Plugin
import name.remal.gradle_plugins.dsl.PluginAction
import name.remal.gradle_plugins.dsl.extensions.all
import name.remal.gradle_plugins.dsl.extensions.beforeResolve
import name.remal.gradle_plugins.dsl.extensions.compileClasspath
import name.remal.gradle_plugins.dsl.extensions.compileOnly
import name.remal.gradle_plugins.dsl.extensions.dependsOn
import name.remal.gradle_plugins.dsl.extensions.dependsOnIf
import name.remal.gradle_plugins.dsl.extensions.doSetup
import name.remal.gradle_plugins.dsl.extensions.get
import name.remal.gradle_plugins.dsl.extensions.getOrNull
import name.remal.gradle_plugins.dsl.extensions.isCompilingSourceSet
import name.remal.gradle_plugins.dsl.extensions.isPluginApplied
import name.remal.gradle_plugins.dsl.extensions.logDebug
import name.remal.gradle_plugins.dsl.extensions.runtimeClasspath
import name.remal.gradle_plugins.plugins.classes_processing.ClassesProcessingPlugin
import name.remal.gradle_plugins.plugins.java.JavaPluginId
import name.remal.nullIf
import name.remal.startsWith
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.ProjectDependency
import org.gradle.api.artifacts.dsl.DependencyHandler
import org.gradle.api.file.DuplicatesStrategy.EXCLUDE
import org.gradle.api.plugins.ExtensionContainer
import org.gradle.api.plugins.JavaPlugin.JAR_TASK_NAME
import org.gradle.api.tasks.AbstractCopyTask
import org.gradle.api.tasks.ClasspathNormalizer
import org.gradle.api.tasks.Copy
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME
import org.gradle.api.tasks.SourceSetContainer
import org.gradle.api.tasks.TaskContainer
import org.gradle.api.tasks.compile.AbstractCompile
import java.io.File
import java.nio.charset.StandardCharsets.UTF_8

private const val RELOCATE_CLASSES_CONFIGURATION_NAME = "relocateClasses"
private const val EXCLUDE_FROM_FORCED_CLASSES_RELOCATION_CONFIGURATION_NAME = "excludeFromForcedClassesRelocation"
private const val EXCLUDE_FROM_CLASSES_RELOCATION_CONFIGURATION_NAME = "excludeFromClassesRelocation"

@Plugin(
    id = "name.remal.classes-relocation",
    description = "Plugin that provides classes relocating functionality",
    tags = ["java", "relocation", "shadow"]
)
@ApplyPlugins(JavaPluginId::class)
@ApplyPluginClasses(ClassesProcessingPlugin::class)
class ClassesRelocationPlugin : BaseReflectiveProjectPlugin() {

    @CreateExtensionsPluginAction
    fun ExtensionContainer.`Create 'classesRelocation' extension`(project: Project) {
        create("classesRelocation", ClassesRelocationExtension::class.java, project)
    }

    @PluginAction(order = -1003)
    fun ConfigurationContainer.`Create 'relocateClasses' configuration`() {
        create(RELOCATE_CLASSES_CONFIGURATION_NAME) {
            compileOnly.extendsFrom(it)
        }
    }

    @PluginAction(order = -1002)
    fun ConfigurationContainer.`Create 'excludeFromForcedClassesRelocation' configuration`(dependencies: DependencyHandler) {
        create(EXCLUDE_FROM_FORCED_CLASSES_RELOCATION_CONFIGURATION_NAME) {
            it.extendsFrom(runtimeClasspath)
        }
    }

    @PluginAction(order = -1001)
    fun ConfigurationContainer.`Create 'excludeFromClassesRelocation' configuration`() {
        create(EXCLUDE_FROM_CLASSES_RELOCATION_CONFIGURATION_NAME) {
            it.extendsFrom(excludeFromForcedClassesRelocation)
        }
    }

    @PluginAction(order = -1000)
    fun ConfigurationContainer.`Make 'excludeFromClassesRelocation excluding 'compileClasspath'`() {
        this.excludeFromClassesRelocation.beforeResolve { excludeFromClassesRelocation ->
            val confsToSkip = relocateClasses.hierarchy + excludeFromClassesRelocation.hierarchy
            compileClasspath.hierarchy.forEach { conf ->
                if (conf !in confsToSkip) {
                    excludeFromClassesRelocation.dependencies.addAll(conf.dependencies)
                }
            }
            runtimeClasspath.hierarchy.forEach { conf ->
                if (conf !in confsToSkip) {
                    excludeFromClassesRelocation.dependencies.addAll(conf.dependencies)
                }
            }
        }
    }

    @PluginAction("Support name.remal.gradle_plugins.api.RelocateClasses annotation")
    fun supportRelocateClassesAnnotation() {
        // It's implemented via RelocateClassesClassesProcessorsGradleTaskFactory
    }

    @PluginAction
    fun TaskContainer.`Process relocated services`() {
        fun Task.processDestinationDir(destinationDir: File) {
            val classesRelocation = project[ClassesRelocationExtension::class.java]
            val relocatedClassNames = classesRelocation._relocatedClassNames
            relocatedClassNames.forEach { className, relocatedClassName ->
                val servicesResourceName = "META-INF/services/$className"
                val servicesFile = destinationDir.resolve(servicesResourceName).nullIf { !isFile } ?: return@forEach

                val relocatedServicesResourceName = "META-INF/services/$relocatedClassName"
                val relocatedServicesFile = destinationDir.resolve(relocatedServicesResourceName)

                if (relocatedServicesFile.isFile) {
                    logDebug("Appending {} to {}", servicesFile, relocatedServicesFile)
                    val implementations = sequenceOf(relocatedServicesFile, servicesFile)
                        .map { it.readText(UTF_8) }
                        .flatMap { it.splitToSequence('\n', '\r') }
                        .map { it.substringBefore('#') }
                        .map(String::trim)
                        .filter(String::isNotEmpty)
                        .toSet()
                    relocatedServicesFile.writeText(implementations.joinToString("\n"), UTF_8)
                    servicesFile.forceDelete()

                } else {
                    logDebug("Renaming {} to {}", servicesFile, relocatedServicesFile)
                    servicesFile.renameTo(relocatedServicesFile)
                }
            }
        }

        matching(Task::isRelocationTask).all(AbstractCompile::class.java) { task ->
            task.doLast { _ ->
                task.processDestinationDir(task.destinationDirectory.asFile.get())
            }
        }

        matching(Task::isRelocationTask).all(Copy::class.java) { task ->
            task.doLast { _ ->
                task.processDestinationDir(task.destinationDir)
            }
        }
    }

    @PluginAction
    fun TaskContainer.`Make relocation tasks depends on another project's 'jar' task`(configurations: ConfigurationContainer) {
        matching(Task::isRelocationTask).all { task ->
            task.dependsOn {
                val mainSourceSet = task.mainSourceSet!!
                val compileClasspathConf = configurations[mainSourceSet.compileClasspathConfigurationName]
                val dependencyProjects = compileClasspathConf.allDependencies.asSequence()
                    .filterIsInstance(ProjectDependency::class.java)
                    .map { it.dependencyProject }
                    .toSet()
                val dependencyTasks = dependencyProjects.asSequence()
                    .filter { it.isPluginApplied(JavaPluginId) }
                    .mapNotNull { it.tasks.findByName(JAR_TASK_NAME) }
                    .toList()
                return@dependsOn dependencyTasks
            }
        }
    }

    @PluginAction
    fun TaskContainer.`Make processResources task depends on AbstractCompile tasks`() {
        all(Copy::class.java) { task ->
            task.dependsOnIf(Task::isRelocationTask) dependsOn@{
                return@dependsOn filterIsInstance(AbstractCompile::class.java).filter(Task::isRelocationTask)
            }
        }
    }

    @LowestPriorityPluginAction
    fun TaskContainer.`Merge relocated classes in AbstractCopyTask tasks`() {
        all(AbstractCopyTask::class.java) {
            it.doSetup {
                val classesRelocation = it.project[ClassesRelocationExtension::class.java]
                val relocatedClassesJavaPackageSegments = classesRelocation.relocatedClassesPackageName.split('.').toTypedArray()
                it.eachFile {
                    if (it.relativePath.segments.startsWith(relocatedClassesJavaPackageSegments)) {
                        it.duplicatesStrategy = EXCLUDE
                    }
                }
            }
        }
    }

    @LowestPriorityPluginAction
    fun TaskContainer.`Tune cache of relocation tasks`(configurations: ConfigurationContainer) {
        matching(Task::isRelocationTask).all {
            it.doSetup(Int.MAX_VALUE) { task ->
                listOf(
                    configurations.relocateClasses,
                    configurations.excludeFromForcedClassesRelocation,
                    configurations.excludeFromClassesRelocation
                ).forEach { conf ->
                    task.inputs.files(conf).optional().withNormalizer(ClasspathNormalizer::class.java)
                }
            }
        }
    }

}


val ConfigurationContainer.relocateClasses: Configuration get() = this[RELOCATE_CLASSES_CONFIGURATION_NAME]
val ConfigurationContainer.excludeFromForcedClassesRelocation: Configuration get() = this[EXCLUDE_FROM_FORCED_CLASSES_RELOCATION_CONFIGURATION_NAME]
val ConfigurationContainer.excludeFromClassesRelocation: Configuration get() = this[EXCLUDE_FROM_CLASSES_RELOCATION_CONFIGURATION_NAME]


private val Task.isRelocationTask: Boolean
    get() {
        if (this is AbstractCompile) {
            val mainSourceSet = this.mainSourceSet ?: return false
            return isCompilingSourceSet(mainSourceSet)

        } else if (this is Copy) {
            val mainSourceSet = this.mainSourceSet ?: return false
            return name == mainSourceSet.processResourcesTaskName

        } else {
            return false
        }
    }

private val Task.mainSourceSet: SourceSet?
    get() = project.getOrNull(SourceSetContainer::class.java)?.findByName(MAIN_SOURCE_SET_NAME)




© 2015 - 2024 Weber Informatics LLC | Privacy Policy