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

main.name.remal.gradle_plugins.plugins.java.JavaSettingsPlugin.kt Maven / Gradle / Ivy

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

import name.remal.default
import name.remal.gradle_plugins.api.AutoService
import name.remal.gradle_plugins.dsl.ApplyPluginClasses
import name.remal.gradle_plugins.dsl.ApplyPluginClassesAtTheEnd
import name.remal.gradle_plugins.dsl.BaseReflectiveProjectPlugin
import name.remal.gradle_plugins.dsl.CreateConfigurationsPluginAction
import name.remal.gradle_plugins.dsl.GradleEnumVersion.GRADLE_VERSION_5_6
import name.remal.gradle_plugins.dsl.MinGradleVersion
import name.remal.gradle_plugins.dsl.Plugin
import name.remal.gradle_plugins.dsl.PluginAction
import name.remal.gradle_plugins.dsl.PluginActionsGroup
import name.remal.gradle_plugins.dsl.PluginCondition
import name.remal.gradle_plugins.dsl.WithPlugins
import name.remal.gradle_plugins.dsl.extensions.all
import name.remal.gradle_plugins.dsl.extensions.classifierCompatible
import name.remal.gradle_plugins.dsl.extensions.createDependencyTransformConfiguration
import name.remal.gradle_plugins.dsl.extensions.doSetup
import name.remal.gradle_plugins.dsl.extensions.doSetupIf
import name.remal.gradle_plugins.dsl.extensions.doSetupIfAndAfterEvaluate
import name.remal.gradle_plugins.dsl.extensions.get
import name.remal.gradle_plugins.dsl.extensions.getJavaModuleName
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.isSourceJava9Compatible
import name.remal.gradle_plugins.dsl.extensions.isTargetJava8Compatible
import name.remal.gradle_plugins.dsl.extensions.javaModuleName
import name.remal.gradle_plugins.dsl.extensions.logDebug
import name.remal.gradle_plugins.dsl.extensions.parameters
import name.remal.gradle_plugins.dsl.extensions.set
import name.remal.gradle_plugins.dsl.extensions.useDefault
import name.remal.gradle_plugins.dsl.utils.MODULE_NAME_MANIFEST_ATTRIBUTE
import name.remal.gradle_plugins.plugins.classes_processing.ClassesProcessingPlugin
import name.remal.gradle_plugins.plugins.code_quality.checkstyle.CheckstyleSettingsPlugin
import name.remal.gradle_plugins.plugins.code_quality.jacoco.JacocoSettingsPlugin
import name.remal.gradle_plugins.plugins.common.CommonSettingsPlugin
import name.remal.gradle_plugins.plugins.dependencies.TransitiveDependenciesConfigurationMatcher
import name.remal.gradle_plugins.plugins.dependencies.TransitiveDependenciesPlugin
import name.remal.gradle_plugins.plugins.merge_resources.MergeResourcesPlugin
import name.remal.gradle_plugins.plugins.testing.TestSettingsPlugin
import name.remal.gradle_plugins.test_source_sets.TestSourceSetsPlugin
import name.remal.nullIfEmpty
import name.remal.uncheckedCast
import org.gradle.api.Named
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ConfigurationContainer
import org.gradle.api.artifacts.ModuleDependency
import org.gradle.api.artifacts.dsl.RepositoryHandler
import org.gradle.api.attributes.Attribute
import org.gradle.api.model.ObjectFactory
import org.gradle.api.plugins.JavaPluginExtension
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.SourceSetOutput
import org.gradle.api.tasks.TaskContainer
import org.gradle.api.tasks.compile.AbstractCompile
import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.api.tasks.testing.Test
import org.gradle.jvm.tasks.Jar
import org.gradle.process.JavaExecSpec
import org.gradle.util.GradleVersion
import java.nio.charset.StandardCharsets.UTF_8

const val COMPILE_ONLY_ALL_CONFIGURATION_NAME = "compileOnlyAll"
const val COMPILE_OPTIONAL_CONFIGURATION_NAME = "compileOptional"
const val COMPILE_OPTIONAL_TRANSITIVE_CONFIGURATION_NAME = "compileOptional-transitive"

@Plugin(
    id = "name.remal.java-settings",
    description = "Plugin that configures 'java' plugin if it's applied. This plugin fixes some problems building Java 9 projects.",
    tags = ["java"]
)
@WithPlugins(JavaPluginId::class)
@ApplyPluginClasses(CommonSettingsPlugin::class)
@ApplyPluginClassesAtTheEnd(
    TransitiveDependenciesPlugin::class,
    AptPlugin::class,
    ClassesProcessingPlugin::class,
    MergeResourcesPlugin::class,
    TestSettingsPlugin::class,
    TestSourceSetsPlugin::class,
    CheckstyleSettingsPlugin::class,
    JacocoSettingsPlugin::class,
    JavaApplicationSettingsPlugin::class
)
class JavaSettingsPlugin : BaseReflectiveProjectPlugin() {

    @PluginAction
    @MinGradleVersion(GRADLE_VERSION_5_6)
    fun SourceSetContainer.`Always consume JAR artifact and not classes`(configurations: ConfigurationContainer, objects: ObjectFactory) {
        val libraryElementsClass: Class = Class.forName("org.gradle.api.attributes.LibraryElements").uncheckedCast()
        val libraryElementsAttribute: Attribute = libraryElementsClass.getField("LIBRARY_ELEMENTS_ATTRIBUTE").get(null).uncheckedCast()
        //val libraryElementClassesAndResources: String = libraryElementsClass.getField("CLASSES_AND_RESOURCES").get(null).uncheckedCast()
        val libraryElementJar: String = libraryElementsClass.getField("JAR").get(null).uncheckedCast()

        all { sourceSet ->
            configurations[sourceSet.compileClasspathConfigurationName].attributes { attrs ->
                attrs.attribute(libraryElementsAttribute, objects.named(libraryElementsClass, libraryElementJar))
            }
        }
    }

    @CreateConfigurationsPluginAction
    fun ConfigurationContainer.`Create compileOnlyAll configuration`(sourceSets: SourceSetContainer) {
        create(COMPILE_ONLY_ALL_CONFIGURATION_NAME) { conf ->
            conf.description = "All compileOnly configurations extend this configuration"
            sourceSets.all { sourceSet -> this[sourceSet.compileOnlyConfigurationName].extendsFrom(conf) }
        }
    }

    @CreateConfigurationsPluginAction(
        ""
            + "Create compileOptional configuration.\n"
            + "CompileOnly configuration extends it. Also all *compile configurations extend it.\n"
            + "Dependencies from compileOptional configuration are added to all Test and JavaExec tasks including transitive dependencies. Transitive dependencies can be configured using name.remal.transitive-dependencies plugin."
    )
    fun ConfigurationContainer.createCompileOptionalConfiguration(sourceSets: SourceSetContainer, tasks: TaskContainer) {
        val compileOptional = create(COMPILE_OPTIONAL_CONFIGURATION_NAME) { conf ->
            conf.description = "Optional compile dependencies"

            sourceSets.all(MAIN_SOURCE_SET_NAME) {
                findByName(it.compileOnlyConfigurationName)?.extendsFrom(conf)
            }
            sourceSets.matching { MAIN_SOURCE_SET_NAME != it.name }.all {
                findByName(it.implementationConfigurationName)?.extendsFrom(conf)
            }
        }

        val transitiveConf = createDependencyTransformConfiguration(compileOptional, COMPILE_OPTIONAL_TRANSITIVE_CONFIGURATION_NAME) { dep ->
            dep.copy().also { copyDep ->
                if (copyDep is ModuleDependency) {
                    copyDep.isTransitive = true
                }
            }
        }
        transitiveConf.isVisible = false
        transitiveConf.isCanBeConsumed = false


        tasks.all { task ->
            if (task is JavaExecSpec
                && task !is Test // handled below
            ) {
                task.doSetup(Int.MIN_VALUE) { _ ->
                    val transitiveConfFiles = transitiveConf.files
                    if (transitiveConfFiles.isNotEmpty()) {
                        task.classpath = task.classpath + task.project.files(transitiveConfFiles)
                    }
                }
            }
        }
        tasks.all(Test::class.java) {
            it.doSetup(Int.MIN_VALUE) {
                val transitiveConfFiles = transitiveConf.files
                if (transitiveConfFiles.isNotEmpty()) {
                    it.classpath = it.classpath + it.project.files(transitiveConfFiles)
                }
            }
        }
    }

    @PluginAction
    fun RepositoryHandler.`Use mavenCentral and mavenLocal repositories by default`() {
        useDefault {
            mavenCentral()
            mavenLocal()
        }
    }

    @PluginActionsGroup
    inner class `For all JavaCompile tasks` {

        @PluginAction
        fun TaskContainer.`Set default encoding to UTF-8`() {
            all(JavaCompile::class.java) {
                if (null == it.options.encoding) {
                    it.logDebug("Setting encoding to UTF-8")
                    it.options.encoding = UTF_8.name()
                }
            }
        }

        @PluginAction
        fun TaskContainer.`Enable displaying deprecation warnings`() {
            all(JavaCompile::class.java) {
                it.options.isDeprecation = true
            }
        }

        @PluginAction
        fun TaskContainer.`Add '-parameters' compiler option if targetting Java 8 and above`() {
            all(JavaCompile::class.java) {
                it.doSetupIfAndAfterEvaluate(AbstractCompile::isTargetJava8Compatible) { task ->
                    task.options.parameters = true
                }
            }
        }

        @PluginActionsGroup
        inner class `For Gradle less than 7` {

            @PluginCondition
            fun `Gradle less than 7`(): Boolean {
                return GradleVersion.current() < GradleVersion.version("7.0")
            }

            @PluginAction
            fun TaskContainer.`Add '--module-path' compiler option if sources are compatible with Java 9 and above`() {
                all(JavaCompile::class.java) {
                    it.doSetupIf(Int.MAX_VALUE, AbstractCompile::isSourceJava9Compatible) { task ->
                        task.options.compilerArgs.let { compilerArgs ->
                            if ("--module-path" !in compilerArgs) {
                                var classpath = task.classpath
                                task.options.annotationProcessorPath?.let { classpath += it }

                                val modulePath = classpath.asPath
                                if (modulePath.isNotEmpty()) {
                                    task.logDebug("Adding --module-path {}", modulePath)
                                    compilerArgs.add("--module-path")
                                    compilerArgs.add(modulePath)
                                }
                            }
                        }
                    }
                }
            }

            @PluginAction
            fun TaskContainer.`Add '--patch-module' compiler option if sources are compatible with Java 9 and above`() {
                all(JavaCompile::class.java) {
                    it.doSetupIf(Int.MAX_VALUE, AbstractCompile::isSourceJava9Compatible) { task ->
                        val moduleName = task.source.getJavaModuleName() ?: return@doSetupIf
                        val classesDirsList = (task.project.getOrNull(SourceSetContainer::class.java)?.asSequence() ?: emptySequence())
                            .filter(task::isCompilingSourceSet)
                            .map(SourceSet::getOutput)
                            .map(SourceSetOutput::getClassesDirs)
                            .toList()
                            .nullIfEmpty() ?: return@doSetupIf
                        val modulePath = classesDirsList.asSequence()
                            .reduce { fc1, fc2 -> fc1 + fc2 }
                            .asPath
                            .nullIfEmpty() ?: return@doSetupIf
                        task.options.compilerArgs.apply {
                            forEachIndexed { index, arg ->
                                if (arg == "--patch-module") {
                                    if (getOrNull(index + 1).default().startsWith("$moduleName=")) {
                                        task.logDebug("{} module has been already patched", moduleName)
                                        return@apply
                                    }
                                }
                            }

                            task.logDebug("Adding --patch-module for module {}: {}", moduleName, modulePath)
                            add("--patch-module")
                            add("$moduleName=$modulePath")
                        }
                    }
                }
            }

        }

    }

    @PluginAction
    fun TaskContainer.`Add Automatic-Module-Name manifest attribute in result jar archive if targetting Java 8 or below`(java: JavaPluginExtension) {
        all(Jar::class.java) {
            it.doSetup(10) { task ->
                val targetCompatibility = java.sourceCompatibility
                if (targetCompatibility.isJava9Compatible) return@doSetup

                val moduleNameAttribute = MODULE_NAME_MANIFEST_ATTRIBUTE
                if (task.classifierCompatible in setOf("sources", "javadoc", "groovydoc", "kotlindoc", "dokka", "kdoc", "scaladoc")) {
                    task.logDebug("Skip adding {} manifest attribute, as classifier equals to '{}'", moduleNameAttribute, it.classifierCompatible)
                    return@doSetup
                }
                val attributes = task.manifest.attributes
                if (moduleNameAttribute !in attributes) {
                    val moduleName = task.project.javaModuleName
                    task.logDebug("Adding {} manifest attribute: {}", moduleNameAttribute, moduleName)
                    task.inputs[JavaSettingsPlugin::class.java.name + ':' + moduleNameAttribute] = moduleName
                    attributes[moduleNameAttribute] = moduleName
                }
            }
        }
    }

}


val ConfigurationContainer.compileOnlyAll: Configuration get() = this[COMPILE_ONLY_ALL_CONFIGURATION_NAME]
val ConfigurationContainer.compileOptional: Configuration get() = this[COMPILE_OPTIONAL_CONFIGURATION_NAME]
val ConfigurationContainer.compileOptionalTransitive: Configuration get() = this[COMPILE_OPTIONAL_TRANSITIVE_CONFIGURATION_NAME]


@AutoService
class JavaSettingsPluginTransitiveDependenciesConfigurationMatcher : TransitiveDependenciesConfigurationMatcher {
    override fun matches(project: Project, configuration: Configuration): Boolean {
        if (!project.isPluginApplied(JavaSettingsPlugin::class.java)) return false
        if (COMPILE_ONLY_ALL_CONFIGURATION_NAME == configuration.name) return true
        if (COMPILE_OPTIONAL_CONFIGURATION_NAME == configuration.name) return true
        if (COMPILE_OPTIONAL_TRANSITIVE_CONFIGURATION_NAME == configuration.name) return true
        return false
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy