[Gradle][KT-53342] Implement Multiplatform/Android V2 source set layout

^KT-53342 Verification Pending
This commit is contained in:
sebastian.sellmair
2022-08-01 14:36:05 +02:00
committed by Space
parent 38ada5c9fc
commit fe5a4bb12a
33 changed files with 875 additions and 239 deletions
+4 -1
View File
@@ -8,4 +8,7 @@
<component name="KotlinCompilerSettings">
<option name="additionalArguments" value="-version -Xallow-kotlin-package -Xskip-metadata-version-check" />
</component>
</project>
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.8.0-dev-1006" />
</component>
</project>
@@ -5,7 +5,6 @@
package org.jetbrains.kotlin.gradle.internal
import com.android.build.gradle.TestedExtension
import com.android.build.gradle.api.TestVariant
import com.android.build.gradle.api.UnitTestVariant
import org.gradle.api.InvalidUserDataException
@@ -27,10 +26,12 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.GradleKpmModule
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.hasKpmModel
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.kpmModules
import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope
import org.jetbrains.kotlin.gradle.plugin.sources.android.AndroidBaseSourceSetName
import org.jetbrains.kotlin.gradle.plugin.sources.android.AndroidVariantType
import org.jetbrains.kotlin.gradle.plugin.sources.android.androidSourceSetInfoOrNull
import org.jetbrains.kotlin.gradle.plugin.sources.dependsOnClosure
import org.jetbrains.kotlin.gradle.plugin.sources.sourceSetDependencyConfigurationByScope
import org.jetbrains.kotlin.gradle.plugin.sources.withDependsOnClosure
import org.jetbrains.kotlin.gradle.targets.android.findAndroidTarget
import org.jetbrains.kotlin.gradle.targets.jvm.JvmCompilationsTestRunSource
import org.jetbrains.kotlin.gradle.tasks.locateTask
import org.jetbrains.kotlin.gradle.testing.KotlinTaskTestRun
@@ -112,10 +113,7 @@ internal fun configureStdlibDefaultDependency(project: Project) = with(project)
project.tryWithDependenciesIfUnresolved(scopeConfiguration) { dependencies ->
val scopeToAddStdlibDependency =
if (isRelatedToAndroidTestSourceSet(
project,
kotlinSourceSet
)
if (isRelatedToAndroidTestSourceSet(kotlinSourceSet)
) // AGP deprecates API configurations in test source sets
KotlinDependencyScope.IMPLEMENTATION_SCOPE
else KotlinDependencyScope.API_SCOPE
@@ -195,7 +193,9 @@ private fun chooseAndAddStdlibDependency(
val stdlibModuleName = when (sourceSetStdlibPlatformType) {
KotlinPlatformType.jvm -> stdlibModuleForJvmCompilations(compilations)
KotlinPlatformType.androidJvm ->
if (kotlinSourceSet.name == androidMainSourceSetName(project)) stdlibModuleForJvmCompilations(compilations) else null
if (kotlinSourceSet.androidSourceSetInfoOrNull?.androidSourceSetName == AndroidBaseSourceSetName.Main.name)
stdlibModuleForJvmCompilations(compilations) else null
KotlinPlatformType.js -> "kotlin-stdlib-js"
KotlinPlatformType.wasm -> "kotlin-stdlib-wasm"
KotlinPlatformType.native -> null
@@ -214,28 +214,9 @@ private fun chooseAndAddStdlibDependency(
}
}
private fun androidMainSourceSetName(project: Project): String {
val target = project.findAndroidTarget() ?: error("No Android target found")
return AndroidProjectHandler.kotlinSourceSetNameForAndroidSourceSet(target, "main")
}
private fun isRelatedToAndroidTestSourceSet(project: Project, kotlinSourceSet: KotlinSourceSet): Boolean {
val androidExtension = project.extensions.findByName("android") as? TestedExtension ?: return false
val androidTarget = project.findAndroidTarget() ?: return false
if (androidTarget.compilations.any {
(it.androidVariant is UnitTestVariant || it.androidVariant is TestVariant) && it.defaultSourceSet === kotlinSourceSet
}
) return true
(androidExtension.testVariants + androidExtension.unitTestVariants).forEach { variant ->
if (variant.sourceSets.any {
kotlinSourceSet.name == AndroidProjectHandler.kotlinSourceSetNameForAndroidSourceSet(androidTarget, it.name)
}
) return true
}
return false
private fun isRelatedToAndroidTestSourceSet(kotlinSourceSet: KotlinSourceSet): Boolean {
val androidVariantType = kotlinSourceSet.androidSourceSetInfoOrNull?.androidVariantType ?: return false
return androidVariantType in setOf(AndroidVariantType.UnitTest, AndroidVariantType.InstrumentedTest)
}
private val stdlibModules = setOf("kotlin-stdlib-common", "kotlin-stdlib", "kotlin-stdlib-jdk7", "kotlin-stdlib-jdk8", "kotlin-stdlib-js")
@@ -191,7 +191,7 @@ open class KotlinPlatformImplementationPluginBase(platformName: String) : Kotlin
@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")
@Deprecated("Retained for older Kotlin/Native MPP plugin binary compatibility", level = DeprecationLevel.ERROR)
protected val SourceSet.kotlin: SourceDirectorySet?
get() {
@Suppress("DEPRECATION")
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.gradle.internal.testing.TCServiceMessageOutputStream
import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType.Companion.jsCompilerProperty
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_ABI_SNAPSHOT
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_MPP_ANDROID_GRADLE_PLUGIN_COMPATIBILITY_NO_WARN
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_MPP_ANDROID_SOURCE_SET_LAYOUT_VERSION
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_MPP_ENABLE_CINTEROP_COMMONIZATION
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_MPP_ENABLE_GRANULAR_SOURCE_SETS_METADATA
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_MPP_ENABLE_OPTIMISTIC_NUMBER_COMMONIZATION
@@ -209,6 +210,9 @@ internal class PropertiesProvider private constructor(private val project: Proje
val ignoreAndroidGradlePluginCompatibilityIssues: Boolean
get() = booleanProperty(KOTLIN_MPP_ANDROID_GRADLE_PLUGIN_COMPATIBILITY_NO_WARN) ?: false
val mppAndroidSourceSetLayoutVersion: Int?
get() = property(KOTLIN_MPP_ANDROID_SOURCE_SET_LAYOUT_VERSION)?.toIntOrNull()
val ignoreDisabledCInteropCommonization: Boolean
get() = booleanProperty("$KOTLIN_MPP_ENABLE_CINTEROP_COMMONIZATION.nowarn") ?: false
@@ -467,6 +471,7 @@ internal class PropertiesProvider private constructor(private val project: Proje
const val KOTLIN_MPP_HIERARCHICAL_STRUCTURE_BY_DEFAULT = "kotlin.internal.mpp.hierarchicalStructureByDefault"
const val KOTLIN_MPP_HIERARCHICAL_STRUCTURE_SUPPORT = "kotlin.mpp.hierarchicalStructureSupport"
const val KOTLIN_MPP_ANDROID_GRADLE_PLUGIN_COMPATIBILITY_NO_WARN = "kotlin.mpp.androidGradlePluginCompatibility.nowarn"
const val KOTLIN_MPP_ANDROID_SOURCE_SET_LAYOUT_VERSION = "kotlin.mpp.androidSourceSetLayoutVersion"
const val KOTLIN_NATIVE_DEPENDENCY_PROPAGATION = "kotlin.native.enableDependencyPropagation"
const val KOTLIN_MPP_ENABLE_OPTIMISTIC_NUMBER_COMMONIZATION = "kotlin.mpp.enableOptimisticNumberCommonization"
const val KOTLIN_KPM_EXPERIMENTAL_MODEL_MAPPING = "kotlin.kpm.experimentalModelMapping"
@@ -94,7 +94,7 @@ internal fun addCompilationSourcesToExternalCompileTask(
task: TaskProvider<out AbstractKotlinCompileTool<*>>
) {
if (compilation is KotlinJvmAndroidCompilation) {
compilation.androidVariant.forEachKotlinSourceDirectorySet { sourceSet ->
compilation.androidVariant.forEachKotlinSourceDirectorySet(compilation.project) { sourceSet ->
task.configure { it.setSource(sourceSet) }
}
compilation.androidVariant.forEachJavaSourceDir { sources ->
@@ -9,6 +9,7 @@ import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.extraProperties
import org.jetbrains.kotlin.gradle.plugin.sources.android.androidSourceSetInfoOrNull
import org.jetbrains.kotlin.gradle.plugin.whenEvaluated
object UnusedSourceSetsChecker {
@@ -39,13 +40,10 @@ object UnusedSourceSetsChecker {
fun checkSourceSets(project: Project) {
project.whenEvaluated {
val compilationsBySourceSet = CompilationSourceSetUtil.compilationsBySourceSets(project)
val unusedSourceSets = project.kotlinExtension.sourceSets.filter {
val unusedSourceSets = project.kotlinExtension.sourceSets
// Ignoring Android source sets
val isAndroidSourceSet = it.extraProperties.has(IS_ANDROID_SOURCE_SET) &&
(it.extraProperties[IS_ANDROID_SOURCE_SET] as Boolean)
!isAndroidSourceSet && compilationsBySourceSet[it]?.isEmpty() ?: true
}
.filter { it.androidSourceSetInfoOrNull == null }
.filter { compilationsBySourceSet[it]?.isEmpty() ?: true }
if (unusedSourceSets.isNotEmpty()) {
reportUnusedSourceSets(project, unusedSourceSets.toSet())
}
@@ -1,148 +0,0 @@
/*
* 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.plugin.mpp
import com.android.build.gradle.BaseExtension
import com.android.build.gradle.api.AndroidSourceSet
import org.gradle.api.file.SourceDirectorySet
import org.gradle.api.internal.plugins.DslObject
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
import org.jetbrains.kotlin.gradle.internal.Kapt3GradleSubplugin
import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.plugin.AndroidProjectHandler.Companion.kotlinSourceSetNameForAndroidSourceSet
import org.jetbrains.kotlin.gradle.plugin.sources.KotlinSourceSetFactory.Companion.defaultSourceFolder
internal const val IS_ANDROID_SOURCE_SET = "android-source-set"
internal fun syncKotlinAndAndroidSourceSets(target: KotlinAndroidTarget) {
val project = target.project
val android = project.extensions.getByName("android") as BaseExtension
val kotlinExtension = project.kotlinExtension
val createdKotlinSourceSets = mutableSetOf<String>()
android.sourceSets.all { androidSourceSet ->
val kotlinSourceSetName = kotlinSourceSetNameForAndroidSourceSet(target, androidSourceSet.name)
createdKotlinSourceSets.add(kotlinSourceSetName)
val kotlinSourceSet = kotlinExtension.sourceSets.maybeCreate(kotlinSourceSetName)
// Marking KotlinSource set is created for Android compilation to ignore it in UnusedSourceSetChecker
kotlinSourceSet.extraProperties.set(IS_ANDROID_SOURCE_SET, true)
androidSourceSet.addKotlinSources(kotlinSourceSet)
createDefaultDependsOnEdges(target, kotlinSourceSet, androidSourceSet)
syncKotlinAndAndroidSourceDirs(target, kotlinSourceSet, androidSourceSet)
syncKotlinAndAndroidResources(target, kotlinSourceSet, androidSourceSet)
ifKaptEnabled(project) {
Kapt3GradleSubplugin.createAptConfigurationIfNeeded(project, androidSourceSet.name)
}
}
}
internal val AndroidSourceSet.kotlinSourceSet: SourceDirectorySet
get() = checkNotNull(getExtension(KOTLIN_DSL_NAME)) { "Missing kotlinSourceSet for Android source set $name" }
internal fun AndroidSourceSet.addKotlinSources(kotlinSourceSet: KotlinSourceSet) {
@Suppress("DEPRECATION") addConvention(KOTLIN_DSL_NAME, kotlinSourceSet)
DslObject(this).addExtension(KOTLIN_DSL_NAME, kotlinSourceSet.kotlin)
}
private fun createDefaultDependsOnEdges(
target: KotlinAndroidTarget,
kotlinSourceSet: KotlinSourceSet,
androidSourceSet: AndroidSourceSet
) {
val commonSourceSetName = when (androidSourceSet.name) {
"main" -> "commonMain"
"test" -> "commonTest"
"androidTest" -> "commonTest"
else -> return
}
val commonSourceSet = target.project.kotlinExtension.sourceSets.findByName(commonSourceSetName) ?: return
kotlinSourceSet.dependsOn(commonSourceSet)
}
private fun syncKotlinAndAndroidSourceDirs(
target: KotlinAndroidTarget, kotlinSourceSet: KotlinSourceSet, androidSourceSet: AndroidSourceSet
) {
val disambiguationClassifier = target.disambiguationClassifier
/*
Mitigate ambiguity!
Example: disambiguationClassifier="android"
Source Directory "src/androidTest/kotlin"
-- could be claimed by kotlin {android}Test (unit test)
-- could be claimed by Android androidTest (instrumented test)
The Kotlin source set would win in this scenario.
*/
if (!androidSourceSet.name.startsWith(disambiguationClassifier)) {
kotlinSourceSet.kotlin.srcDir("src/${androidSourceSet.name}/kotlin")
}
kotlinSourceSet.kotlin.srcDirs(*androidSourceSet.java.srcDirs.toTypedArray())
/*
Make sure to include user configuration as well.
Unfortunately, there does not exist any "ad-hoc" API like `all`.
Therefore we sync the directories once again after evaluation
*/
target.project.whenEvaluated {
kotlinSourceSet.kotlin.srcDirs(*androidSourceSet.java.srcDirs.toTypedArray())
}
}
private fun syncKotlinAndAndroidResources(
target: KotlinAndroidTarget,
kotlinSourceSet: KotlinSourceSet,
androidSourceSet: AndroidSourceSet
) {
/*
Non-MPP projects will not have a different name for kotlin source sets vs Android source sets
Registering resource folders is unnecessary therefore.
*/
if (kotlinSourceSet.name == androidSourceSet.name) {
return
}
val project = target.project
androidSourceSet.resources.srcDirs(kotlinSourceSet.resources.srcDirs)
if (androidSourceSet.resources.srcDirs.isNotEmpty()) {
androidSourceSet.resources.srcDir(defaultSourceFolder(project, kotlinSourceSet.name, "resources"))
kotlinSourceSet.resources.srcDirs(androidSourceSet.resources.srcDirs)
}
if (androidSourceSet.assets.srcDirs.isNotEmpty()) {
androidSourceSet.assets.srcDir(defaultSourceFolder(project, kotlinSourceSet.name, "assets"))
}
if (androidSourceSet.res.srcDirs.isNotEmpty()) {
androidSourceSet.res.srcDir(defaultSourceFolder(project, kotlinSourceSet.name, "res"))
}
if (androidSourceSet.aidl.srcDirs.isNotEmpty()) {
androidSourceSet.aidl.srcDir(defaultSourceFolder(project, kotlinSourceSet.name, "aidl"))
}
if (androidSourceSet.renderscript.srcDirs.isNotEmpty()) {
androidSourceSet.renderscript.srcDir(defaultSourceFolder(project, kotlinSourceSet.name, "rs"))
}
if (androidSourceSet.jni.srcDirs.isNotEmpty()) {
androidSourceSet.jni.srcDir(defaultSourceFolder(project, kotlinSourceSet.name, "jni"))
}
if (androidSourceSet.jniLibs.srcDirs.isNotEmpty()) {
androidSourceSet.jniLibs.srcDir(defaultSourceFolder(project, kotlinSourceSet.name, "jniLibs"))
}
if (androidSourceSet.shaders.srcDirs.isNotEmpty()) {
androidSourceSet.shaders.srcDir(defaultSourceFolder(project, kotlinSourceSet.name, "shaders"))
}
}
@@ -0,0 +1,38 @@
/*
* Copyright 2010-2022 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.plugin.sources.android
import com.android.build.gradle.api.AndroidSourceSet
import org.gradle.api.NamedDomainObjectContainer
internal val NamedDomainObjectContainer<out AndroidSourceSet>.main: AndroidSourceSet
get() = getByName(AndroidBaseSourceSetName.Main.name)
internal val NamedDomainObjectContainer<out AndroidSourceSet>.test: AndroidSourceSet
get() = getByName(AndroidBaseSourceSetName.Test.name)
internal val NamedDomainObjectContainer<out AndroidSourceSet>.androidTest: AndroidSourceSet
get() = getByName(AndroidBaseSourceSetName.AndroidTest.name)
/*
Not written as enum class to avoid Enum.name ambiguity with 'source set name' semantics.
*/
internal sealed class AndroidBaseSourceSetName(val name: String) {
final override fun toString(): String = name
object Main : AndroidBaseSourceSetName("main")
object Test : AndroidBaseSourceSetName("test")
object AndroidTest : AndroidBaseSourceSetName("androidTest")
companion object {
fun byName(name: String): AndroidBaseSourceSetName? = when (name) {
Main.name -> Main
Test.name -> Test
AndroidTest.name -> AndroidTest
else -> null
}
}
}
@@ -0,0 +1,39 @@
/*
* Copyright 2010-2022 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.plugin.sources.android
import com.android.build.gradle.api.*
internal val BaseVariant.type: AndroidVariantType
get() = when (this) {
is UnitTestVariant -> AndroidVariantType.UnitTest
is TestVariant -> AndroidVariantType.InstrumentedTest
is ApplicationVariant, is LibraryVariant -> AndroidVariantType.Main
else -> AndroidVariantType.Unknown
}
internal val AndroidBaseSourceSetName.variantType: AndroidVariantType
get() = when (this) {
AndroidBaseSourceSetName.Main -> AndroidVariantType.Main
AndroidBaseSourceSetName.Test -> AndroidVariantType.UnitTest
AndroidBaseSourceSetName.AndroidTest -> AndroidVariantType.InstrumentedTest
}
internal enum class AndroidVariantType {
Main, UnitTest, InstrumentedTest, Unknown;
/**
* Every known type of Android variant has a 'base source set', which
* participates in all variants of sad type (main, test, androidTest, ...)
*/
val androidBaseSourceSetName: AndroidBaseSourceSetName?
get() = when (this) {
Main -> AndroidBaseSourceSetName.Main
UnitTest -> AndroidBaseSourceSetName.Test
InstrumentedTest -> AndroidBaseSourceSetName.AndroidTest
Unknown -> null
}
}
@@ -0,0 +1,69 @@
/*
* Copyright 2010-2022 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.plugin.sources.android
import com.android.build.gradle.BaseExtension
import com.android.build.gradle.api.AndroidSourceSet
import com.android.builder.model.SourceProvider
import org.gradle.api.Project
import org.gradle.api.UnknownDomainObjectException
import org.gradle.api.plugins.ExtensionAware
import org.jetbrains.kotlin.gradle.dsl.kotlinExtensionOrNull
import org.jetbrains.kotlin.gradle.plugin.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.utils.findByType
@ExperimentalKotlinGradlePluginApi
val KotlinSourceSet.androidSourceSetInfoOrNull: KotlinAndroidSourceSetInfo?
get() = (this as? ExtensionAware)?.extensions?.findByType()
@ExperimentalKotlinGradlePluginApi
var KotlinSourceSet.androidSourceSetInfo: KotlinAndroidSourceSetInfo
get() = androidSourceSetInfoOrNull ?: throw UnknownDomainObjectException("No 'androidSourceSetInfo' found on KotlinSourceSet: $name")
internal set(value) {
(this as ExtensionAware).extensions.add("androidSourceSetInfo", value)
}
@ExperimentalKotlinGradlePluginApi
fun Project.findAndroidSourceSet(kotlinSourceSet: KotlinSourceSet): AndroidSourceSet? {
val androidSourceSetInfo = kotlinSourceSet.androidSourceSetInfoOrNull ?: return null
val android = extensions.findByType<BaseExtension>() ?: return null
return android.sourceSets.getByName(androidSourceSetInfo.androidSourceSetName)
}
@ExperimentalKotlinGradlePluginApi
fun Project.findKotlinSourceSet(androidSourceSet: AndroidSourceSet): KotlinSourceSet? {
return findKotlinSourceSet(androidSourceSet.name)
}
@ExperimentalKotlinGradlePluginApi
fun Project.findKotlinSourceSet(androidSourceSet: SourceProvider): KotlinSourceSet? {
return findKotlinSourceSet(androidSourceSet.name)
}
private fun Project.findKotlinSourceSet(androidSourceSetName: String): KotlinSourceSet? {
return kotlinExtensionOrNull?.sourceSets.orEmpty()
.find { kotlinSourceSet -> kotlinSourceSet.androidSourceSetInfoOrNull?.androidSourceSetName == androidSourceSetName }
}
@ExperimentalKotlinGradlePluginApi
sealed class KotlinAndroidSourceSetInfo {
abstract val kotlinSourceSetName: String
abstract val androidSourceSetName: String
internal abstract val androidVariantType: AndroidVariantType
abstract val androidVariantNames: Set<String>
internal data class Mutable(
override val kotlinSourceSetName: String,
override val androidSourceSetName: String,
override var androidVariantType: AndroidVariantType = AndroidVariantType.Unknown,
override val androidVariantNames: MutableSet<String> = mutableSetOf()
) : KotlinAndroidSourceSetInfo()
internal fun asMutable(): Mutable = when (this) {
is Mutable -> this
}
}
@@ -0,0 +1,53 @@
/*
* Copyright 2010-2022 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.plugin.sources.android
import org.jetbrains.kotlin.gradle.plugin.sources.android.configurator.*
internal data class KotlinAndroidSourceSetLayout(
val name: String,
val naming: KotlinAndroidSourceSetNaming,
val sourceSetConfigurator: KotlinAndroidSourceSetConfigurator
) {
override fun toString(): String = "KotlinAndroidSourceSetLayout: $name"
}
internal val singleTargetAndroidSourceSetLayout = KotlinAndroidSourceSetLayout(
name = "Kotlin/Android",
naming = SingleTargetKotlinAndroidSourceSetNaming,
sourceSetConfigurator = KotlinAndroidSourceSetConfigurator(
KotlinAndroidSourceSetInfoConfigurator,
AndroidKaptSourceSetConfigurator,
AndroidSourceSetConventionConfigurator,
SingleTargetSourceDirConfigurator,
)
)
internal val multiplatformAndroidSourceSetLayoutV1 = KotlinAndroidSourceSetLayout(
name = "Multiplatform/Android V1",
naming = MultiplatformLayoutV1KotlinAndroidSourceSetNaming,
sourceSetConfigurator = KotlinAndroidSourceSetConfigurator(
KotlinAndroidSourceSetInfoConfigurator,
AndroidKaptSourceSetConfigurator,
AndroidSourceSetConventionConfigurator,
MultiplatformAndroidResourceDirConfigurator,
MultiplatformLayoutV1DependsOnConfigurator,
MultiplatformLayoutV1SourceDirConfigurator
)
)
internal val multiplatformAndroidSourceSetLayoutV2 = KotlinAndroidSourceSetLayout(
name = "Multiplatform/Android V2",
naming = MultiplatformLayoutV2KotlinAndroidSourceSetNaming,
sourceSetConfigurator = KotlinAndroidSourceSetConfigurator(
KotlinAndroidSourceSetInfoConfigurator,
AndroidKaptSourceSetConfigurator,
MultiplatformAndroidResourceDirConfigurator,
MultiplatformLayoutV2DependsOnConfigurator,
MultiplatformLayoutV2SourceDirConfigurator,
MultiplatformLayoutV2DefaultManifestLocationConfigurator
)
)
@@ -0,0 +1,41 @@
/*
* Copyright 2010-2022 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.plugin.sources.android
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
internal interface KotlinAndroidSourceSetNaming {
/**
* Returns non-null name of the corresponding [KotlinSourceSet], if the name of said source set can be
* determined by the [disambiguationClassifier] and [androidSourceSetName] alone.
*
* Returns `null`, if KotlinSourceSet name requires additional [AndroidVariantType] to be determined.
*/
fun kotlinSourceSetName(
disambiguationClassifier: String,
androidSourceSetName: String
): String?
/**
* Returns the name of the corresponding [KotlinSourceSet], if the name of said source set cannot be determined
* by the [disambiguationClassifier] and [androidSourceSetName] alone.
*/
fun kotlinSourceSetName(
disambiguationClassifier: String,
androidSourceSetName: String,
type: AndroidVariantType
): String
/**
* Always capable of creating the [KotlinSourceSet]'s name based upon the disambiguationClassifier and androidSourceSetName alone.
*/
interface Simple : KotlinAndroidSourceSetNaming {
override fun kotlinSourceSetName(disambiguationClassifier: String, androidSourceSetName: String): String
override fun kotlinSourceSetName(disambiguationClassifier: String, androidSourceSetName: String, type: AndroidVariantType): String =
kotlinSourceSetName(disambiguationClassifier, androidSourceSetName)
}
}
@@ -0,0 +1,83 @@
/*
* Copyright 2010-2022 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.plugin.sources.android
import com.android.build.gradle.api.AndroidSourceSet
import org.gradle.api.logging.Logging
import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.forEachVariant
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
import org.jetbrains.kotlin.gradle.utils.androidExtension
import org.jetbrains.kotlin.gradle.utils.getOrCreate
internal object KotlinAndroidSourceSets {
private val logger = Logging.getLogger(this::class.java)
fun setupKotlinAndroidSourceSets(
target: KotlinAndroidTarget,
layout: KotlinAndroidSourceSetLayout = target.project.kotlinAndroidSourceSetLayout
) {
logger.debug("Applying ${KotlinAndroidSourceSetLayout::class.java.simpleName}: ${layout.name}")
val project = target.project
val kotlin = project.kotlinExtension
val android = project.androidExtension
val naming = layout.naming
val configurator = layout.sourceSetConfigurator
/* Ensures that each KotlinSourceSet only invokes the 'configurator' once */
val configuredKotlinSourceSets = mutableSetOf<KotlinSourceSet>()
/* Hook eagerly into AndroidSourceSet creation */
android.sourceSets.all { androidSourceSet ->
val kotlinSourceSetName = naming.kotlinSourceSetName(target.disambiguationClassifier, androidSourceSet.name) ?: return@all
val kotlinSourceSet = kotlin.getOrCreateKotlinSourceSet(kotlinSourceSetName, androidSourceSet)
if (configuredKotlinSourceSets.add(kotlinSourceSet)) {
configurator.configure(target, kotlinSourceSet, androidSourceSet)
}
}
/* Hook into Android's variant creation: This is invoked in 'afterEvaluate' */
forEachVariant(project) { variant ->
variant.sourceSets.forEach { sourceProvider ->
val androidSourceSet = android.sourceSets.findByName(sourceProvider.name) ?: return@forEach
val kotlinSourceSetName = naming.kotlinSourceSetName(target.disambiguationClassifier, sourceProvider.name)
?: naming.kotlinSourceSetName(target.disambiguationClassifier, sourceProvider.name, variant.type)
val kotlinSourceSet = kotlin.getOrCreateKotlinSourceSet(kotlinSourceSetName, androidSourceSet)
if (configuredKotlinSourceSets.add(kotlinSourceSet)) {
configurator.configure(target, kotlinSourceSet, androidSourceSet)
}
configurator.configureWithVariant(target, kotlinSourceSet, variant)
}
}
}
private fun KotlinProjectExtension.getOrCreateKotlinSourceSet(
name: String, androidSourceSet: AndroidSourceSet
): KotlinSourceSet {
return sourceSets.getOrCreate(name) { kotlinSourceSet ->
kotlinSourceSet.androidSourceSetInfoOrNull?.let { info ->
check(info.kotlinSourceSetName == name) { "Bad 'androidSourceSetInfo' on $name: Bad 'kotlinSourceSetName'" }
check(info.androidSourceSetName == androidSourceSet.name) { "Bad 'androidSourceSetInfo' on $name: Bad 'androidSourceSetName'" }
return@getOrCreate
}
kotlinSourceSet.androidSourceSetInfo = KotlinAndroidSourceSetInfo.Mutable(
kotlinSourceSetName = name,
androidSourceSetName = androidSourceSet.name,
androidVariantType = AndroidBaseSourceSetName.byName(androidSourceSet.name)?.variantType ?: AndroidVariantType.Unknown
)
logger.debug("Created KotlinSourceSet: ${kotlinSourceSet.name} for AndroidSourceSet: ${androidSourceSet.name}")
}
}
}
@@ -0,0 +1,13 @@
/*
* Copyright 2010-2022 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.plugin.sources.android
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
internal object MultiplatformLayoutV1KotlinAndroidSourceSetNaming : KotlinAndroidSourceSetNaming.Simple {
override fun kotlinSourceSetName(disambiguationClassifier: String, androidSourceSetName: String): String =
lowerCamelCaseName(disambiguationClassifier, androidSourceSetName)
}
@@ -0,0 +1,42 @@
/*
* Copyright 2010-2022 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.plugin.sources.android
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
internal object MultiplatformLayoutV2KotlinAndroidSourceSetNaming : KotlinAndroidSourceSetNaming {
private val AndroidBaseSourceSetName.kotlinName
get() = when (this) {
AndroidBaseSourceSetName.Main -> "main"
AndroidBaseSourceSetName.Test -> "unitTest"
AndroidBaseSourceSetName.AndroidTest -> "instrumentedTest"
}
override fun kotlinSourceSetName(
disambiguationClassifier: String,
androidSourceSetName: String,
): String? {
val androidBaseSourceSetName = AndroidBaseSourceSetName.byName(androidSourceSetName) ?: return null
return lowerCamelCaseName(disambiguationClassifier, androidBaseSourceSetName.kotlinName)
}
override fun kotlinSourceSetName(
disambiguationClassifier: String,
androidSourceSetName: String,
type: AndroidVariantType
): String {
return lowerCamelCaseName(disambiguationClassifier, replaceAndroidBaseSourceSetName(androidSourceSetName, type))
}
private fun replaceAndroidBaseSourceSetName(
androidSourceSetName: String,
type: AndroidVariantType
): String {
if (type == AndroidVariantType.Main) return androidSourceSetName
val androidBaseSourceSetName = type.androidBaseSourceSetName ?: return androidSourceSetName
return lowerCamelCaseName(androidBaseSourceSetName.kotlinName, androidSourceSetName.removePrefix(androidBaseSourceSetName.name))
}
}
@@ -0,0 +1,15 @@
/*
* Copyright 2010-2022 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.plugin.sources.android
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
internal object SingleTargetKotlinAndroidSourceSetNaming : KotlinAndroidSourceSetNaming.Simple {
override fun kotlinSourceSetName(disambiguationClassifier: String, androidSourceSetName: String): String {
assert(disambiguationClassifier.isEmpty()) { "Unexpected non-empty disambiguationClassifier found: $disambiguationClassifier" }
return lowerCamelCaseName(disambiguationClassifier, androidSourceSetName)
}
}
@@ -0,0 +1,22 @@
/*
* Copyright 2010-2022 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.plugin.sources.android.configurator
import com.android.build.gradle.api.AndroidSourceSet
import org.jetbrains.kotlin.gradle.internal.Kapt3GradleSubplugin
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.ifKaptEnabled
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
internal object AndroidKaptSourceSetConfigurator : KotlinAndroidSourceSetConfigurator {
override fun configure(
target: KotlinAndroidTarget,
kotlinSourceSet: KotlinSourceSet,
androidSourceSet: AndroidSourceSet
) = ifKaptEnabled(target.project) {
Kapt3GradleSubplugin.createAptConfigurationIfNeeded(target.project, androidSourceSet.name)
}
}
@@ -0,0 +1,23 @@
/*
* Copyright 2010-2022 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.plugin.sources.android.configurator
import com.android.build.gradle.api.AndroidSourceSet
import org.jetbrains.kotlin.gradle.plugin.KOTLIN_DSL_NAME
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.addConvention
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
/*
Used by AGP com.android.build.gradle.internal.utils.syncAgpAndKgpSources to automatically pick up sources
from this KotlinSourceSet. This mechanism is deprecated.
*/
@Suppress("DEPRECATION")
internal object AndroidSourceSetConventionConfigurator : KotlinAndroidSourceSetConfigurator {
override fun configure(target: KotlinAndroidTarget, kotlinSourceSet: KotlinSourceSet, androidSourceSet: AndroidSourceSet) {
androidSourceSet.addConvention(KOTLIN_DSL_NAME, kotlinSourceSet)
}
}
@@ -0,0 +1,71 @@
/*
* Copyright 2010-2022 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.plugin.sources.android.configurator
import com.android.build.gradle.api.AndroidSourceSet
import com.android.build.gradle.api.BaseVariant
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
internal interface KotlinAndroidSourceSetConfigurator {
/**
* Called once, when the corresponding KotlinSourceSet is created for a given [AndroidSourceSet].
* Note, this can also be called in 'afterEvaluate', when Android is finalizing its variants.
*/
fun configure(
target: KotlinAndroidTarget, kotlinSourceSet: KotlinSourceSet, androidSourceSet: AndroidSourceSet
) = Unit
/**
* Called every time, when a given [KotlinSourceSet] participates in a given Android variant.
*/
fun configureWithVariant(
target: KotlinAndroidTarget, kotlinSourceSet: KotlinSourceSet, variant: BaseVariant
) = Unit
}
/* Composite implementation */
internal fun KotlinAndroidSourceSetConfigurator(
vararg configurators: KotlinAndroidSourceSetConfigurator?
): KotlinAndroidSourceSetConfigurator {
return CompositeKotlinAndroidSourceSetConfigurator(configurators.filterNotNull())
}
internal operator fun KotlinAndroidSourceSetConfigurator.plus(
other: KotlinAndroidSourceSetConfigurator
): KotlinAndroidSourceSetConfigurator {
val configurators = when {
this is CompositeKotlinAndroidSourceSetConfigurator && other is CompositeKotlinAndroidSourceSetConfigurator ->
this.configurators + other.configurators
this is CompositeKotlinAndroidSourceSetConfigurator -> this.configurators + other
other is CompositeKotlinAndroidSourceSetConfigurator -> listOf(this) + other.configurators
else -> listOf(this, other)
}
return CompositeKotlinAndroidSourceSetConfigurator(configurators)
}
private class CompositeKotlinAndroidSourceSetConfigurator(
val configurators: List<KotlinAndroidSourceSetConfigurator>
) : KotlinAndroidSourceSetConfigurator {
override fun configure(
target: KotlinAndroidTarget,
kotlinSourceSet: KotlinSourceSet,
androidSourceSet: AndroidSourceSet
) {
configurators.forEach { configurator ->
configurator.configure(target, kotlinSourceSet, androidSourceSet)
}
}
override fun configureWithVariant(target: KotlinAndroidTarget, kotlinSourceSet: KotlinSourceSet, variant: BaseVariant) {
configurators.forEach { configurator ->
configurator.configureWithVariant(target, kotlinSourceSet, variant)
}
}
}
@@ -0,0 +1,20 @@
/*
* Copyright 2010-2022 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.plugin.sources.android.configurator
import com.android.build.gradle.api.BaseVariant
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
import org.jetbrains.kotlin.gradle.plugin.sources.android.androidSourceSetInfo
import org.jetbrains.kotlin.gradle.plugin.sources.android.type
internal object KotlinAndroidSourceSetInfoConfigurator : KotlinAndroidSourceSetConfigurator {
override fun configureWithVariant(target: KotlinAndroidTarget, kotlinSourceSet: KotlinSourceSet, variant: BaseVariant) {
val info = kotlinSourceSet.androidSourceSetInfo.asMutable()
info.androidVariantType = variant.type
info.androidVariantNames.add(variant.name)
}
}
@@ -0,0 +1,64 @@
/*
* Copyright 2010-2022 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.plugin.sources.android.configurator
import com.android.build.gradle.api.AndroidSourceSet
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
import org.jetbrains.kotlin.gradle.plugin.sources.KotlinSourceSetFactory
internal object MultiplatformAndroidResourceDirConfigurator : KotlinAndroidSourceSetConfigurator {
override fun configure(
target: KotlinAndroidTarget,
kotlinSourceSet: KotlinSourceSet,
androidSourceSet: AndroidSourceSet
) {
/*
Non-MPP projects will not have a different name for kotlin source sets vs Android source sets
Registering resource folders is unnecessary therefore.
*/
if (kotlinSourceSet.name == androidSourceSet.name) {
return
}
val project = target.project
androidSourceSet.resources.srcDirs(kotlinSourceSet.resources.srcDirs)
if (androidSourceSet.resources.srcDirs.isNotEmpty()) {
androidSourceSet.resources.srcDir(KotlinSourceSetFactory.defaultSourceFolder(project, kotlinSourceSet.name, "resources"))
kotlinSourceSet.resources.srcDirs(androidSourceSet.resources.srcDirs)
}
if (androidSourceSet.assets.srcDirs.isNotEmpty()) {
androidSourceSet.assets.srcDir(KotlinSourceSetFactory.defaultSourceFolder(project, kotlinSourceSet.name, "assets"))
}
if (androidSourceSet.res.srcDirs.isNotEmpty()) {
androidSourceSet.res.srcDir(KotlinSourceSetFactory.defaultSourceFolder(project, kotlinSourceSet.name, "res"))
}
if (androidSourceSet.aidl.srcDirs.isNotEmpty()) {
androidSourceSet.aidl.srcDir(KotlinSourceSetFactory.defaultSourceFolder(project, kotlinSourceSet.name, "aidl"))
}
if (androidSourceSet.renderscript.srcDirs.isNotEmpty()) {
androidSourceSet.renderscript.srcDir(KotlinSourceSetFactory.defaultSourceFolder(project, kotlinSourceSet.name, "rs"))
}
if (androidSourceSet.jni.srcDirs.isNotEmpty()) {
androidSourceSet.jni.srcDir(KotlinSourceSetFactory.defaultSourceFolder(project, kotlinSourceSet.name, "jni"))
}
if (androidSourceSet.jniLibs.srcDirs.isNotEmpty()) {
androidSourceSet.jniLibs.srcDir(KotlinSourceSetFactory.defaultSourceFolder(project, kotlinSourceSet.name, "jniLibs"))
}
if (androidSourceSet.shaders.srcDirs.isNotEmpty()) {
androidSourceSet.shaders.srcDir(KotlinSourceSetFactory.defaultSourceFolder(project, kotlinSourceSet.name, "shaders"))
}
}
}
@@ -0,0 +1,30 @@
/*
* Copyright 2010-2022 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.plugin.sources.android.configurator
import com.android.build.gradle.api.AndroidSourceSet
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet.Companion.COMMON_MAIN_SOURCE_SET_NAME
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet.Companion.COMMON_TEST_SOURCE_SET_NAME
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
internal object MultiplatformLayoutV1DependsOnConfigurator : KotlinAndroidSourceSetConfigurator {
override fun configure(
target: KotlinAndroidTarget,
kotlinSourceSet: KotlinSourceSet,
androidSourceSet: AndroidSourceSet
) {
val commonSourceSetName = when (androidSourceSet.name) {
"main" -> COMMON_MAIN_SOURCE_SET_NAME
"test" -> COMMON_TEST_SOURCE_SET_NAME
"androidTest" -> COMMON_TEST_SOURCE_SET_NAME
else -> return
}
val commonSourceSet = target.project.kotlinExtension.sourceSets.findByName(commonSourceSetName) ?: return
kotlinSourceSet.dependsOn(commonSourceSet)
}
}
@@ -0,0 +1,29 @@
/*
* Copyright 2010-2022 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.plugin.sources.android.configurator
import com.android.build.gradle.api.AndroidSourceSet
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
internal object MultiplatformLayoutV1SourceDirConfigurator : KotlinAndroidSourceSetConfigurator {
override fun configure(target: KotlinAndroidTarget, kotlinSourceSet: KotlinSourceSet, androidSourceSet: AndroidSourceSet) {
/*
Mitigate ambiguity!
Example: disambiguationClassifier="android"
Source Directory "src/androidTest/kotlin"
-- could be claimed by kotlin {android}Test (unit test)
-- could be claimed by Android androidTest (instrumented test)
The Kotlin source set would win in this scenario.
*/
if (!androidSourceSet.name.startsWith(target.disambiguationClassifier)) {
kotlinSourceSet.kotlin.srcDir("src/${androidSourceSet.name}/kotlin")
}
kotlinSourceSet.kotlin.srcDir(target.project.provider { androidSourceSet.java.srcDirs })
}
}
@@ -0,0 +1,19 @@
/*
* Copyright 2010-2022 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.plugin.sources.android.configurator
import com.android.build.gradle.api.AndroidSourceSet
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
internal object MultiplatformLayoutV2DefaultManifestLocationConfigurator : KotlinAndroidSourceSetConfigurator {
override fun configure(target: KotlinAndroidTarget, kotlinSourceSet: KotlinSourceSet, androidSourceSet: AndroidSourceSet) {
/* Default can only be set when the entity is created 'fresh'. Changes here in afterEvaluate might overwrite user setup */
if (!target.project.state.executed) {
androidSourceSet.manifest.srcFile("src/${kotlinSourceSet.name}/AndroidManifest.xml")
}
}
}
@@ -0,0 +1,41 @@
/*
* Copyright 2010-2022 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.plugin.sources.android.configurator
import com.android.build.gradle.api.AndroidSourceSet
import com.android.build.gradle.api.BaseVariant
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet.Companion.COMMON_MAIN_SOURCE_SET_NAME
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet.Companion.COMMON_TEST_SOURCE_SET_NAME
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
import org.jetbrains.kotlin.gradle.plugin.sources.android.AndroidBaseSourceSetName
import org.jetbrains.kotlin.gradle.plugin.sources.android.AndroidVariantType
import org.jetbrains.kotlin.gradle.plugin.sources.android.type
import org.jetbrains.kotlin.gradle.plugin.sources.android.variantType
internal object MultiplatformLayoutV2DependsOnConfigurator : KotlinAndroidSourceSetConfigurator {
override fun configure(target: KotlinAndroidTarget, kotlinSourceSet: KotlinSourceSet, androidSourceSet: AndroidSourceSet) {
val androidBaseSourceSetName = AndroidBaseSourceSetName.byName(androidSourceSet.name) ?: return
setDefaultDependsOn(target, kotlinSourceSet, androidBaseSourceSetName.variantType)
}
override fun configureWithVariant(target: KotlinAndroidTarget, kotlinSourceSet: KotlinSourceSet, variant: BaseVariant) {
setDefaultDependsOn(target, kotlinSourceSet, variant.type)
}
private fun setDefaultDependsOn(target: KotlinAndroidTarget, kotlinSourceSet: KotlinSourceSet, variantType: AndroidVariantType) {
/* Add default dependency on 'commonMain' or 'commonTest' */
val commonMain = target.project.kotlinExtension.sourceSets.getByName(COMMON_MAIN_SOURCE_SET_NAME)
val commonTest = target.project.kotlinExtension.sourceSets.getByName(COMMON_TEST_SOURCE_SET_NAME)
when (variantType) {
AndroidVariantType.Main -> kotlinSourceSet.dependsOn(commonMain)
AndroidVariantType.UnitTest -> kotlinSourceSet.dependsOn(commonTest)
AndroidVariantType.InstrumentedTest, AndroidVariantType.Unknown -> Unit
}
}
}
@@ -0,0 +1,22 @@
/*
* Copyright 2010-2022 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.plugin.sources.android.configurator
import com.android.build.gradle.api.AndroidSourceDirectorySet
import com.android.build.gradle.api.AndroidSourceSet
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
internal object MultiplatformLayoutV2SourceDirConfigurator : KotlinAndroidSourceSetConfigurator {
override fun configure(target: KotlinAndroidTarget, kotlinSourceSet: KotlinSourceSet, androidSourceSet: AndroidSourceSet) {
val androidKotlinSourceDirectorySet = androidSourceSet.javaClass.getMethod("getKotlin")
.invoke(androidSourceSet) as AndroidSourceDirectorySet
androidKotlinSourceDirectorySet.srcDirs(target.project.provider { kotlinSourceSet.kotlin.srcDirs })
kotlinSourceSet.kotlin.srcDir(target.project.provider { androidSourceSet.java.srcDirs })
kotlinSourceSet.kotlin.srcDir("src/${androidSourceSet.name}/kotlin")
}
}
@@ -0,0 +1,17 @@
/*
* Copyright 2010-2022 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.plugin.sources.android.configurator
import com.android.build.gradle.api.AndroidSourceSet
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
internal object SingleTargetSourceDirConfigurator : KotlinAndroidSourceSetConfigurator {
override fun configure(target: KotlinAndroidTarget, kotlinSourceSet: KotlinSourceSet, androidSourceSet: AndroidSourceSet) {
kotlinSourceSet.kotlin.srcDir("src/${androidSourceSet.name}/kotlin")
kotlinSourceSet.kotlin.srcDir(target.project.provider { androidSourceSet.java.srcDirs })
}
}
@@ -0,0 +1,25 @@
/*
* Copyright 2010-2022 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.plugin.sources.android
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.Companion.kotlinPropertiesProvider
internal val Project.kotlinAndroidSourceSetLayout: KotlinAndroidSourceSetLayout
get() {
return if (kotlinExtension is KotlinMultiplatformExtension) {
when (val version = kotlinPropertiesProvider.mppAndroidSourceSetLayoutVersion) {
null, 1 -> multiplatformAndroidSourceSetLayoutV1
2 -> multiplatformAndroidSourceSetLayoutV2
else -> throw IllegalArgumentException(
"Unsupported '${PropertiesProvider.PropertyNames.KOTLIN_MPP_ANDROID_SOURCE_SET_LAYOUT_VERSION}=$version' Supported versions: {1, 2}"
)
}
} else singleTargetAndroidSourceSetLayout
}
@@ -28,7 +28,6 @@ import org.gradle.api.tasks.bundling.AbstractArchiveTask
import org.gradle.api.tasks.compile.AbstractCompile
import org.gradle.api.tasks.compile.JavaCompile
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptionsImpl
import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
import org.jetbrains.kotlin.gradle.internal.Kapt3GradleSubplugin
import org.jetbrains.kotlin.gradle.internal.checkAndroidAnnotationProcessorDependencyUsage
@@ -36,7 +35,8 @@ import org.jetbrains.kotlin.gradle.logging.kotlinDebug
import org.jetbrains.kotlin.gradle.plugin.android.AndroidGradleWrapper
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmAndroidCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.syncKotlinAndAndroidSourceSets
import org.jetbrains.kotlin.gradle.plugin.sources.android.KotlinAndroidSourceSets.setupKotlinAndroidSourceSets
import org.jetbrains.kotlin.gradle.plugin.sources.android.findKotlinSourceSet
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.jetbrains.kotlin.gradle.tasks.KotlinTasksProvider
import org.jetbrains.kotlin.gradle.tasks.configuration.KotlinCompileConfig
@@ -55,19 +55,14 @@ import java.util.concurrent.Callable
internal class AndroidProjectHandler(
private val kotlinConfigurationTools: KotlinConfigurationTools
) {
companion object {
fun kotlinSourceSetNameForAndroidSourceSet(kotlinAndroidTarget: KotlinAndroidTarget, androidSourceSetName: String) =
lowerCamelCaseName(kotlinAndroidTarget.disambiguationClassifier, androidSourceSetName)
}
private val logger = Logging.getLogger(this.javaClass)
fun configureTarget(kotlinAndroidTarget: KotlinAndroidTarget) {
syncKotlinAndAndroidSourceSets(kotlinAndroidTarget)
val project = kotlinAndroidTarget.project
val ext = project.extensions.getByName("android") as BaseExtension
setupKotlinAndroidSourceSets(kotlinAndroidTarget)
val kotlinOptions = KotlinJvmOptionsImpl()
kotlinOptions.noJdk = true
ext.addExtension(KOTLIN_OPTIONS_DSL_NAME, kotlinOptions)
@@ -109,7 +104,7 @@ internal class AndroidProjectHandler(
}
checkAndroidAnnotationProcessorDependencyUsage(project)
addKotlinDependenciesToAndroidSourceSets(project, kotlinAndroidTarget)
addKotlinDependenciesToAndroidSourceSets(project)
}
project.includeKotlinToolingMetadataInApk()
@@ -123,10 +118,7 @@ internal class AndroidProjectHandler(
* source set dependencies as well, we need to add them to the Android source sets' api/implementation-like configurations,
* not just the classpath-like configurations of the variants.
*/
private fun addKotlinDependenciesToAndroidSourceSets(
project: Project,
kotlinAndroidTarget: KotlinAndroidTarget
) {
private fun addKotlinDependenciesToAndroidSourceSets(project: Project) {
fun addDependenciesToAndroidSourceSet(
androidSourceSet: AndroidSourceSet,
apiConfigurationName: String,
@@ -154,8 +146,7 @@ internal class AndroidProjectHandler(
* see [org.jetbrains.kotlin.gradle.plugin.AndroidProjectHandler.configureTarget]
*/
(project.extensions.getByName("android") as BaseExtension).sourceSets.forEach { androidSourceSet ->
val kotlinSourceSetName = kotlinSourceSetNameForAndroidSourceSet(kotlinAndroidTarget, androidSourceSet.name)
project.kotlinExtension.sourceSets.findByName(kotlinSourceSetName)?.let { kotlinSourceSet ->
project.findKotlinSourceSet(androidSourceSet)?.let { kotlinSourceSet ->
addDependenciesToAndroidSourceSet(
androidSourceSet,
kotlinSourceSet.apiConfigurationName,
@@ -212,10 +203,7 @@ internal class AndroidProjectHandler(
// Register the source only after the task is created, because the task is required for that:
compilation.source(defaultSourceSet)
compilation.androidVariant.forEachKotlinSourceSet(
compilation.target as KotlinAndroidTarget,
project.kotlinExtension
) { kotlinSourceSet ->
compilation.androidVariant.forEachKotlinSourceSet(project) { kotlinSourceSet ->
compilation.source(kotlinSourceSet)
}
}
@@ -464,22 +452,17 @@ class AndroidTestedVariantArtifactsFilter(
}
internal inline fun BaseVariant.forEachKotlinSourceSet(
kotlinAndroidTarget: KotlinAndroidTarget,
kotlinExtension: KotlinProjectExtension,
action: (KotlinSourceSet) -> Unit
project: Project, action: (KotlinSourceSet) -> Unit
) {
sourceSets
.mapNotNull { provider ->
val kotlinSourceSetName = AndroidProjectHandler.kotlinSourceSetNameForAndroidSourceSet(kotlinAndroidTarget, provider.name)
kotlinExtension.sourceSets.findByName(kotlinSourceSetName)
}
.forEach(action)
.forEach { provider -> action(project.findKotlinSourceSet(provider) ?: return@forEach) }
}
internal inline fun BaseVariant.forEachKotlinSourceDirectorySet(action: (SourceDirectorySet) -> Unit) {
internal inline fun BaseVariant.forEachKotlinSourceDirectorySet(
project: Project, action: (SourceDirectorySet) -> Unit
) {
sourceSets
.mapNotNull { it.getExtension<SourceDirectorySet>(KOTLIN_DSL_NAME) }
.forEach(action)
.forEach { androidSourceSet -> action(project.findKotlinSourceSet(androidSourceSet)?.kotlin ?: return@forEach) }
}
internal inline fun BaseVariant.forEachJavaSourceDir(action: (ConfigurableFileTree) -> Unit) {
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.gradle.utils
import org.gradle.api.DomainObjectCollection
import org.gradle.api.NamedDomainObjectContainer
import org.gradle.api.Task
import org.gradle.api.tasks.TaskCollection
import org.gradle.api.tasks.TaskProvider
@@ -13,8 +14,18 @@ import org.gradle.api.tasks.TaskProvider
internal inline fun <reified S> DomainObjectCollection<in S>.withType(): DomainObjectCollection<S> =
withType(S::class.java)
@Suppress("extension_shadowed_by_member", "UNCHECKED_CAST")
inline fun <reified T : Task> TaskCollection<out Task>.named(name: String): TaskProvider<T> =
(this as TaskCollection<T>).named(name, T::class.java)
internal fun <T> NamedDomainObjectContainer<T>.getOrCreate(
name: String,
invokeWhenCreated: ((T) -> Unit)? = null,
configure: ((T) -> Unit)? = null
): T {
return if (name in names) named(name).also { provider ->
if (configure != null) provider.configure(configure)
}.get() else (if (configure != null) create(name, configure) else create(name)).also { value ->
if (invokeWhenCreated != null) invokeWhenCreated(value)
}
}
@@ -0,0 +1,13 @@
/*
* Copyright 2010-2022 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
import com.android.build.gradle.BaseExtension
import org.gradle.api.Project
internal val Project.androidExtensionOrNull: BaseExtension? get() = extensions.findByName("android")?.let { it as? BaseExtension }
internal val Project.androidExtension: BaseExtension get() = extensions.getByName("android") as BaseExtension
@@ -15,6 +15,7 @@ import org.gradle.testfixtures.ProjectBuilder
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_KPM_EXPERIMENTAL_MODEL_MAPPING
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_MPP_ANDROID_SOURCE_SET_LAYOUT_VERSION
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_MPP_ENABLE_CINTEROP_COMMONIZATION
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_MPP_HIERARCHICAL_STRUCTURE_BY_DEFAULT
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_STDLIB_DEFAULT_DEPENDENCY
@@ -111,3 +112,7 @@ fun Project.enableKpmModelMapping(enabled: Boolean = true) {
fun Project.enableDefaultStdlibDependency(enabled: Boolean = true) {
project.propertiesExtension.set(KOTLIN_STDLIB_DEFAULT_DEPENDENCY, enabled.toString())
}
fun Project.setAndroidMultiplatformSourceSetLayoutVersion(version: Int) {
project.propertiesExtension.set(KOTLIN_MPP_ANDROID_SOURCE_SET_LAYOUT_VERSION, version.toString())
}
@@ -1,23 +1,27 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Copyright 2010-2022 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.
*/
/* Associate compilations are not yet supported by the IDE. KT-34102 */
@file:Suppress("invisible_reference", "invisible_member", "FunctionName", "DuplicatedCode")
@file:Suppress("DEPRECATION", "DuplicatedCode", "FunctionName")
package org.jetbrains.kotlin.gradle
package org.jetbrains.kotlin.gradle.sources.android
import com.android.build.gradle.LibraryExtension
import com.android.build.gradle.api.AndroidSourceSet
import org.gradle.api.Action
import org.gradle.api.internal.HasConvention
import org.gradle.api.internal.project.ProjectInternal
import org.gradle.testfixtures.ProjectBuilder
import org.jetbrains.kotlin.gradle.addBuildEventsListenerRegistryMock
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.mpp.kotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.sources.android.findKotlinSourceSet
import org.jetbrains.kotlin.gradle.setAndroidMultiplatformSourceSetLayoutVersion
import kotlin.test.*
class SyncKotlinAndAndroidSourceSetsTest {
class MultiplatformAndroidSourceSetLayoutV1Test {
private lateinit var project: ProjectInternal
private lateinit var kotlin: KotlinMultiplatformExtension
@@ -27,6 +31,8 @@ class SyncKotlinAndAndroidSourceSetsTest {
fun setup() {
project = ProjectBuilder.builder().build() as ProjectInternal
addBuildEventsListenerRegistryMock(project)
project.setAndroidMultiplatformSourceSetLayoutVersion(1)
project.plugins.apply("kotlin-multiplatform")
project.plugins.apply("android-library")
@@ -36,7 +42,6 @@ class SyncKotlinAndAndroidSourceSetsTest {
/* Kotlin Setup */
kotlin = project.multiplatformExtension
}
@@ -93,21 +98,19 @@ class SyncKotlinAndAndroidSourceSetsTest {
@Test
fun `two product flavor dimensions`() {
android.flavorDimensions("pricing", "releaseType")
android.productFlavors(
Action {
it.create("beta").dimension = "releaseType"
it.create("production").dimension = "releaseType"
it.create("free").dimension = "pricing"
it.create("paid").dimension = "pricing"
}
)
android.productFlavors {
it.create("beta").dimension = "releaseType"
it.create("production").dimension = "releaseType"
it.create("free").dimension = "pricing"
it.create("paid").dimension = "pricing"
}
kotlin.android()
project.evaluate()
fun assertSourceSetsExist(androidName: String, kotlinName: String) {
val androidSourceSet = assertNotNull(android.sourceSets.findByName(androidName), "Expected Android source set '$androidName'")
val kotlinSourceSet = assertNotNull(kotlin.sourceSets.findByName(kotlinName), "Expected Kotlin source set '$kotlinName'")
assertSame(kotlinSourceSet.kotlin, androidSourceSet.kotlinSourceSet)
assertSame(kotlinSourceSet, androidSourceSet.kotlinSourceSet)
}
assertSourceSetsExist("freeBetaDebug", "androidFreeBetaDebug")
@@ -178,18 +181,24 @@ class SyncKotlinAndAndroidSourceSetsTest {
}
@Test
fun `AndroidSourceSet#kotlinSourceSet extension`() {
fun `AndroidSourceSet#kotlinSourceSet convention`() {
kotlin.android()
fun AndroidSourceSet.kotlinSourceSetByConvention(): KotlinSourceSet =
(this as HasConvention).convention.plugins["kotlin"] as KotlinSourceSet
val main = android.sourceSets.getByName("main")
assertSame(kotlin.sourceSets.getByName("androidMain").kotlin, main.kotlinSourceSet)
assertSame(kotlin.sourceSets.getByName("androidMain"), main.kotlinSourceSetByConvention())
val test = android.sourceSets.getByName("test")
assertSame(kotlin.sourceSets.getByName("androidTest").kotlin, test.kotlinSourceSet)
assertSame(kotlin.sourceSets.getByName("androidTest"), test.kotlinSourceSetByConvention())
val androidTest = android.sourceSets.getByName("androidTest")
assertSame(kotlin.sourceSets.getByName("androidAndroidTest").kotlin, androidTest.kotlinSourceSet)
assertSame(kotlin.sourceSets.getByName("androidAndroidTest"), androidTest.kotlinSourceSetByConvention())
}
private val AndroidSourceSet.kotlinSourceSet
get() = project.findKotlinSourceSet(this) ?: fail("Missing KotlinSourceSet for AndroidSourceSet: $name")
}
private fun <T> Set<T>.allPairs(): Sequence<Pair<T, T>> {
@@ -203,4 +212,4 @@ private fun <T> Set<T>.allPairs(): Sequence<Pair<T, T>> {
}
}
}
}
}