org.jetbrains.kotlin.gradle.plugin.KotlinLegacyMultiplatformPlugin.kt Maven / Gradle / Ivy
/*
* 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 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.tasks.SourceSet
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
import org.jetbrains.kotlin.gradle.logging.kotlinWarn
import org.jetbrains.kotlin.gradle.plugin.diagnostics.KotlinToolingDiagnostics
import org.jetbrains.kotlin.gradle.plugin.diagnostics.reportDiagnosticOncePerBuild
import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile
import org.jetbrains.kotlin.gradle.utils.createDependencyScope
import org.jetbrains.kotlin.gradle.utils.javaSourceSetsIfAvailable
import org.jetbrains.kotlin.gradle.utils.whenEvaluated
abstract class KotlinPlatformPluginBase(protected val platformName: String) : Plugin {
companion object {
@JvmStatic
protected inline fun > Project.applyPlugin() {
pluginManager.apply(T::class.java)
}
}
}
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()
private fun configurationsForCommonModuleDependency(project: Project): List =
listOf(project.configurations.getByName("api"))
override fun apply(project: Project) {
warnAboutKotlin12xMppDeprecation(project)
val implementConfig = project.configurations.createDependencyScope(IMPLEMENT_CONFIG_NAME).get()
val expectedByConfig = project.configurations.createDependencyScope(EXPECTED_BY_CONFIG_NAME).get()
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.javaSourceSetsIfAvailable
?.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.get() == commonSourceSet.name } // TODO use strict check once this code is not run in K/N
val commonSources = getKotlinSourceDirectorySetSafe(commonSourceSet)!!
for (platformTask in platformTasks) {
platformTask.setSource(commonSources)
platformTask.commonSourceSet.from(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", level = DeprecationLevel.ERROR)
protected val SourceSet.kotlin: SourceDirectorySet?
get() {
@Suppress("DEPRECATION")
return getExtension(KOTLIN_DSL_NAME) ?: getExtension(KOTLIN_JS_DSL_NAME)
}
}
private const val KOTLIN_12X_MPP_DEPRECATION_SUPPRESS_FLAG = "kotlin.internal.mpp12x.deprecation.suppress"
internal fun warnAboutKotlin12xMppDeprecation(project: Project) {
if (project.findProperty(KOTLIN_12X_MPP_DEPRECATION_SUPPRESS_FLAG) != "true") {
project.reportDiagnosticOncePerBuild(KotlinToolingDiagnostics.Kotlin12XMppDeprecation())
}
}