Add (and disable) metadata compilation for single backend source sets

KotlinProjectStructureMetadata is created by inspecting source sets
with metadata compilation. Single backend common source sets are
currently not supported for metadata compilation and therefore
did not register one. Foreseeing to support this case with KT-42468,
metadata compilation is now created and registered, but just disabled.
This change will include android variants in
KotlinProjectStructureMetadata even when not officially published.
With those variants, the IDE can infer visibility from some module's
"jvmAndAndroidMain" source set to another modules "jvmAndAndroidMain"

KotlinMetadataTargetConfigurator: rename getPublishedCommonSourceSets to getCommonSourceSetsForMetadataCompilation

KotlinMetadataTargetConfigurator: isMetadataCompilationSupported and add comment to getPublishedCommonSourceSets

Re-use androidPluginIds in KotlinPlugin

KotlinAndroidTarget: Create KotlinComponents for non-published variants and mark them with publishable=false

JvmAndAndroidIntermediateSourceSetTest: add KotlinProjectStructureMetadata jvmAndAndroidMain exists in jvm variants test

AbstractAndroidProjectHandler: Add java sources only in post processing

KotlinAndroidTarget: Inline creation of usageContexts

KotlinAndroidTarget.doCreateComponents: filter non-publishable variants when publishLibraryVariantsGroupedByFlavor is enabled

#KT-42383 fixed
This commit is contained in:
sebastian.sellmair
2020-11-04 11:40:34 +01:00
committed by Sebastian Sellmair
parent 03e63f1103
commit 96352b9c8c
29 changed files with 264 additions and 86 deletions
@@ -0,0 +1,128 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
@file:Suppress("invisible_reference", "invisible_member", "FunctionName", "DuplicatedCode")
package org.jetbrains.kotlin.gradle
import com.android.build.gradle.LibraryExtension
import org.gradle.api.Project
import org.gradle.api.internal.project.ProjectInternal
import org.gradle.api.plugins.ExtraPropertiesExtension
import org.gradle.testfixtures.ProjectBuilder
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMetadataCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.buildKotlinProjectStructureMetadata
import kotlin.test.*
class JvmAndAndroidIntermediateSourceSetTest {
private lateinit var project: ProjectInternal
private lateinit var kotlin: KotlinMultiplatformExtension
private lateinit var jvmAndAndroidMain: KotlinSourceSet
@BeforeTest
fun setup() {
project = ProjectBuilder.builder().build() as ProjectInternal
project.extensions.getByType(ExtraPropertiesExtension::class.java).set("kotlin.mpp.enableGranularSourceSetsMetadata", "true")
project.plugins.apply("kotlin-multiplatform")
project.plugins.apply("android-library")
/* Arbitrary minimal Android setup */
val android = project.extensions.getByName("android") as LibraryExtension
android.compileSdkVersion(30)
/* Kotlin Setup */
kotlin = project.multiplatformExtension
kotlin.jvm()
kotlin.android()
jvmAndAndroidMain = kotlin.sourceSets.create("jvmAndAndroidMain")
kotlin.sourceSets.run {
jvmAndAndroidMain.dependsOn(getByName("commonMain"))
getByName("jvmMain") {
it.dependsOn(jvmAndAndroidMain)
}
getByName("androidMain") {
it.dependsOn(jvmAndAndroidMain)
}
}
}
@Test
fun `metadata compilation is created and disabled`() {
/* evaluate */
project.evaluate()
/* Check if compilation is created correctly */
val jvmAndAndroidMainMetadataCompilations = kotlin.targets.flatMap { it.compilations }
.filterIsInstance<KotlinMetadataCompilation<*>>()
.filter { it.name == jvmAndAndroidMain.name }
assertEquals(
1, jvmAndAndroidMainMetadataCompilations.size,
"Expected exactly one metadata compilation created for jvmAndAndroidMain source set"
)
val compilation = jvmAndAndroidMainMetadataCompilations.single()
assertFalse(
compilation.compileKotlinTaskProvider.get().enabled,
"Expected compilation task to be disabled, because not supported yet"
)
}
@Test
fun `KotlinProjectStructureMetadata jvmAndAndroidMain exists in jvm variants`() {
project.evaluate()
val metadata = assertNotNull(buildKotlinProjectStructureMetadata(project))
assertTrue("jvmAndAndroidMain" in metadata.sourceSetNamesByVariantName["jvmApiElements"].orEmpty())
assertTrue("jvmAndAndroidMain" in metadata.sourceSetNamesByVariantName["jvmRuntimeElements"].orEmpty())
}
@Test
fun `KotlinProjectStructureMetadata jvmAndAndroidMain exists in android variants`() {
project.evaluate()
val metadata = assertNotNull(buildKotlinProjectStructureMetadata(project))
assertTrue("jvmAndAndroidMain" in metadata.sourceSetNamesByVariantName["debugApiElements"].orEmpty())
assertTrue("jvmAndAndroidMain" in metadata.sourceSetNamesByVariantName["debugRuntimeElements"].orEmpty())
assertTrue("jvmAndAndroidMain" in metadata.sourceSetNamesByVariantName["releaseApiElements"].orEmpty())
assertTrue("jvmAndAndroidMain" in metadata.sourceSetNamesByVariantName["releaseRuntimeElements"].orEmpty())
}
@Test
fun `Android Kotlin Components are marked as not publishable when variant is not published`() {
val target = kotlin.targets.getByName("android") as KotlinAndroidTarget
target.publishLibraryVariants = emptyList()
project.evaluate()
val kotlinComponents = target.kotlinComponents
assertTrue(kotlinComponents.isNotEmpty(), "Expected at least one KotlinComponent to be present")
kotlinComponents.forEach { component ->
assertFalse(component.publishable, "Expected component to not publishable, because no publication is configured")
}
}
@Test
fun `Android Kotlin Components are marked as publishable when variant is published`() {
val target = kotlin.targets.getByName("android") as KotlinAndroidTarget
target.publishLibraryVariants = listOf("release")
project.evaluate()
val kotlinComponents = target.kotlinComponents
assertTrue(kotlinComponents.isNotEmpty(), "Expected at least one KotlinComponent to be present")
kotlinComponents.forEach { component ->
val isReleaseComponent = "release" in component.name.toLowerCase()
if (isReleaseComponent) {
assertTrue(component.publishable, "Expected release component to be marked as publishable")
} else {
assertFalse(component.publishable, "Expected non-release component to be marked as not publishable")
}
}
}
}
@@ -3,7 +3,7 @@
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/ */
// TODO KT-34102 // TODO KT-34102
@file:Suppress("invisible_reference", "invisible_member") @file:Suppress("invisible_reference", "invisible_member", "FunctionName")
package org.jetbrains.kotlin.gradle package org.jetbrains.kotlin.gradle
import com.android.build.gradle.LibraryExtension import com.android.build.gradle.LibraryExtension
@@ -1,4 +1,3 @@
/* /*
* Copyright 2010-2016 JetBrains s.r.o. * Copyright 2010-2016 JetBrains s.r.o.
* *
@@ -28,6 +27,8 @@ import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
import org.jetbrains.kotlin.gradle.logging.kotlinWarn import org.jetbrains.kotlin.gradle.logging.kotlinWarn
import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile
import org.jetbrains.kotlin.gradle.utils.SingleWarningPerBuild import org.jetbrains.kotlin.gradle.utils.SingleWarningPerBuild
import org.jetbrains.kotlin.gradle.utils.androidPluginIds
import java.util.concurrent.atomic.AtomicBoolean
abstract class KotlinPlatformPluginBase(protected val platformName: String) : Plugin<Project> { abstract class KotlinPlatformPluginBase(protected val platformName: String) : Plugin<Project> {
companion object { companion object {
@@ -182,6 +183,7 @@ open class KotlinPlatformImplementationPluginBase(platformName: String) : Kotlin
private fun getKotlinSourceSetsSafe(project: Project): NamedDomainObjectCollection<out Named> { private fun getKotlinSourceSetsSafe(project: Project): NamedDomainObjectCollection<out Named> {
// Access through reflection, because another project's KotlinProjectExtension might be loaded by a different class loader: // Access through reflection, because another project's KotlinProjectExtension might be loaded by a different class loader:
val kotlinExt = project.extensions.getByName("kotlin") val kotlinExt = project.extensions.getByName("kotlin")
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
val sourceSets = kotlinExt.javaClass.getMethod("getSourceSets").invoke(kotlinExt) as NamedDomainObjectCollection<out Named> val sourceSets = kotlinExt.javaClass.getMethod("getSourceSets").invoke(kotlinExt) as NamedDomainObjectCollection<out Named>
return sourceSets return sourceSets
@@ -210,8 +212,24 @@ open class KotlinPlatformImplementationPluginBase(platformName: String) : Kotlin
internal fun <T> Project.whenEvaluated(fn: Project.() -> T) { internal fun <T> Project.whenEvaluated(fn: Project.() -> T) {
if (state.executed) { if (state.executed) {
fn() fn()
} else { return
afterEvaluate { it.fn() } }
/* Make sure that all afterEvaluate blocks from the AndroidPlugin get scheduled first */
val isDispatched = AtomicBoolean(false)
androidPluginIds.forEach { androidPluginId ->
pluginManager.withPlugin(androidPluginId) {
if (!isDispatched.getAndSet(true)) {
afterEvaluate { fn() }
}
}
}
afterEvaluate {
/* If no Android plugin was loaded, then the action was not dispatched and we can freely execute it now */
if (!isDispatched.getAndSet(true)) {
fn()
}
} }
} }
@@ -796,11 +796,6 @@ abstract class AbstractAndroidProjectHandler(private val kotlinConfigurationTool
kotlinOptions.noJdk = true kotlinOptions.noJdk = true
ext.addExtension(KOTLIN_OPTIONS_DSL_NAME, kotlinOptions) ext.addExtension(KOTLIN_OPTIONS_DSL_NAME, kotlinOptions)
val androidPluginIds = listOf(
"android", "com.android.application", "android-library", "com.android.library",
"com.android.test", "com.android.feature", "com.android.dynamic-feature", "com.android.instantapp"
)
val plugin by lazy { val plugin by lazy {
androidPluginIds.asSequence() androidPluginIds.asSequence()
.mapNotNull { project.plugins.findPlugin(it) as? BasePlugin } .mapNotNull { project.plugins.findPlugin(it) as? BasePlugin }
@@ -904,6 +899,7 @@ abstract class AbstractAndroidProjectHandler(private val kotlinConfigurationTool
// Trivial mapping of Android variants to Android source set names is impossible here, // Trivial mapping of Android variants to Android source set names is impossible here,
// because some variants have their dedicated source sets with mismatching names, // because some variants have their dedicated source sets with mismatching names,
// because some variants have their dedicated source sets with mismatching names,
// e.g. variant 'fooBarDebugAndroidTest' <-> source set 'androidTestFooBarDebug' // e.g. variant 'fooBarDebugAndroidTest' <-> source set 'androidTestFooBarDebug'
// In single-platform projects, the Kotlin compilations already reference the Android plugin's configurations by the names, // In single-platform projects, the Kotlin compilations already reference the Android plugin's configurations by the names,
@@ -974,6 +970,8 @@ abstract class AbstractAndroidProjectHandler(private val kotlinConfigurationTool
// Register the source only after the task is created, because the task is required for that: // Register the source only after the task is created, because the task is required for that:
compilation.source(defaultSourceSet) compilation.source(defaultSourceSet)
compilation.androidVariant.forEachKotlinSourceSet { kotlinSourceSet -> compilation.source(kotlinSourceSet) }
} }
private fun postprocessVariant( private fun postprocessVariant(
@@ -983,7 +981,6 @@ abstract class AbstractAndroidProjectHandler(private val kotlinConfigurationTool
androidExt: BaseExtension, androidExt: BaseExtension,
androidPlugin: BasePlugin androidPlugin: BasePlugin
) { ) {
val javaTask = variantData.getJavaTaskProvider()
getTestedVariantData(variantData)?.let { testedVariant -> getTestedVariantData(variantData)?.let { testedVariant ->
val testedVariantName = getVariantName(testedVariant) val testedVariantName = getVariantName(testedVariant)
@@ -991,34 +988,24 @@ abstract class AbstractAndroidProjectHandler(private val kotlinConfigurationTool
compilation.associateWith(testedCompilation) compilation.associateWith(testedCompilation)
} }
val javaTask = variantData.getJavaTaskProvider()
val kotlinTask = compilation.compileKotlinTaskProvider val kotlinTask = compilation.compileKotlinTaskProvider
processAndroidKotlinAndJavaSources( compilation.androidVariant.forEachJavaSourceDir { sources -> kotlinTask.configure { it.source(sources) } }
compilation,
addKotlinSources = { kotlinSourceSet -> compilation.source(kotlinSourceSet) },
addJavaSources = { sources -> compilation.compileKotlinTaskProvider.configure { it.source(sources) } }
)
wireKotlinTasks(project, compilation, androidPlugin, androidExt, variantData, javaTask, kotlinTask) wireKotlinTasks(project, compilation, androidPlugin, androidExt, variantData, javaTask, kotlinTask)
} }
} }
private fun getAllJavaSources(variantData: BaseVariant): Iterable<File> = internal inline fun BaseVariant.forEachKotlinSourceSet(action: (KotlinSourceSet) -> Unit) {
variantData.getSourceFolders(SourceKind.JAVA).map { it.dir } sourceSets
.mapNotNull { provider -> provider.getConvention(KOTLIN_DSL_NAME) as? KotlinSourceSet }
internal fun processAndroidKotlinAndJavaSources( .forEach(action)
compilation: KotlinJvmAndroidCompilation,
addKotlinSources: (KotlinSourceSet) -> Unit,
addJavaSources: (Iterable<File>) -> Unit
) {
val variantData = compilation.androidVariant
for (provider in variantData.sourceSets) {
val kotlinSourceSet = provider.getConvention(KOTLIN_DSL_NAME) as? KotlinSourceSet ?: continue
addKotlinSources(kotlinSourceSet)
}
addJavaSources(getAllJavaSources(variantData))
} }
internal inline fun BaseVariant.forEachJavaSourceDir(action: (File) -> Unit) {
getSourceFolders(SourceKind.JAVA).map { it.dir }.forEach(action)
}
internal fun configureJavaTask( internal fun configureJavaTask(
kotlinTaskProvider: TaskProvider<out KotlinCompile>, kotlinTaskProvider: TaskProvider<out KotlinCompile>,
javaTaskProvider: TaskProvider<out AbstractCompile> javaTaskProvider: TaskProvider<out AbstractCompile>
@@ -51,11 +51,7 @@ internal fun Project.runOnceAfterEvaluated(name: String, task: TaskProvider<*>,
} }
internal fun Project.runOnceAfterEvaluated(runOnce: RunOnceAfterEvaluated, task: TaskProvider<*>) { internal fun Project.runOnceAfterEvaluated(runOnce: RunOnceAfterEvaluated, task: TaskProvider<*>) {
if (state.executed) { whenEvaluated { runOnce.onEvaluated() }
runOnce.onEvaluated()
} else {
afterEvaluate { runOnce.onEvaluated() }
}
task.configure { task.configure {
runOnce.onConfigure() runOnce.onConfigure()
} }
@@ -114,11 +114,8 @@ class SubpluginEnvironment(
internal fun addCompilationSourcesToExternalCompileTask(compilation: KotlinCompilation<*>, task: TaskProvider<out AbstractCompile>) { internal fun addCompilationSourcesToExternalCompileTask(compilation: KotlinCompilation<*>, task: TaskProvider<out AbstractCompile>) {
if (compilation is KotlinJvmAndroidCompilation) { if (compilation is KotlinJvmAndroidCompilation) {
processAndroidKotlinAndJavaSources( compilation.androidVariant.forEachKotlinSourceSet { sourceSet -> task.configure { it.source(sourceSet.kotlin) } }
compilation, compilation.androidVariant.forEachJavaSourceDir { sources -> task.configure { it.source(sources) } }
addKotlinSources = { sourceSet -> task.configure { it.source(sourceSet.kotlin) } },
addJavaSources = { sources -> task.configure { it.source(sources) }}
)
} else { } else {
task.configure { taskInstance -> task.configure { taskInstance ->
compilation.allKotlinSourceSets.forEach { sourceSet -> taskInstance.source(sourceSet.kotlin) } compilation.allKotlinSourceSets.forEach { sourceSet -> taskInstance.source(sourceSet.kotlin) }
@@ -17,6 +17,7 @@ import org.jetbrains.kotlin.gradle.plugin.LanguageSettingsBuilder
import org.jetbrains.kotlin.gradle.plugin.mpp.* import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.gradle.plugin.mpp.GranularMetadataTransformation import org.jetbrains.kotlin.gradle.plugin.mpp.GranularMetadataTransformation
import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution
import org.jetbrains.kotlin.gradle.plugin.whenEvaluated
import org.jetbrains.kotlin.gradle.utils.* import org.jetbrains.kotlin.gradle.utils.*
import java.io.File import java.io.File
import java.lang.reflect.Constructor import java.lang.reflect.Constructor
@@ -84,7 +85,7 @@ class DefaultKotlinSourceSet(
// Fail-fast approach: check on each new added edge and report a circular dependency at once when the edge is added. // Fail-fast approach: check on each new added edge and report a circular dependency at once when the edge is added.
checkForCircularDependencies() checkForCircularDependencies()
project.afterEvaluate { defaultSourceSetLanguageSettingsChecker.runAllChecks(this, other) } project.whenEvaluated { defaultSourceSetLanguageSettingsChecker.runAllChecks(this@DefaultKotlinSourceSet, other) }
} }
private val dependsOnSourceSetsImpl = mutableSetOf<KotlinSourceSet>() private val dependsOnSourceSetsImpl = mutableSetOf<KotlinSourceSet>()
@@ -43,7 +43,9 @@ open class KotlinAndroidTarget(
* all library variants will be published, but not test or application variants. */ * all library variants will be published, but not test or application variants. */
var publishLibraryVariants: List<String>? = listOf() var publishLibraryVariants: List<String>? = listOf()
// Workaround for Groovy GString items in a list: // Workaround for Groovy GString items in a list:
set(value) { field = value?.map(Any::toString) } set(value) {
field = value?.map(Any::toString)
}
/** Add Android library variant names to [publishLibraryVariants]. */ /** Add Android library variant names to [publishLibraryVariants]. */
fun publishLibraryVariants(vararg names: String) { fun publishLibraryVariants(vararg names: String) {
@@ -91,11 +93,16 @@ open class KotlinAndroidTarget(
KotlinAndroidPlugin.androidTargetHandler(project.getKotlinPluginVersion()!!, this).doCreateComponents() KotlinAndroidPlugin.androidTargetHandler(project.getKotlinPluginVersion()!!, this).doCreateComponents()
} }
private fun isVariantPublished(variant: BaseVariant): Boolean {
return publishLibraryVariants?.contains(getVariantName(variant)) ?: true
}
private fun AbstractAndroidProjectHandler.doCreateComponents(): Set<KotlinTargetComponent> { private fun AbstractAndroidProjectHandler.doCreateComponents(): Set<KotlinTargetComponent> {
val publishableVariants = mutableListOf<BaseVariant>() val publishableVariants = mutableListOf<BaseVariant>()
.apply { project.forEachVariant { add(it) } } .apply { project.forEachVariant { add(it) } }
.toList() // Defensive copy against unlikely modification by the lambda that captures the list above in forEachVariant { } .toList() // Defensive copy against unlikely modification by the lambda that captures the list above in forEachVariant { }
.filter { getLibraryOutputTask(it) != null && publishLibraryVariants?.contains(getVariantName(it)) ?: true } .filter { getLibraryOutputTask(it) != null }
val publishableVariantGroups = publishableVariants.groupBy { variant -> val publishableVariantGroups = publishableVariants.groupBy { variant ->
val flavorNames = getFlavorNames(variant) val flavorNames = getFlavorNames(variant)
@@ -118,12 +125,12 @@ open class KotlinAndroidTarget(
val artifactClassifier = buildTypeName.takeIf { it != "release" && publishLibraryVariantsGroupedByFlavor } val artifactClassifier = buildTypeName.takeIf { it != "release" && publishLibraryVariantsGroupedByFlavor }
val usageContexts = createAndroidUsageContexts(androidVariant, compilation, artifactClassifier)
createKotlinVariant( createKotlinVariant(
lowerCamelCaseName(compilation.target.name, *flavorGroupNameParts.toTypedArray()), lowerCamelCaseName(compilation.target.name, *flavorGroupNameParts.toTypedArray()),
compilation, compilation,
usageContexts createAndroidUsageContexts(androidVariant, compilation, artifactClassifier)
).apply { ).apply {
publishable = isVariantPublished(androidVariant)
sourcesArtifacts = setOf( sourcesArtifacts = setOf(
sourcesJarArtifact( sourcesJarArtifact(
compilation, compilation.disambiguateName(""), compilation, compilation.disambiguateName(""),
@@ -148,10 +155,10 @@ open class KotlinAndroidTarget(
if (publishLibraryVariantsGroupedByFlavor) { if (publishLibraryVariantsGroupedByFlavor) {
JointAndroidKotlinTargetComponent( JointAndroidKotlinTargetComponent(
this@KotlinAndroidTarget, target = this@KotlinAndroidTarget,
nestedVariants, nestedVariants = nestedVariants.filter { it.publishable }.toSet(),
flavorGroupNameParts, flavorNames = flavorGroupNameParts,
nestedVariants.flatMap { it.sourcesArtifacts }.toSet() sourcesArtifacts = nestedVariants.filter { it.publishable }.flatMap { it.sourcesArtifacts }.toSet()
) )
} else { } else {
nestedVariants.single() nestedVariants.single()
@@ -20,6 +20,7 @@ import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.* import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.plugin.mpp.* import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.gradle.plugin.mpp.CompilationSourceSetUtil.compilationsBySourceSets
import org.jetbrains.kotlin.gradle.plugin.sources.* import org.jetbrains.kotlin.gradle.plugin.sources.*
import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatsService import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatsService
import org.jetbrains.kotlin.gradle.tasks.* import org.jetbrains.kotlin.gradle.tasks.*
@@ -163,7 +164,7 @@ class KotlinMetadataTargetConfigurator(kotlinPluginVersion: String) :
private fun setupDependencyTransformationForCommonSourceSets(target: KotlinMetadataTarget) { private fun setupDependencyTransformationForCommonSourceSets(target: KotlinMetadataTarget) {
target.project.whenEvaluated { target.project.whenEvaluated {
val publishedCommonSourceSets: Set<KotlinSourceSet> = getPublishedCommonSourceSets(project) val publishedCommonSourceSets: Set<KotlinSourceSet> = getCommonSourceSetsForMetadataCompilation(project)
kotlinExtension.sourceSets.all { kotlinExtension.sourceSets.all {
setupDependencyTransformationForSourceSet(target.project, it, it in publishedCommonSourceSets) setupDependencyTransformationForSourceSet(target.project, it, it in publishedCommonSourceSets)
@@ -177,12 +178,17 @@ class KotlinMetadataTargetConfigurator(kotlinPluginVersion: String) :
) = target.project.whenEvaluated { ) = target.project.whenEvaluated {
// Do this after all targets are configured by the user build script // Do this after all targets are configured by the user build script
val publishedCommonSourceSets: Set<KotlinSourceSet> = getPublishedCommonSourceSets(project) val publishedCommonSourceSets: Set<KotlinSourceSet> = getCommonSourceSetsForMetadataCompilation(project)
val hostSpecificSourceSets: Set<KotlinSourceSet> = getHostSpecificSourceSets(project).toSet() val hostSpecificSourceSets: Set<KotlinSourceSet> = getHostSpecificSourceSets(project).toSet()
val sourceSetsWithMetadataCompilations: Map<KotlinSourceSet, AbstractKotlinCompilation<*>> = val sourceSetsWithMetadataCompilations: Map<KotlinSourceSet, AbstractKotlinCompilation<*>> = publishedCommonSourceSets
publishedCommonSourceSets.associate { sourceSet -> .associateWith { sourceSet ->
sourceSet to createMetadataCompilation(target, sourceSet, allMetadataJar, sourceSet in hostSpecificSourceSets) createMetadataCompilation(target, sourceSet, allMetadataJar, sourceSet in hostSpecificSourceSets)
}
.onEach { (sourceSet, compilation) ->
if (!isMetadataCompilationSupported(target.project, sourceSet)) {
compilation.compileKotlinTaskProvider.configure { it.enabled = false }
}
} }
if (project.isCompatibilityMetadataVariantEnabled) { if (project.isCompatibilityMetadataVariantEnabled) {
@@ -195,6 +201,30 @@ class KotlinMetadataTargetConfigurator(kotlinPluginVersion: String) :
} }
} }
private fun isMetadataCompilationSupported(project: Project, sourceSet: KotlinSourceSet): Boolean {
val platforms = compilationsBySourceSets(project)[sourceSet].orEmpty()
.filter { it.target !is KotlinMetadataTarget }
.map { it.target.platformType }.distinct()
/*
Android and jvm do share the JVM backend which is not supported for metadata compilation
See [HMPP: Bad IDEA dependencies for JVM and Android intermediate source set](https://youtrack.jetbrains.com/issue/KT-42383)
See [HMPP: JVM and Android intermediate source set publication](https://youtrack.jetbrains.com/issue/KT-42468)
*/
if (platforms.all { it == KotlinPlatformType.jvm || it == KotlinPlatformType.androidJvm }) {
return false
}
/* Metadata compilation for a single platform is only supported native and common source sets */
if (platforms.size == 1) {
val platform = platforms.single()
return platform == KotlinPlatformType.native || platform == KotlinPlatformType.common
}
/* Source sets sharing code between multiple backends are supported */
return true
}
private fun configureProjectStructureMetadataGeneration(project: Project, allMetadataJar: TaskProvider<out Jar>) { private fun configureProjectStructureMetadataGeneration(project: Project, allMetadataJar: TaskProvider<out Jar>) {
val generateMetadata = project.createGenerateProjectStructureMetadataTask() val generateMetadata = project.createGenerateProjectStructureMetadataTask()
@@ -256,7 +286,7 @@ class KotlinMetadataTargetConfigurator(kotlinPluginVersion: String) :
val compilationName = sourceSet.name val compilationName = sourceSet.name
val platformCompilations = CompilationSourceSetUtil.compilationsBySourceSets(project).getValue(sourceSet) val platformCompilations = compilationsBySourceSets(project).getValue(sourceSet)
val isNativeSourceSet = platformCompilations.all { compilation -> compilation.target is KotlinNativeTarget } val isNativeSourceSet = platformCompilations.all { compilation -> compilation.target is KotlinNativeTarget }
@@ -271,7 +301,6 @@ class KotlinMetadataTargetConfigurator(kotlinPluginVersion: String) :
return compilationFactory.create(compilationName).apply { return compilationFactory.create(compilationName).apply {
target.compilations.add(this@apply) target.compilations.add(this@apply)
addExactSourceSetsEagerly(setOf(sourceSet)) addExactSourceSetsEagerly(setOf(sourceSet))
configureMetadataDependenciesForCompilation(this@apply) configureMetadataDependenciesForCompilation(this@apply)
if (!isHostSpecific) { if (!isHostSpecific) {
@@ -513,14 +542,18 @@ class KotlinMetadataTargetConfigurator(kotlinPluginVersion: String) :
} }
} }
internal fun getPublishedCommonSourceSets(project: Project): Set<KotlinSourceSet> { /**
* @return All common source sets that can potentially be published. Right now, not all combinations of platforms actually
* support metadata compilation (see [KotlinMetadataTargetConfigurator.isMetadataCompilationSupported].
* Those compilations will be created but the corresponding tasks will be disabled.
*/
internal fun getCommonSourceSetsForMetadataCompilation(project: Project): Set<KotlinSourceSet> {
val compilationsBySourceSet: Map<KotlinSourceSet, Set<KotlinCompilation<*>>> = val compilationsBySourceSet: Map<KotlinSourceSet, Set<KotlinCompilation<*>>> =
CompilationSourceSetUtil.compilationsBySourceSets(project) compilationsBySourceSets(project)
val sourceSetsUsedInMultipleTargets = compilationsBySourceSet.filterValues { compilations -> val sourceSetsUsedInMultipleTargets = compilationsBySourceSet.filterValues { compilations ->
compilations.map { it.target.platformType }.distinct().run { compilations.map { it.target.platformType }.distinct().run {
size > 1 && toSet() != setOf(KotlinPlatformType.androidJvm, KotlinPlatformType.jvm) || size > 1 || singleOrNull() == KotlinPlatformType.native && compilations.map { it.target }.distinct().size > 1
singleOrNull() == KotlinPlatformType.native && compilations.map { it.target }.distinct().size > 1
// TODO: platform-shared source sets other than Kotlin/Native ones are not yet supported; support will be needed for JVM, JS // TODO: platform-shared source sets other than Kotlin/Native ones are not yet supported; support will be needed for JVM, JS
} }
} }
@@ -0,0 +1,11 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.utils
internal val androidPluginIds = listOf(
"android", "com.android.application", "android-library", "com.android.library",
"com.android.test", "com.android.feature", "com.android.dynamic-feature", "com.android.instantapp"
)