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

org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPlugin.kt Maven / Gradle / Ivy

There is a newer version: 2.0.20-RC
Show newest version
/*
 * Copyright 2010-2016 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jetbrains.kotlin.gradle.plugin

import com.android.build.gradle.BaseExtension
import org.gradle.api.*
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ProjectDependency
import org.gradle.api.file.SourceDirectorySet
import org.gradle.api.plugins.JavaPluginConvention
import org.gradle.api.tasks.SourceSet
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
import org.jetbrains.kotlin.gradle.logging.kotlinWarn
import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile

abstract class KotlinPlatformPluginBase(protected val platformName: String) : Plugin {
    companion object {
        @JvmStatic
        protected inline fun > Project.applyPlugin() {
            pluginManager.apply(T::class.java)
        }
    }
}

open class KotlinPlatformCommonPlugin : KotlinPlatformPluginBase("common") {
    override fun apply(project: Project) {
        project.applyPlugin()
    }
}

const val EXPECTED_BY_CONFIG_NAME = "expectedBy"

const val IMPLEMENT_CONFIG_NAME = "implement"
const val IMPLEMENT_DEPRECATION_WARNING = "The '$IMPLEMENT_CONFIG_NAME' configuration is deprecated and will be removed. " +
        "Use '$EXPECTED_BY_CONFIG_NAME' instead."

open class KotlinPlatformImplementationPluginBase(platformName: String) : KotlinPlatformPluginBase(platformName) {
    private val commonProjects = arrayListOf()

    protected open fun configurationsForCommonModuleDependency(project: Project): List =
        listOf(project.configurations.getByName("compile"))

    override fun apply(project: Project) {
        val implementConfig = project.configurations.create(IMPLEMENT_CONFIG_NAME)
        val expectedByConfig = project.configurations.create(EXPECTED_BY_CONFIG_NAME)

        implementConfig.dependencies.whenObjectAdded {
            if (!implementConfigurationIsUsed) {
                implementConfigurationIsUsed = true
                project.logger.kotlinWarn(IMPLEMENT_DEPRECATION_WARNING)
            }
        }

        listOf(implementConfig, expectedByConfig).forEach { config ->
            config.isTransitive = false

            config.dependencies.whenObjectAdded { dep ->
                if (dep is ProjectDependency) {
                    addCommonProject(dep.dependencyProject, project)

                    // Needed for the projects that depend on this one to recover the common module sources through
                    // the transitive dependency (also, it will be added to the POM generated by Gradle):
                    configurationsForCommonModuleDependency(project).forEach { configuration ->
                        configuration.dependencies.add(dep)
                    }
                } else {
                    throw GradleException("$project '${config.name}' dependency is not a project: $dep")
                }
            }
        }

        val incrementalMultiplatform = PropertiesProvider(project).incrementalMultiplatform ?: true
        project.afterEvaluate {
            project.tasks.withType(AbstractKotlinCompile::class.java).all { task ->
                if (task.incremental && !incrementalMultiplatform) {
                    task.logger.debug("IC is turned off for task '${task.path}' because multiplatform IC is not enabled")
                }
                task.incremental = task.incremental && incrementalMultiplatform
            }
        }
    }

    private var implementConfigurationIsUsed = false

    private fun addCommonProject(commonProject: Project, platformProject: Project) {
        commonProjects.add(commonProject)

        commonProject.whenEvaluated {
            if (!commonProject.pluginManager.hasPlugin("kotlin-platform-common")) {
                throw GradleException(
                    "Platform project $platformProject has an " +
                            "'$EXPECTED_BY_CONFIG_NAME'${if (implementConfigurationIsUsed) "/'$IMPLEMENT_CONFIG_NAME'" else ""} " +
                            "dependency to non-common project $commonProject"
                )
            }

            // Since the two projects may add source sets in arbitrary order, and both may do that after the plugin is applied,
            // we need to handle all source sets of the two projects and connect them once we get a match:
            // todo warn if no match found
            matchSymmetricallyByNames(
                getKotlinSourceSetsSafe(commonProject),
                namedSourceSetsContainer(platformProject)
            ) { commonSourceSet: Named, _ ->
                addCommonSourceSetToPlatformSourceSet(commonSourceSet, platformProject)

                // Workaround for older versions of Kotlin/Native overriding the old signature
                commonProject.convention.findPlugin(JavaPluginConvention::class.java)
                    ?.sourceSets
                    ?.findByName(commonSourceSet.name)
                    ?.let { javaSourceSet ->
                        @Suppress("DEPRECATION")
                        addCommonSourceSetToPlatformSourceSet(javaSourceSet, platformProject)
                    }
            }
        }
    }

    /**
     * Applies [whenMatched] to pairs of items with the same name in [containerA] and [containerB],
     * regardless of the order in which they are added to the containers.
     */
    private fun  matchSymmetricallyByNames(
        containerA: NamedDomainObjectCollection,
        containerB: NamedDomainObjectCollection,
        whenMatched: (A, B) -> Unit
    ) {
        val matchedNames = mutableSetOf()

        fun  NamedDomainObjectCollection.matchAllWith(other: NamedDomainObjectCollection, match: (T, R) -> Unit) {
            [email protected] { item ->
                val itemName = [email protected](item)
                if (itemName !in matchedNames) {
                    val otherItem = other.findByName(itemName)
                    if (otherItem != null) {
                        matchedNames += itemName
                        match(item, otherItem)
                    }
                }
            }
        }
        containerA.matchAllWith(containerB) { a, b -> whenMatched(a, b) }
        containerB.matchAllWith(containerA) { b, a -> whenMatched(a, b) }
    }

    protected open fun namedSourceSetsContainer(project: Project): NamedDomainObjectContainer<*> =
        project.kotlinExtension.sourceSets

    protected open fun addCommonSourceSetToPlatformSourceSet(commonSourceSet: Named, platformProject: Project) {
        platformProject.whenEvaluated {
            // At the point when the source set in the platform module is created, the task does not exist
            val platformTasks = platformProject.tasks
                .withType(AbstractKotlinCompile::class.java)
                .filter { it.sourceSetName == commonSourceSet.name } // TODO use strict check once this code is not run in K/N

            val commonSources = getKotlinSourceDirectorySetSafe(commonSourceSet)!!
            for (platformTask in platformTasks) {
                platformTask.source(commonSources)
                platformTask.commonSourceSet += commonSources
            }
        }
    }

    private fun getKotlinSourceSetsSafe(project: Project): NamedDomainObjectCollection {
        // Access through reflection, because another project's KotlinProjectExtension might be loaded by a different class loader:
        val kotlinExt = project.extensions.getByName("kotlin")
        @Suppress("UNCHECKED_CAST")
        val sourceSets = kotlinExt.javaClass.getMethod("getSourceSets").invoke(kotlinExt) as NamedDomainObjectCollection
        return sourceSets
    }

    protected fun getKotlinSourceDirectorySetSafe(from: Any): SourceDirectorySet? {
        val getKotlin = from.javaClass.getMethod("getKotlin")
        return getKotlin(from) as? SourceDirectorySet
    }

    @Deprecated("Migrate to the new Kotlin source sets and use the addCommonSourceSetToPlatformSourceSet(Named, Project) overload")
    protected open fun addCommonSourceSetToPlatformSourceSet(sourceSet: SourceSet, platformProject: Project) = Unit

    @Deprecated("Retained for older Kotlin/Native MPP plugin binary compatibility")
    protected val SourceSet.kotlin: SourceDirectorySet?
        get() {
            // Access through reflection, because another project's KotlinSourceSet might be loaded
            // by a different class loader:
            val convention = (getConvention("kotlin") ?: getConvention("kotlin2js")) ?: return null
            val kotlinSourceSetIface = convention.javaClass.interfaces.find { it.name == KotlinSourceSet::class.qualifiedName }
            val getKotlin = kotlinSourceSetIface?.methods?.find { it.name == "getKotlin" } ?: return null
            return getKotlin(convention) as? SourceDirectorySet
        }
}

internal fun  Project.whenEvaluated(fn: Project.() -> T) {
    if (state.executed) {
        fn()
    } else {
        afterEvaluate { it.fn() }
    }
}

open class KotlinPlatformAndroidPlugin : KotlinPlatformImplementationPluginBase("android") {
    override fun apply(project: Project) {
        project.applyPlugin()
        super.apply(project)
    }

    override fun configurationsForCommonModuleDependency(project: Project): List =
        (project.configurations.findByName("api"))?.let(::listOf)
            ?: super.configurationsForCommonModuleDependency(project) // older Android plugins don't have api/implementation configs

    override fun namedSourceSetsContainer(project: Project): NamedDomainObjectContainer<*> =
        (project.extensions.getByName("android") as BaseExtension).sourceSets

    override fun addCommonSourceSetToPlatformSourceSet(commonSourceSet: Named, platformProject: Project) {
        val androidExtension = platformProject.extensions.getByName("android") as BaseExtension
        val androidSourceSet = androidExtension.sourceSets.findByName(commonSourceSet.name) ?: return
        val kotlinSourceSet = androidSourceSet.getConvention(KOTLIN_DSL_NAME) as? KotlinSourceSet
            ?: return
        kotlinSourceSet.kotlin.source(getKotlinSourceDirectorySetSafe(commonSourceSet)!!)
    }
}

open class KotlinPlatformJvmPlugin : KotlinPlatformImplementationPluginBase("jvm") {
    override fun apply(project: Project) {
        project.applyPlugin()
        super.apply(project)
    }
}

open class KotlinPlatformJsPlugin : KotlinPlatformImplementationPluginBase("js") {
    override fun apply(project: Project) {
        project.applyPlugin()
        super.apply(project)
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy