From fe5a4bb12a7aa3e948f326f2c2509e04468d5e38 Mon Sep 17 00:00:00 2001 From: "sebastian.sellmair" Date: Mon, 1 Aug 2022 14:36:05 +0200 Subject: [PATCH] [Gradle][KT-53342] Implement Multiplatform/Android V2 source set layout ^KT-53342 Verification Pending --- .idea/kotlinc.xml | 5 +- .../internal/KotlinDependenciesManagement.kt | 39 ++--- .../plugin/KotlinMultiplatformPlugin.kt | 2 +- .../kotlin/gradle/plugin/KotlinProperties.kt | 5 + .../gradle/plugin/SubpluginEnvironment.kt | 2 +- .../plugin/mpp/UnusedSourceSetsChecker.kt | 10 +- .../mpp/syncKotlinAndAndroidSourceSets.kt | 148 ------------------ .../android/AndroidBaseSourceSetName.kt | 38 +++++ .../sources/android/AndroidVariantType.kt | 39 +++++ .../android/KotlinAndroidSourceSetInfo.kt | 69 ++++++++ .../android/KotlinAndroidSourceSetLayout.kt | 53 +++++++ .../android/KotlinAndroidSourceSetNaming.kt | 41 +++++ .../android/KotlinAndroidSourceSets.kt | 83 ++++++++++ ...ormLayoutV1KotlinAndroidSourceSetNaming.kt | 13 ++ ...ormLayoutV2KotlinAndroidSourceSetNaming.kt | 42 +++++ ...ingleTargetKotlinAndroidSourceSetNaming.kt | 15 ++ .../AndroidKaptSourceSetConfigurator.kt | 22 +++ .../AndroidSourceSetConventionConfigurator.kt | 23 +++ .../KotlinAndroidSourceSetConfigurator.kt | 71 +++++++++ .../KotlinAndroidSourceSetInfoConfigurator.kt | 20 +++ ...iplatformAndroidResourceDirConfigurator.kt | 64 ++++++++ ...tiplatformLayoutV1DependsOnConfigurator.kt | 30 ++++ ...tiplatformLayoutV1SourceDirConfigurator.kt | 29 ++++ ...utV2DefaultManifestLocationConfigurator.kt | 19 +++ ...tiplatformLayoutV2DependsOnConfigurator.kt | 41 +++++ ...tiplatformLayoutV2SourceDirConfigurator.kt | 22 +++ .../SingleTargetSourceDirConfigurator.kt | 17 ++ .../kotlinAndroidSourceSetLayoutExtension.kt | 25 +++ .../targets/android/AndroidProjectHandler.kt | 45 ++---- .../gradle/utils/ObjectCollectionExt.kt | 13 +- .../gradle/utils/androidExtensionUitls.kt | 13 ++ .../gradle/MultiplatformExtensionTest.kt | 5 + ...tiplatformAndroidSourceSetLayoutV1Test.kt} | 51 +++--- 33 files changed, 875 insertions(+), 239 deletions(-) delete mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/syncKotlinAndAndroidSourceSets.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/AndroidBaseSourceSetName.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/AndroidVariantType.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/KotlinAndroidSourceSetInfo.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/KotlinAndroidSourceSetLayout.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/KotlinAndroidSourceSetNaming.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/KotlinAndroidSourceSets.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/MultiplatformLayoutV1KotlinAndroidSourceSetNaming.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/MultiplatformLayoutV2KotlinAndroidSourceSetNaming.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/SingleTargetKotlinAndroidSourceSetNaming.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/configurator/AndroidKaptSourceSetConfigurator.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/configurator/AndroidSourceSetConventionConfigurator.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/configurator/KotlinAndroidSourceSetConfigurator.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/configurator/KotlinAndroidSourceSetInfoConfigurator.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/configurator/MultiplatformAndroidResourceDirConfigurator.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/configurator/MultiplatformLayoutV1DependsOnConfigurator.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/configurator/MultiplatformLayoutV1SourceDirConfigurator.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/configurator/MultiplatformLayoutV2DefaultManifestLocationConfigurator.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/configurator/MultiplatformLayoutV2DependsOnConfigurator.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/configurator/MultiplatformLayoutV2SourceDirConfigurator.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/configurator/SingleTargetSourceDirConfigurator.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/kotlinAndroidSourceSetLayoutExtension.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/androidExtensionUitls.kt rename libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/{SyncKotlinAndAndroidSourceSetsTest.kt => sources/android/MultiplatformAndroidSourceSetLayoutV1Test.kt} (81%) diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index b7c117d82c0..a6cc312853c 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -8,4 +8,7 @@ - + + + \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/internal/KotlinDependenciesManagement.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/internal/KotlinDependenciesManagement.kt index e6cf516a816..9051d3922b0 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/internal/KotlinDependenciesManagement.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/internal/KotlinDependenciesManagement.kt @@ -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") diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinMultiplatformPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinMultiplatformPlugin.kt index f7d719d0aa5..83311757283 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinMultiplatformPlugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinMultiplatformPlugin.kt @@ -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") diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinProperties.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinProperties.kt index d25e6b640a9..1c79ec4be37 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinProperties.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinProperties.kt @@ -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" diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/SubpluginEnvironment.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/SubpluginEnvironment.kt index 5bf6e6f3a88..9fefbd09330 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/SubpluginEnvironment.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/SubpluginEnvironment.kt @@ -94,7 +94,7 @@ internal fun addCompilationSourcesToExternalCompileTask( task: TaskProvider> ) { if (compilation is KotlinJvmAndroidCompilation) { - compilation.androidVariant.forEachKotlinSourceDirectorySet { sourceSet -> + compilation.androidVariant.forEachKotlinSourceDirectorySet(compilation.project) { sourceSet -> task.configure { it.setSource(sourceSet) } } compilation.androidVariant.forEachJavaSourceDir { sources -> diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/UnusedSourceSetsChecker.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/UnusedSourceSetsChecker.kt index 9162e97e5f9..5959b378c26 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/UnusedSourceSetsChecker.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/UnusedSourceSetsChecker.kt @@ -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()) } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/syncKotlinAndAndroidSourceSets.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/syncKotlinAndAndroidSourceSets.kt deleted file mode 100644 index 51721588769..00000000000 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/syncKotlinAndAndroidSourceSets.kt +++ /dev/null @@ -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() - 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")) - } -} - diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/AndroidBaseSourceSetName.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/AndroidBaseSourceSetName.kt new file mode 100644 index 00000000000..67ee86480cf --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/AndroidBaseSourceSetName.kt @@ -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.main: AndroidSourceSet + get() = getByName(AndroidBaseSourceSetName.Main.name) + +internal val NamedDomainObjectContainer.test: AndroidSourceSet + get() = getByName(AndroidBaseSourceSetName.Test.name) + +internal val NamedDomainObjectContainer.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 + } + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/AndroidVariantType.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/AndroidVariantType.kt new file mode 100644 index 00000000000..6d658f99a28 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/AndroidVariantType.kt @@ -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 + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/KotlinAndroidSourceSetInfo.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/KotlinAndroidSourceSetInfo.kt new file mode 100644 index 00000000000..d605677817a --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/KotlinAndroidSourceSetInfo.kt @@ -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() ?: 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 + + internal data class Mutable( + override val kotlinSourceSetName: String, + override val androidSourceSetName: String, + override var androidVariantType: AndroidVariantType = AndroidVariantType.Unknown, + override val androidVariantNames: MutableSet = mutableSetOf() + ) : KotlinAndroidSourceSetInfo() + + internal fun asMutable(): Mutable = when (this) { + is Mutable -> this + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/KotlinAndroidSourceSetLayout.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/KotlinAndroidSourceSetLayout.kt new file mode 100644 index 00000000000..1e33785d6fd --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/KotlinAndroidSourceSetLayout.kt @@ -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 + ) +) diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/KotlinAndroidSourceSetNaming.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/KotlinAndroidSourceSetNaming.kt new file mode 100644 index 00000000000..3d7e5174659 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/KotlinAndroidSourceSetNaming.kt @@ -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) + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/KotlinAndroidSourceSets.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/KotlinAndroidSourceSets.kt new file mode 100644 index 00000000000..9d062609394 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/KotlinAndroidSourceSets.kt @@ -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() + + /* 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}") + } + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/MultiplatformLayoutV1KotlinAndroidSourceSetNaming.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/MultiplatformLayoutV1KotlinAndroidSourceSetNaming.kt new file mode 100644 index 00000000000..375998d6546 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/MultiplatformLayoutV1KotlinAndroidSourceSetNaming.kt @@ -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) +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/MultiplatformLayoutV2KotlinAndroidSourceSetNaming.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/MultiplatformLayoutV2KotlinAndroidSourceSetNaming.kt new file mode 100644 index 00000000000..5bfd5bdafa4 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/MultiplatformLayoutV2KotlinAndroidSourceSetNaming.kt @@ -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)) + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/SingleTargetKotlinAndroidSourceSetNaming.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/SingleTargetKotlinAndroidSourceSetNaming.kt new file mode 100644 index 00000000000..cc5510a3f47 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/SingleTargetKotlinAndroidSourceSetNaming.kt @@ -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) + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/configurator/AndroidKaptSourceSetConfigurator.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/configurator/AndroidKaptSourceSetConfigurator.kt new file mode 100644 index 00000000000..e3ba8a7388e --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/configurator/AndroidKaptSourceSetConfigurator.kt @@ -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) + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/configurator/AndroidSourceSetConventionConfigurator.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/configurator/AndroidSourceSetConventionConfigurator.kt new file mode 100644 index 00000000000..11d21618fb5 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/configurator/AndroidSourceSetConventionConfigurator.kt @@ -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) + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/configurator/KotlinAndroidSourceSetConfigurator.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/configurator/KotlinAndroidSourceSetConfigurator.kt new file mode 100644 index 00000000000..42b4ba748eb --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/configurator/KotlinAndroidSourceSetConfigurator.kt @@ -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 { + 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) + } + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/configurator/KotlinAndroidSourceSetInfoConfigurator.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/configurator/KotlinAndroidSourceSetInfoConfigurator.kt new file mode 100644 index 00000000000..0335454f81a --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/configurator/KotlinAndroidSourceSetInfoConfigurator.kt @@ -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) + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/configurator/MultiplatformAndroidResourceDirConfigurator.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/configurator/MultiplatformAndroidResourceDirConfigurator.kt new file mode 100644 index 00000000000..3a7f7abe175 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/configurator/MultiplatformAndroidResourceDirConfigurator.kt @@ -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")) + } + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/configurator/MultiplatformLayoutV1DependsOnConfigurator.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/configurator/MultiplatformLayoutV1DependsOnConfigurator.kt new file mode 100644 index 00000000000..447c2ce55a8 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/configurator/MultiplatformLayoutV1DependsOnConfigurator.kt @@ -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) + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/configurator/MultiplatformLayoutV1SourceDirConfigurator.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/configurator/MultiplatformLayoutV1SourceDirConfigurator.kt new file mode 100644 index 00000000000..f4d0ab09927 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/configurator/MultiplatformLayoutV1SourceDirConfigurator.kt @@ -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 }) + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/configurator/MultiplatformLayoutV2DefaultManifestLocationConfigurator.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/configurator/MultiplatformLayoutV2DefaultManifestLocationConfigurator.kt new file mode 100644 index 00000000000..3ebd4ea93a8 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/configurator/MultiplatformLayoutV2DefaultManifestLocationConfigurator.kt @@ -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") + } + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/configurator/MultiplatformLayoutV2DependsOnConfigurator.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/configurator/MultiplatformLayoutV2DependsOnConfigurator.kt new file mode 100644 index 00000000000..639c61e5998 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/configurator/MultiplatformLayoutV2DependsOnConfigurator.kt @@ -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 + } + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/configurator/MultiplatformLayoutV2SourceDirConfigurator.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/configurator/MultiplatformLayoutV2SourceDirConfigurator.kt new file mode 100644 index 00000000000..10bb687644c --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/configurator/MultiplatformLayoutV2SourceDirConfigurator.kt @@ -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") + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/configurator/SingleTargetSourceDirConfigurator.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/configurator/SingleTargetSourceDirConfigurator.kt new file mode 100644 index 00000000000..f31a393bbf4 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/configurator/SingleTargetSourceDirConfigurator.kt @@ -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 }) + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/kotlinAndroidSourceSetLayoutExtension.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/kotlinAndroidSourceSetLayoutExtension.kt new file mode 100644 index 00000000000..f7eb942a9b1 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/android/kotlinAndroidSourceSetLayoutExtension.kt @@ -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 + } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/android/AndroidProjectHandler.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/android/AndroidProjectHandler.kt index 6590029af8b..93f9d8c7a36 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/android/AndroidProjectHandler.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/android/AndroidProjectHandler.kt @@ -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(KOTLIN_DSL_NAME) } - .forEach(action) + .forEach { androidSourceSet -> action(project.findKotlinSourceSet(androidSourceSet)?.kotlin ?: return@forEach) } } internal inline fun BaseVariant.forEachJavaSourceDir(action: (ConfigurableFileTree) -> Unit) { diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/ObjectCollectionExt.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/ObjectCollectionExt.kt index 743972a5dc8..17cdf111ea9 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/ObjectCollectionExt.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/ObjectCollectionExt.kt @@ -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 DomainObjectCollection.withType(): DomainObjectCollection = withType(S::class.java) - @Suppress("extension_shadowed_by_member", "UNCHECKED_CAST") inline fun TaskCollection.named(name: String): TaskProvider = (this as TaskCollection).named(name, T::class.java) +internal fun NamedDomainObjectContainer.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) + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/androidExtensionUitls.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/androidExtensionUitls.kt new file mode 100644 index 00000000000..a797643cfa0 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/androidExtensionUitls.kt @@ -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 \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/MultiplatformExtensionTest.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/MultiplatformExtensionTest.kt index 101db77e1b4..7eb55ffe4cb 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/MultiplatformExtensionTest.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/MultiplatformExtensionTest.kt @@ -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()) +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/SyncKotlinAndAndroidSourceSetsTest.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/sources/android/MultiplatformAndroidSourceSetLayoutV1Test.kt similarity index 81% rename from libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/SyncKotlinAndAndroidSourceSetsTest.kt rename to libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/sources/android/MultiplatformAndroidSourceSetLayoutV1Test.kt index 800c135e71c..b8003566cc5 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/SyncKotlinAndAndroidSourceSetsTest.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/sources/android/MultiplatformAndroidSourceSetLayoutV1Test.kt @@ -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 Set.allPairs(): Sequence> { @@ -203,4 +212,4 @@ private fun Set.allPairs(): Sequence> { } } } -} +} \ No newline at end of file