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

org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget.kt Maven / Gradle / Ivy

There is a newer version: 2.0.0
Show newest version
/*
 * Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
 * that can be found in the license/LICENSE.txt file.
 */

package org.jetbrains.kotlin.gradle.targets.jvm

import org.gradle.api.Action
import org.gradle.api.InvalidUserCodeException
import org.gradle.api.NamedDomainObjectContainer
import org.gradle.api.Project
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.DuplicatesStrategy
import org.gradle.api.plugins.JavaBasePlugin
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.SourceSetContainer
import org.gradle.api.tasks.compile.AbstractCompile
import org.gradle.api.tasks.testing.Test
import org.gradle.jvm.tasks.Jar
import org.gradle.language.jvm.tasks.ProcessResources
import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.dsl.*
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompilerOptionsDefault
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.plugin.KotlinPluginLifecycle.Stage.AfterFinaliseDsl
import org.jetbrains.kotlin.gradle.plugin.diagnostics.KotlinToolingDiagnostics
import org.jetbrains.kotlin.gradle.plugin.diagnostics.reportDiagnostic
import org.jetbrains.kotlin.gradle.plugin.internal.JavaSourceSetsAccessor
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinOnlyTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.internal
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.util.copyAttributes
import org.jetbrains.kotlin.gradle.targets.jvm.tasks.KotlinJvmRunDsl
import org.jetbrains.kotlin.gradle.targets.jvm.tasks.KotlinJvmRunDslImpl
import org.jetbrains.kotlin.gradle.targets.jvm.tasks.registerMainRunTask
import org.jetbrains.kotlin.gradle.tasks.DefaultKotlinJavaToolchain
import org.jetbrains.kotlin.gradle.tasks.withType
import org.jetbrains.kotlin.gradle.utils.*
import org.jetbrains.kotlin.gradle.utils.Future
import org.jetbrains.kotlin.gradle.utils.findAppliedAndroidPluginIdOrNull
import org.jetbrains.kotlin.gradle.utils.future
import org.jetbrains.kotlin.utils.addToStdlib.cast
import java.util.concurrent.Callable
import javax.inject.Inject
import kotlin.reflect.full.functions

abstract class KotlinJvmTarget @Inject constructor(
    project: Project,
) : KotlinOnlyTarget(project, KotlinPlatformType.jvm),
    KotlinTargetWithTests {

    override lateinit var testRuns: NamedDomainObjectContainer

    internal val mainRun: Future = project.future { registerMainRunTask() }

    /**
     * ### ⚠️ KotlinJvmTarget 'mainRun' is experimental
     * The [KotlinJvmTarget], by default, creates a 'run' task called {targetName}Run, which will allows simple
     * execution of the targets 'main' code.
     *
     * e.g.
     * ```kotlin
     * // build.gradle.kts
     * kotlin {
     *     jvm().mainRun {
     *         mainClass.set("FooKt")
     *     }
     * }
     *
     * // src/jvmMain/Foo
     * fun main() {
     *     println("Hello from foo")
     * }
     * ```
     *
     * will be executable using
     * ```text
     * ./gradlew jvmRun
     * > "Hello from foo"
     * ```
     *
     * ### Running a different 'mainClass' from CLI:
     * The execution of the main code allows providing a different 'mainClass' via CLI. *
     * It accepts System Properties and Gradle Properties. However, when Gradle Configuration Cache is used,
     * System Properties are the preferred way.
     *
     * ```text
     * ./gradlew jvmRun -DmainClass="BarKt"
     *                    ^
     *                    Will execute the 'src/jvmMain/kotlin/Bar' main method.
     * ```
     */
    @ExperimentalKotlinGradlePluginApi
    fun mainRun(configure: KotlinJvmRunDsl.() -> Unit) = project.launch {
        mainRun.await()?.configure()
    }

    var withJavaEnabled = false
        private set

    @Suppress("unused") // user DSL
    fun withJava() {
        if (withJavaEnabled)
            return

        project.multiplatformExtension.targets.find { it is KotlinJvmTarget && it.withJavaEnabled }
            ?.let { existingJavaTarget ->
                throw InvalidUserCodeException(
                    "Only one of the JVM targets can be configured to work with Java. The target '${existingJavaTarget.name}' is " +
                            "already set up to work with Java; cannot setup another target '$targetName'"
                )
            }


        /**
         * Reports diagnostic in the case of
         * ```kotlin
         * kotlin {
         *     jvm().withJava()
         * }
         * ```
         *
         * is used together with the Android Gradle Plugin.
         * This case is incompatible so far, as the 'withJava' implementation is still using 'global' namespaces
         * (like main/test, etc), which will clash with the global names used by AGP (also occupying main, test, etc).
         */
        val trace = Throwable()
        project.launchInStage(AfterFinaliseDsl) check@{
            val androidPluginId = project.findAppliedAndroidPluginIdOrNull() ?: return@check
            project.reportDiagnostic(KotlinToolingDiagnostics.JvmWithJavaIsIncompatibleWithAndroid(androidPluginId, trace))
        }

        withJavaEnabled = true

        project.plugins.apply(JavaBasePlugin::class.java)
        val javaSourceSets = project.variantImplementationFactory()
            .getInstance(project)
            .sourceSets
        AbstractKotlinPlugin.setUpJavaSourceSets(this, duplicateJavaSourceSetsAsKotlinSourceSets = false)

        // Below, some effort is made to ensure that a user or 3rd-party plugin that inspects or interacts
        // with the entities created by the Java plugin, not knowing of the existence of the Kotlin plugin,
        // sees 'the right picture' of the inputs and outputs, for example:
        // * the relevant dependencies for Java and Kotlin are in sync,
        // * the Java outputs contain the outputs produced by Kotlin as well

        javaSourceSets.all { javaSourceSet ->
            val compilation = compilations.getByName(javaSourceSet.name)
            val compileJavaTask = project.tasks.withType().named(javaSourceSet.compileJavaTaskName)

            setupJavaSourceSetSourcesAndResources(javaSourceSet, compilation)

            val javaClasses = project.files(compileJavaTask.map { it.destinationDirectory })

            compilation.output.classesDirs.from(javaClasses)

            (javaSourceSet.output.classesDirs as? ConfigurableFileCollection)?.from(
                compilation.output.classesDirs.minus(javaClasses)
            )

            javaSourceSet.output.setResourcesDir(Callable { compilation.output.resourcesDirProvider })

            setupDependenciesCrossInclusionForJava(compilation, javaSourceSet)
        }

        project.launchInStage(AfterFinaliseDsl) {
            javaSourceSets.all { javaSourceSet ->
                copyUserDefinedAttributesToJavaConfigurations(javaSourceSet)
            }
        }

        project.plugins.withType(JavaPlugin::class.java) {
            // Eliminate the Java output configurations from dependency resolution to avoid ambiguity between them and
            // the equivalent configurations created for the target:
            project.configurations.findByName(JavaPlugin.API_ELEMENTS_CONFIGURATION_NAME)?.isCanBeConsumed = false
            project.configurations.findByName(JavaPlugin.RUNTIME_ELEMENTS_CONFIGURATION_NAME)?.isCanBeConsumed = false
            disableJavaPluginTasks(javaSourceSets)
        }

        compilations.all { compilation ->
            compilation.maybeCreateJavaSourceSet()
        }
    }

    private fun disableJavaPluginTasks(javaSourceSet: SourceSetContainer) {
        // A 'normal' build should not do redundant job like running the tests twice or building two JARs,
        // so disable some tasks and just make them depend on the others:
        val targetJar = project.tasks.withType(Jar::class.java).named(artifactsTaskName)

        project.tasks.withType(Jar::class.java).named(javaSourceSet.getByName("main").jarTaskName) { javaJar ->
            (javaJar.source as? ConfigurableFileCollection)?.setFrom(targetJar.map { it.source })
            javaJar.archiveFileName.set(targetJar.flatMap { it.archiveFileName })
            javaJar.dependsOn(targetJar)
            javaJar.enabled = false
        }

        project.tasks.withType(Test::class.java).named(JavaPlugin.TEST_TASK_NAME) { javaTestTask ->
            javaTestTask.dependsOn(project.tasks.named(testTaskName))
            javaTestTask.enabled = false
        }
    }

    private fun setupJavaSourceSetSourcesAndResources(
        javaSourceSet: SourceSet,
        compilation: KotlinJvmCompilation,
    ) {
        javaSourceSet.java.setSrcDirs(listOf("src/${compilation.defaultSourceSet.name}/java"))
        compilation.defaultSourceSet.kotlin.srcDirs(javaSourceSet.java.sourceDirectories)

        // To avoid confusion in the sources layout, remove the default Java source directories
        // (like src/main/java, src/test/java) and instead add sibling directories to those where the Kotlin
        // sources are placed (i.e. src/jvmMain/java, src/jvmTest/java):
        javaSourceSet.resources.setSrcDirs(compilation.defaultSourceSet.resources.sourceDirectories)
        compilation.defaultSourceSet.resources.srcDirs(javaSourceSet.resources.sourceDirectories)
        project.tasks.named(
            compilation.processResourcesTaskName,
            ProcessResources::class.java
        ).configure {
            // Now 'compilation' has additional resources dir from java compilation which points to the initial
            // resources location. Because of this, ProcessResources task will copy same files twice,
            // so we are excluding duplicates.
            it.duplicatesStrategy = DuplicatesStrategy.EXCLUDE
        }

        // Resources processing is done with the Kotlin resource processing task:
        project.tasks.named(javaSourceSet.processResourcesTaskName).configure {
            it.dependsOn(project.tasks.named(compilation.processResourcesTaskName))
            it.enabled = false
        }
    }

    private fun setupDependenciesCrossInclusionForJava(
        compilation: KotlinJvmCompilation,
        javaSourceSet: SourceSet,
    ) {
        // Make sure Kotlin compilation dependencies appear in the Java source set classpaths:

        listOfNotNull(
            compilation.apiConfigurationName,
            compilation.implementationConfigurationName,
            compilation.compileOnlyConfigurationName,
            compilation.internal.configurations.deprecatedCompileConfiguration?.name,
        ).forEach { configurationName ->
            project.addExtendsFromRelation(javaSourceSet.compileClasspathConfigurationName, configurationName)
        }

        listOfNotNull(
            compilation.apiConfigurationName,
            compilation.implementationConfigurationName,
            compilation.runtimeOnlyConfigurationName,
            compilation.internal.configurations.deprecatedRuntimeConfiguration?.name,
        ).forEach { configurationName ->
            project.addExtendsFromRelation(javaSourceSet.runtimeClasspathConfigurationName, configurationName)
        }

        // Add the Java source set dependencies to the Kotlin compilation compile & runtime configurations:

        val compileConfigurationName = if (areRuntimeOrCompileConfigurationsAvailable()) {
            javaSourceSet::class
                .functions
                .find { it.name == "getCompileConfigurationName" }
                ?.call(javaSourceSet)
                ?.cast()
                ?.takeIf { project.configurations.findByName(it) != null }
        } else null

        listOfNotNull(
            compileConfigurationName,
            javaSourceSet.compileOnlyConfigurationName,
            javaSourceSet.apiConfigurationName.takeIf { project.configurations.findByName(it) != null },
            javaSourceSet.implementationConfigurationName
        ).forEach { configurationName ->
            project.addExtendsFromRelation(compilation.compileDependencyConfigurationName, configurationName)
        }

        val runtimeConfigurationName = if (areRuntimeOrCompileConfigurationsAvailable()) {
            javaSourceSet::class
                .functions
                .find { it.name == "getRuntimeConfigurationName" }
                ?.call(javaSourceSet)
                ?.cast()
                ?.takeIf { project.configurations.findByName(it) != null }
        } else null

        listOfNotNull(
            runtimeConfigurationName,
            javaSourceSet.runtimeOnlyConfigurationName,
            javaSourceSet.apiConfigurationName.takeIf { project.configurations.findByName(it) != null },
            javaSourceSet.implementationConfigurationName
        ).forEach { configurationName ->
            project.addExtendsFromRelation(compilation.runtimeDependencyConfigurationName, configurationName)
        }
    }

    private fun copyUserDefinedAttributesToJavaConfigurations(javaSourceSet: SourceSet) {
        val compileConfigurationName = if (areRuntimeOrCompileConfigurationsAvailable()) {
            javaSourceSet::class
                .functions
                .find { it.name == "getCompileConfigurationName" }
                ?.call(javaSourceSet)
                ?.cast()
                ?.takeIf { project.configurations.findByName(it) != null }
        } else null

        val runtimeConfigurationName = if (areRuntimeOrCompileConfigurationsAvailable()) {
            javaSourceSet::class
                .functions
                .find { it.name == "getRuntimeConfigurationName" }
                ?.call(javaSourceSet)
                ?.cast()
                ?.takeIf { project.configurations.findByName(it) != null }
        } else null

        listOfNotNull(
            compileConfigurationName,
            javaSourceSet.compileClasspathConfigurationName,
            runtimeConfigurationName,
            javaSourceSet.runtimeClasspathConfigurationName,
            javaSourceSet.apiConfigurationName,
            javaSourceSet.implementationConfigurationName,
            javaSourceSet.compileOnlyConfigurationName,
            javaSourceSet.runtimeOnlyConfigurationName,
        ).mapNotNull {
            project.configurations.findByName(it)
        }.forEach { configuration ->
            copyAttributes(attributes, configuration.attributes)
        }
    }

    /**
     * Check if "compile" and "runtime" configurations are still available in current Gradle version.
     */
    private fun areRuntimeOrCompileConfigurationsAvailable(): Boolean =
        GradleVersion.version(project.gradle.gradleVersion) <= GradleVersion.version("6.8.3")

    @ExperimentalKotlinGradlePluginApi
    override val compilerOptions: KotlinJvmCompilerOptions = project.objects
        .newInstance()
        .apply {
            DefaultKotlinJavaToolchain.wireJvmTargetToToolchain(
                this,
                project
            )
        }

    @ExperimentalKotlinGradlePluginApi
    fun compilerOptions(configure: KotlinJvmCompilerOptions.() -> Unit) {
        configure(compilerOptions)
    }

    @ExperimentalKotlinGradlePluginApi
    fun compilerOptions(configure: Action) {
        configure.execute(compilerOptions)
    }
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy