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